0
0
mirror of https://github.com/PostHog/posthog.git synced 2024-11-24 00:47:50 +01:00

Revert "Revert "Continuation - Frontend - Refactor event & properties for async server-side loading (#4078)" (#4092)" (#4093)

This reverts commit d67ab843f1.
This commit is contained in:
Paolo D'Amico 2021-04-23 17:13:50 -07:00 committed by GitHub
parent c0bdcb8556
commit a7352b4105
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 417 additions and 277 deletions

View File

@ -1,5 +1,6 @@
# This file contains a list of 'ignored' typescript errors. If any new ones are added they will cause ci breakage
# Add lines via bin/check-typescript-strict | cut -d":" -f1
# You can get an updated list of blacklisted errors by running bin/check-typescript-strict | cut -d":" -f1
# You will need to manually update this file if applicable.
Type.ts
frontend/src/initKea.ts(2,32)
frontend/src/initKea.ts(4,31)
@ -60,13 +61,6 @@ frontend/src/scenes/sessions/SessionDetails.tsx(55,51)
frontend/src/scenes/sessions/SessionsView.tsx(199,29)
frontend/src/scenes/sessions/filters/DurationFilter.tsx(27,34)
frontend/src/scenes/sessions/filters/EventPropertyFilter.tsx(21,41)
frontend/src/scenes/sessions/filters/SessionsFilterBox.tsx(43,13)
frontend/src/scenes/sessions/filters/SessionsFilterBox.tsx(44,13)
frontend/src/scenes/sessions/filters/SessionsFilterBox.tsx(78,13)
frontend/src/scenes/sessions/filters/SessionsFilterBox.tsx(79,13)
frontend/src/scenes/sessions/filters/SessionsFilterBox.tsx(87,13)
frontend/src/scenes/sessions/filters/SessionsFilterBox.tsx(116,13)
frontend/src/scenes/sessions/filters/SessionsFilterBox.tsx(173,9)
frontend/src/scenes/sessions/sessionsPlayLogic.ts(155,61)
frontend/src/toolbar/ToolbarApp.tsx(35,74)
frontend/src/toolbar/actions/SelectorCount.tsx(2,38)

View File

@ -3,13 +3,11 @@ import Fuse from 'fuse.js'
import { Select } from 'antd'
import { PropertyKeyInfo } from 'lib/components/PropertyKeyInfo'
import { SelectGradientOverflow } from 'lib/components/SelectGradientOverflow'
import { EventProperty } from 'scenes/teamLogic'
type PropertyOption = EventProperty
import { SelectOption } from '~/types'
interface Props {
optionGroups: Array<PropertyOptionGroup>
value: Partial<PropertyOption> | null
value: Partial<SelectOption> | null
onChange: (type: PropertyOptionGroup['type'], value: string) => void
placeholder: string
autoOpenIfEmpty?: boolean

View File

@ -2,7 +2,7 @@ import { kea } from 'kea'
import api from 'lib/api'
import { objectsEqual } from 'lib/utils'
import { router } from 'kea-router'
import { teamLogic } from 'scenes/teamLogic'
import { propertyDefinitionsLogic } from 'scenes/events/propertyDefinitionsLogic'
export function parseProperties(input) {
if (Array.isArray(input) || !input) {
@ -103,11 +103,11 @@ export const propertyFilterLogic = kea({
}
}
},
[teamLogic.actionTypes.loadCurrentTeamSuccess]: async () => {
/* Set the event properties in case the `currentTeam` request came later, or the event
[propertyDefinitionsLogic.actionTypes.loadPropertyDefinitionsSuccess]: async () => {
/* Set the event properties in case the `loadPropertyDefinitions` request came later, or the event
properties were updated. */
if (props.endpoint !== 'person' && props.endpoint !== 'sessions') {
actions.setProperties(teamLogic.values.eventProperties)
actions.setProperties(propertyDefinitionsLogic.values.transformedPropertyDefinitions)
}
},
}),
@ -136,7 +136,7 @@ export const propertyFilterLogic = kea({
}),
selectors: {
filtersLoading: [() => [teamLogic.selectors.currentTeamLoading], (currentTeamLoading) => currentTeamLoading],
filtersLoading: [() => [propertyDefinitionsLogic.selectors.loaded], (loaded) => !loaded],
},
events: ({ actions, props }) => ({
@ -145,7 +145,7 @@ export const propertyFilterLogic = kea({
actions.loadPersonProperties()
// TODO: Event properties in sessions is temporarily unsupported (context https://github.com/PostHog/posthog/issues/2735)
if (props.endpoint !== 'person' && props.endpoint !== 'sessions') {
actions.setProperties(teamLogic.values.eventProperties)
actions.setProperties(propertyDefinitionsLogic.values.transformedPropertyDefinitions)
}
},
}),

View File

@ -1,7 +1,13 @@
import React from 'react'
import { Popover } from 'antd'
import { KeyMapping } from '~/types'
export const keyMapping = {
export interface KeyMappingInterface {
event: Record<string, KeyMapping>
element: Record<string, KeyMapping>
}
export const keyMapping: KeyMappingInterface = {
event: {
$timestamp: {
label: 'Timestamp',
@ -357,12 +363,12 @@ export const keyMapping = {
$geoip_latitude: {
label: 'Latitude',
description: `Approximated latitude matched to this event's IP address.`,
examples: [-33.8591, 13.1337],
examples: ['-33.8591', '13.1337'],
},
$geoip_longitude: {
label: 'Longitude',
description: `Approximated longitude matched to this event's IP address.`,
examples: [151.2, 80.8008],
examples: ['151.2', '80.8008'],
},
$geoip_time_zone: {
label: 'Timezone',
@ -427,16 +433,17 @@ export const keyMapping = {
},
}
export function PropertyKeyInfo({ value, type = 'event' }) {
let data
if (type === 'cohort') {
return value.name
} else {
data = keyMapping[type][value]
}
interface PropertyKeyInfoInterface {
value: string
type?: 'event' | 'element'
}
if (!data) {
return value
export function PropertyKeyInfo({ value, type = 'event' }: PropertyKeyInfoInterface): JSX.Element {
let data = null
if (value in keyMapping[type]) {
data = keyMapping[type][value]
} else {
return <>{value}</>
}
return (

View File

@ -19,14 +19,14 @@ export interface SelectBoxItem {
}
export interface SelectedItem {
id?: number // Populated for actions
id?: number | string // Populated for actions (string is used for UUIDs)
name: string
key: string
value?: string
action?: ActionType
event?: string
volume?: number
usage_count?: number
volume_30_day?: number | null // Only for properties or events
query_usage_30_day?: number | null // Only for properties or events
is_numerical?: boolean // Only for properties
category?: string
cohort?: CohortType
}

View File

@ -11,6 +11,7 @@ import { featureFlagLogic } from './logic/featureFlagLogic'
import { open } from '@papercups-io/chat-widget'
import posthog from 'posthog-js'
import { WEBHOOK_SERVICES } from 'lib/constants'
import { KeyMappingInterface } from 'lib/components/PropertyKeyInfo'
const SI_PREFIXES: { value: number; symbol: string }[] = [
{ value: 1e18, symbol: 'E' },
@ -269,7 +270,7 @@ export function isValidRegex(value: string): boolean {
export function formatPropertyLabel(
item: Record<string, any>,
cohorts: Record<string, any>[],
keyMapping: Record<string, Record<string, any>>
keyMapping: KeyMappingInterface
): string {
const { value, key, operator, type } = item
return type === 'cohort'
@ -535,7 +536,7 @@ export function dateFilterToText(
return name
}
export function humanizeNumber(number: number, digits: number = 1): string {
export function humanizeNumber(number: number | null, digits: number = 1): string {
if (number === null) {
return '-'
}

View File

@ -76,7 +76,6 @@ export const preflightLogic = kea<preflightLogicType<PreflightStatus, PreflightM
}),
urlToAction: ({ actions }) => ({
'/preflight': (_: any, { mode }: { mode: PreflightMode | null }) => {
console.log(mode)
if (mode) {
actions.setPreflightMode(mode, true)
}

View File

@ -1,11 +1,17 @@
import React from 'react'
import { Select } from 'antd'
import { useValues } from 'kea'
import { teamLogic } from 'scenes/teamLogic'
import { PropertyKeyInfo } from 'lib/components/PropertyKeyInfo'
import { eventDefinitionsLogic } from 'scenes/events/eventDefinitionsLogic'
export function EventName({ value, onChange, isActionStep = false }) {
const { eventNamesGrouped } = useValues(teamLogic)
interface EventNameInterface {
value: string
onChange: (value: string) => void
isActionStep?: boolean
}
export function EventName({ value, onChange, isActionStep = false }: EventNameInterface): JSX.Element {
const { eventNamesGrouped } = useValues(eventDefinitionsLogic)
return (
<span>

View File

@ -1,49 +1,53 @@
import React, { useEffect, useState } from 'react'
import { useValues } from 'kea'
import { Alert, Input, Table, Tooltip } from 'antd'
import { Alert, Input, Skeleton, Table, Tooltip } from 'antd'
import Fuse from 'fuse.js'
import { InfoCircleOutlined, WarningOutlined } from '@ant-design/icons'
import { humanizeNumber } from 'lib/utils'
import { teamLogic } from 'scenes/teamLogic'
import { capitalizeFirstLetter, humanizeNumber } from 'lib/utils'
import { preflightLogic } from 'scenes/PreflightCheck/logic'
import { ColumnsType } from 'antd/lib/table'
import { PropertyKeyInfo } from 'lib/components/PropertyKeyInfo'
export interface EventOrPropType {
event?: string
key?: string
usage_count: number
volume: number
warnings: string[]
}
import { eventDefinitionsLogic } from './eventDefinitionsLogic'
import { EventDefinition, PropertyDefinition } from '~/types'
type EventTableType = 'event' | 'property'
const searchEvents = (sources: EventOrPropType[], search: string, key: EventTableType): EventOrPropType[] => {
type EventOrPropType = EventDefinition & PropertyDefinition
interface VolumeTableRecord {
eventOrProp: EventOrPropType
warnings: string[]
}
const search = (sources: VolumeTableRecord[], searchQuery: string): VolumeTableRecord[] => {
return new Fuse(sources, {
keys: [key],
keys: ['eventOrProp.name'],
threshold: 0.3,
})
.search(search)
.search(searchQuery)
.map((result) => result.item)
}
export function VolumeTable({ type, data }: { type: EventTableType; data: EventOrPropType[] }): JSX.Element {
export function VolumeTable({
type,
data,
}: {
type: EventTableType
data: Array<EventDefinition | PropertyDefinition>
}): JSX.Element {
const [searchTerm, setSearchTerm] = useState(false as string | false)
const [dataWithWarnings, setDataWithWarnings] = useState([] as EventOrPropType[])
const [dataWithWarnings, setDataWithWarnings] = useState([] as VolumeTableRecord[])
const key = type === 'property' ? 'key' : type // Properties are stored under `key`
const columns: ColumnsType<EventOrPropType> = [
const columns: ColumnsType<VolumeTableRecord> = [
{
title: type,
render: function RenderEvent(item: EventOrPropType): JSX.Element {
title: `${capitalizeFirstLetter(type)} name`,
render: function Render(_, record): JSX.Element {
return (
<span>
<span className="ph-no-capture">
<PropertyKeyInfo value={item[key]} />
<PropertyKeyInfo value={record.eventOrProp.name} />
</span>
{item.warnings?.map((warning) => (
{record.warnings?.map((warning) => (
<Tooltip
key={warning}
color="orange"
@ -59,7 +63,7 @@ export function VolumeTable({ type, data }: { type: EventTableType; data: EventO
</span>
)
},
sorter: (a: EventOrPropType, b: EventOrPropType) => ('' + a[key]).localeCompare(b[key] || ''),
sorter: (a, b) => ('' + a.eventOrProp.name).localeCompare(b.eventOrProp.name || ''),
filters: [
{ text: 'Has warnings', value: 'warnings' },
{ text: 'No warnings', value: 'noWarnings' },
@ -78,11 +82,13 @@ export function VolumeTable({ type, data }: { type: EventTableType; data: EventO
</Tooltip>
)
},
render: function RenderVolume(item: EventOrPropType) {
return <span className="ph-no-capture">{humanizeNumber(item.volume)}</span>
render: function RenderVolume(_, record) {
return <span className="ph-no-capture">{humanizeNumber(record.eventOrProp.volume_30_day)}</span>
},
sorter: (a: EventOrPropType, b: EventOrPropType) =>
a.volume == b.volume ? a.usage_count - b.usage_count : a.volume - b.volume,
sorter: (a, b) =>
a.eventOrProp.volume_30_day == b.eventOrProp.volume_30_day
? (a.eventOrProp.volume_30_day || -1) - (b.eventOrProp.volume_30_day || -1)
: (a.eventOrProp.volume_30_day || -1) - (b.eventOrProp.volume_30_day || -1),
},
{
title: function QueriesTitle() {
@ -96,30 +102,34 @@ export function VolumeTable({ type, data }: { type: EventTableType; data: EventO
</Tooltip>
)
},
// eslint-disable-next-line react/display-name
render: (item: EventOrPropType) => (
<span className="ph-no-capture">{humanizeNumber(item.usage_count)}</span>
),
sorter: (a: EventOrPropType, b: EventOrPropType) =>
a.usage_count == b.usage_count ? a.volume - b.volume : a.usage_count - b.usage_count,
render: function Render(_, item) {
return <span className="ph-no-capture">{humanizeNumber(item.eventOrProp.query_usage_30_day)}</span>
},
sorter: (a, b) =>
a.eventOrProp.query_usage_30_day == b.eventOrProp.query_usage_30_day
? (a.eventOrProp.query_usage_30_day || -1) - (b.eventOrProp.query_usage_30_day || -1)
: (a.eventOrProp.query_usage_30_day || -1) - (b.eventOrProp.query_usage_30_day || -1),
},
]
useEffect(() => {
setDataWithWarnings(
data.map(
(item): EventOrPropType => {
item.warnings = []
if (item[key]?.endsWith(' ')) {
item.warnings.push(`This ${type} ends with a space.`)
(eventOrProp: EventOrPropType): VolumeTableRecord => {
const record = { eventOrProp } as VolumeTableRecord
record.warnings = []
if (eventOrProp.name?.endsWith(' ')) {
record.warnings.push(`This ${type} ends with a space.`)
}
if (item[key]?.startsWith(' ')) {
item.warnings.push(`This ${type} starts with a space.`)
if (eventOrProp.name?.startsWith(' ')) {
record.warnings.push(`This ${type} starts with a space.`)
}
return item
return record
}
) || []
)
}, [])
return (
<>
<Input.Search
@ -134,9 +144,9 @@ export function VolumeTable({ type, data }: { type: EventTableType; data: EventO
<br />
<br />
<Table
dataSource={searchTerm ? searchEvents(dataWithWarnings, searchTerm, type) : dataWithWarnings}
dataSource={searchTerm ? search(dataWithWarnings, searchTerm) : dataWithWarnings}
columns={columns}
rowKey={type}
rowKey={(item) => item.eventOrProp.name}
size="small"
style={{ marginBottom: '4rem' }}
pagination={{ pageSize: 99999, hideOnSinglePage: true }}
@ -165,15 +175,15 @@ export function UsageDisabledWarning({ tab }: { tab: string }): JSX.Element {
}
export function EventsVolumeTable(): JSX.Element | null {
const { currentTeam } = useValues(teamLogic)
const { preflight } = useValues(preflightLogic)
const { eventDefinitions, loaded } = useValues(eventDefinitionsLogic)
return currentTeam?.event_names_with_usage ? (
return loaded ? (
<>
{preflight && !preflight?.is_event_property_usage_enabled ? (
<UsageDisabledWarning tab="Events Stats" />
) : (
currentTeam?.event_names_with_usage[0]?.volume === null && (
eventDefinitions[0].volume_30_day === null && (
<>
<Alert
type="warning"
@ -182,7 +192,9 @@ export function EventsVolumeTable(): JSX.Element | null {
</>
)
)}
<VolumeTable data={currentTeam?.event_names_with_usage as EventOrPropType[]} type="event" />
<VolumeTable data={eventDefinitions} type="event" />
</>
) : null
) : (
<Skeleton active paragraph={{ rows: 5 }} />
)
}

View File

@ -1,28 +1,31 @@
import React from 'react'
import { useValues } from 'kea'
import { VolumeTable, UsageDisabledWarning, EventOrPropType } from './EventsVolumeTable'
import { Alert } from 'antd'
import { VolumeTable, UsageDisabledWarning } from './EventsVolumeTable'
import { Alert, Skeleton } from 'antd'
import { preflightLogic } from 'scenes/PreflightCheck/logic'
import { teamLogic } from 'scenes/teamLogic'
import { propertyDefinitionsLogic } from './propertyDefinitionsLogic'
export function PropertiesVolumeTable(): JSX.Element | null {
const { currentTeam } = useValues(teamLogic)
const { preflight } = useValues(preflightLogic)
return currentTeam?.event_properties_with_usage ? (
const { propertyDefinitions, loaded } = useValues(propertyDefinitionsLogic)
return loaded ? (
<>
{preflight && !preflight?.is_event_property_usage_enabled ? (
<UsageDisabledWarning tab="Properties Stats" />
) : (
currentTeam?.event_properties_with_usage[0]?.volume === null && (
propertyDefinitions[0].volume_30_day === null && (
<>
<Alert
type="warning"
message="We haven't been able to get usage and volume data yet. Please check back later"
message="We haven't been able to get usage and volume data yet. Please check back later."
/>
</>
)
)}
<VolumeTable data={currentTeam?.event_properties_with_usage as EventOrPropType[]} type="property" />
<VolumeTable data={propertyDefinitions} type="property" />
</>
) : null
) : (
<Skeleton active paragraph={{ rows: 5 }} />
)
}

View File

@ -0,0 +1,89 @@
import { kea } from 'kea'
import api from 'lib/api'
import { posthogEvents } from 'lib/utils'
import { EventDefinition, SelectOption } from '~/types'
import { eventDefinitionsLogicType } from './eventDefinitionsLogicType'
interface EventDefinitionStorage {
count: number
next: null | string
results: EventDefinition[]
}
interface EventsGroupedInterface {
label: string
options: SelectOption[]
}
export const eventDefinitionsLogic = kea<
eventDefinitionsLogicType<EventDefinitionStorage, EventDefinition, EventsGroupedInterface, SelectOption>
>({
loaders: ({ values }) => ({
eventStorage: [
{ results: [], next: null, count: 0 } as EventDefinitionStorage,
{
loadEventDefinitions: async (initial?: boolean) => {
const url = initial
? 'api/projects/@current/event_definitions/?limit=500'
: values.eventStorage.next
if (!url) {
throw new Error('Incorrect call to eventDefinitionsLogic.loadEventDefinitions')
}
const eventStorage = await api.get(url)
return {
count: eventStorage.count,
results: [...values.eventStorage.results, ...eventStorage.results],
next: eventStorage.next,
}
},
},
],
}),
listeners: ({ actions }) => ({
loadEventDefinitionsSuccess: ({ eventStorage }) => {
if (eventStorage.next) {
actions.loadEventDefinitions()
}
},
}),
events: ({ actions }) => ({
afterMount: () => {
actions.loadEventDefinitions(true)
},
}),
selectors: {
loaded: [
// Whether *all* the event definitions are fully loaded
(s) => [s.eventStorage, s.eventStorageLoading],
(eventStorage, eventStorageLoading): boolean => !eventStorageLoading && !eventStorage.next,
],
eventDefinitions: [(s) => [s.eventStorage], (eventStorage): EventDefinition[] => eventStorage.results],
eventNames: [
(s) => [s.eventDefinitions],
(eventDefinitions): string[] => eventDefinitions.map((definition) => definition.name),
],
customEventNames: [
(s) => [s.eventNames],
(eventNames): string[] => eventNames.filter((event) => !event.startsWith('!')),
],
eventNamesGrouped: [
(s) => [s.eventNames],
(eventNames): EventsGroupedInterface[] => {
const data: EventsGroupedInterface[] = [
{ label: 'Custom events', options: [] },
{ label: 'PostHog events', options: [] },
]
eventNames.forEach((name: string) => {
const format = { label: name, value: name }
if (posthogEvents.includes(name)) {
return data[1].options.push(format)
}
data[0].options.push(format)
})
return data
},
],
},
})

View File

@ -0,0 +1,82 @@
import { kea } from 'kea'
import api from 'lib/api'
import { PropertyDefinition, SelectOption } from '~/types'
import { propertyDefinitionsLogicType } from './propertyDefinitionsLogicType'
interface PropertySelectOption extends SelectOption {
is_numerical?: boolean
}
interface PropertyDefinitionStorage {
count: number
next: null | string
results: PropertyDefinition[]
}
export const propertyDefinitionsLogic = kea<
propertyDefinitionsLogicType<PropertyDefinitionStorage, PropertyDefinition, PropertySelectOption>
>({
loaders: ({ values }) => ({
propertyStorage: [
{ results: [], next: null, count: 0 } as PropertyDefinitionStorage,
{
loadPropertyDefinitions: async (initial?: boolean) => {
const url = initial
? 'api/projects/@current/property_definitions/?limit=500'
: values.propertyStorage.next
if (!url) {
throw new Error('Incorrect call to propertyDefinitionsLogic.loadPropertyDefinitions')
}
const propertyStorage = await api.get(url)
return {
count: propertyStorage.count,
results: [...values.propertyStorage.results, ...propertyStorage.results],
next: propertyStorage.next,
}
},
},
],
}),
listeners: ({ actions }) => ({
loadPropertyDefinitionsSuccess: ({ propertyStorage }) => {
if (propertyStorage.next) {
actions.loadPropertyDefinitions()
}
},
}),
events: ({ actions }) => ({
afterMount: () => {
actions.loadPropertyDefinitions(true)
},
}),
selectors: {
loaded: [
// Whether *all* the property definitions are fully loaded
(s) => [s.propertyStorage, s.propertyStorageLoading],
(propertyStorage, propertyStorageLoading): boolean => !propertyStorageLoading && !propertyStorage.next,
],
propertyDefinitions: [
(s) => [s.propertyStorage],
(propertyStorage): PropertyDefinition[] => propertyStorage.results || [],
],
transformedPropertyDefinitions: [
// Transformed propertyDefinitions to use in `Select` components
(s) => [s.propertyDefinitions],
(propertyDefinitions): PropertySelectOption[] =>
propertyDefinitions.map((property) => ({
value: property.name,
label: property.name,
is_numerical: property.is_numerical,
})),
],
propertyNames: [
(s) => [s.propertyDefinitions],
(propertyDefinitions): string[] => propertyDefinitions.map((definition) => definition.name),
],
numericalPropertyNames: [
(s) => [s.transformedPropertyDefinitions],
(transformedPropertyDefinitions): PropertySelectOption[] =>
transformedPropertyDefinitions.filter((definition) => definition.is_numerical),
],
},
})

View File

@ -1,7 +1,6 @@
import React, { RefObject } from 'react'
import { BuiltLogic, useActions, useValues } from 'kea'
import { ActionType } from '~/types'
import { EventUsageType } from '~/types'
import { ActionType, EventDefinition } from '~/types'
import { EntityTypes } from '../../trends/trendsLogic'
import { actionsModel } from '~/models/actionsModel'
import { FireOutlined, InfoCircleOutlined, AimOutlined, ContainerOutlined } from '@ant-design/icons'
@ -9,8 +8,8 @@ import { Tooltip } from 'antd'
import { ActionSelectInfo } from '../ActionSelectInfo'
import { SelectBox, SelectedItem } from '../../../lib/components/SelectBox'
import { Link } from 'lib/components/Link'
import { teamLogic } from 'scenes/teamLogic'
import { PropertyKeyInfo } from 'lib/components/PropertyKeyInfo'
import { eventDefinitionsLogic } from 'scenes/events/eventDefinitionsLogic'
interface FilterType {
filter: {
@ -26,10 +25,10 @@ interface FilterType {
index: number
}
const getSuggestions = (events: EventUsageType[]): EventUsageType[] => {
const getSuggestions = (events: EventDefinition[]): EventDefinition[] => {
return events
.filter((event) => event.usage_count > 0)
.sort((a, b) => b.usage_count - a.usage_count)
.filter((event) => (event.query_usage_30_day || -1) > 0)
.sort((a, b) => (b.query_usage_30_day || -1) - (a.query_usage_30_day || -1))
.slice(0, 3)
}
@ -52,7 +51,7 @@ export function ActionFilterDropdown({
const { updateFilter, setEntityFilterVisibility } = useActions(logic)
const { actions } = useValues(actionsModel)
const { currentTeam } = useValues(teamLogic)
const { eventDefinitions } = useValues(eventDefinitionsLogic)
const handleDismiss = (event: MouseEvent): void => {
if (openButtonRef?.current?.contains(event.target as Node)) {
@ -68,7 +67,7 @@ export function ActionFilterDropdown({
setEntityFilterVisibility(selectedFilter.index, true)
}
}
const suggestions = getSuggestions(currentTeam?.event_names_with_usage || [])
const suggestions = getSuggestions(eventDefinitions || [])
return (
<SelectBox
@ -85,10 +84,9 @@ export function ActionFilterDropdown({
</Tooltip>
</>
),
dataSource: suggestions.map((event) => ({
key: 'suggestions' + event.event,
name: event.event,
...event,
dataSource: suggestions.map((definition) => ({
...definition,
key: 'suggestions' + definition.id,
})),
renderInfo: function renderSuggestions({ item }) {
return (
@ -96,22 +94,22 @@ export function ActionFilterDropdown({
<FireOutlined /> Suggestions
<br />
<h3>{item.name}</h3>
{(item?.volume ?? 0 > 0) && (
{(item?.volume_30_day ?? 0 > 0) && (
<>
Seen <strong>{item.volume}</strong> times.{' '}
Seen <strong>{item.volume_30_day}</strong> times.{' '}
</>
)}
{(item?.usage_count ?? 0 > 0) && (
{(item?.query_usage_30_day ?? 0 > 0) && (
<>
Used in <strong>{item.usage_count}</strong> queries.
Used in <strong>{item.query_usage_30_day}</strong> queries.
</>
)}
</>
)
},
type: EntityTypes.EVENTS,
getValue: (item: SelectedItem) => item.event || '',
getLabel: (item: SelectedItem) => item.event || '',
getValue: (item: SelectedItem) => item.name || '',
getLabel: (item: SelectedItem) => item.name || '',
},
{
name: (
@ -138,10 +136,9 @@ export function ActionFilterDropdown({
</>
),
dataSource:
currentTeam?.event_names_with_usage.map((event) => ({
key: EntityTypes.EVENTS + event.event,
name: event.event,
...event,
eventDefinitions.map((definition) => ({
...definition,
key: EntityTypes.EVENTS + definition.id,
})) || [],
renderInfo: function events({ item }) {
return (
@ -149,22 +146,22 @@ export function ActionFilterDropdown({
<ContainerOutlined /> Events
<br />
<h3>{item.name}</h3>
{(item?.volume ?? 0 > 0) && (
{(item?.volume_30_day ?? 0 > 0) && (
<>
Seen <strong>{item.volume}</strong> times.{' '}
Seen <strong>{item.volume_30_day}</strong> times.{' '}
</>
)}
{(item?.usage_count ?? 0 > 0) && (
{(item?.query_usage_30_day ?? 0 > 0) && (
<>
Used in <strong>{item.usage_count}</strong> queries.
Used in <strong>{item.query_usage_30_day}</strong> queries.
</>
)}
</>
)
},
type: EntityTypes.EVENTS,
getValue: (item: SelectedItem) => item.event || '',
getLabel: (item: SelectedItem) => item.event || '',
getValue: (item: SelectedItem) => item.name || '',
getLabel: (item: SelectedItem) => item.name || '',
},
]}
/>

View File

@ -8,10 +8,10 @@ import { PROPERTY_MATH_TYPE, EVENT_MATH_TYPE, MATHS } from 'lib/constants'
import { DownOutlined, DeleteOutlined } from '@ant-design/icons'
import { SelectGradientOverflow } from 'lib/components/SelectGradientOverflow'
import './ActionFilterRow.scss'
import { teamLogic } from 'scenes/teamLogic'
import { PropertyKeyInfo } from 'lib/components/PropertyKeyInfo'
import { preflightLogic } from 'scenes/PreflightCheck/logic'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { propertyDefinitionsLogic } from 'scenes/events/propertyDefinitionsLogic'
const EVENT_MATH_ENTRIES = Object.entries(MATHS).filter(([, item]) => item.type == EVENT_MATH_TYPE)
const PROPERTY_MATH_ENTRIES = Object.entries(MATHS).filter(([, item]) => item.type == PROPERTY_MATH_TYPE)
@ -47,7 +47,7 @@ export function ActionFilterRow({
updateFilterProperty,
setEntityFilterVisibility,
} = useActions(logic)
const { eventProperties, eventPropertiesNumerical } = useValues(teamLogic)
const { propertyNames, numericalPropertyNames } = useValues(propertyDefinitionsLogic)
const visible = entityFilterVisible[filter.order]
@ -58,20 +58,20 @@ export function ActionFilterRow({
const onClose = () => {
removeLocalFilter({ value: filter.id, type: filter.type, index })
}
const onMathSelect = (_, math) => {
const onMathSelect = (_, selectedMath) => {
updateFilterMath({
math,
math_property: MATHS[math]?.onProperty ? mathProperty : undefined,
onProperty: MATHS[math]?.onProperty,
math: selectedMath,
math_property: MATHS[selectedMath]?.onProperty ? mathProperty : undefined,
onProperty: MATHS[selectedMath]?.onProperty,
value: filter.id,
type: filter.type,
index: index,
})
}
const onMathPropertySelect = (_, mathProperty) => {
const onMathPropertySelect = (_, selectedMathProperty) => {
updateFilterMath({
math: filter.math,
math_property: mathProperty,
math_property: selectedMathProperty,
value: filter.id,
type: filter.type,
index: index,
@ -139,9 +139,7 @@ export function ActionFilterRow({
math={math}
index={index}
onMathSelect={onMathSelect}
areEventPropertiesNumericalAvailable={
eventPropertiesNumerical && eventPropertiesNumerical.length > 0
}
areEventPropertiesNumericalAvailable={!!numericalPropertyNames.length}
style={{ maxWidth: '100%', width: 'initial' }}
/>
)}
@ -168,7 +166,7 @@ export function ActionFilterRow({
mathProperty={mathProperty}
index={index}
onMathPropertySelect={onMathPropertySelect}
properties={eventPropertiesNumerical}
properties={numericalPropertyNames}
/>
)}
{(!hidePropertySelector || (filter.properties && filter.properties.length > 0)) && (
@ -188,7 +186,7 @@ export function ActionFilterRow({
<div className="ml">
<PropertyFilters
pageKey={`${index}-${value}-filter`}
properties={eventProperties}
properties={propertyNames}
propertyFilters={filter.properties}
onChange={(properties) => updateFilterProperty({ properties, index })}
style={{ marginBottom: 0 }}

View File

@ -1,7 +1,7 @@
import { kea } from 'kea'
import { actionsModel } from '~/models/actionsModel'
import { EntityTypes } from '../../trends/trendsLogic'
import { teamLogic } from 'scenes/teamLogic'
import { eventDefinitionsLogic } from 'scenes/events/eventDefinitionsLogic'
export function toLocalFilters(filters) {
return [
@ -33,7 +33,7 @@ export function toFilters(localFilters) {
export const entityFilterLogic = kea({
key: (props) => props.typeKey,
connect: {
values: [teamLogic, ['eventNames'], actionsModel, ['actions']],
values: [actionsModel, ['actions']],
},
actions: () => ({
selectFilter: (filter) => ({ filter }),
@ -76,7 +76,7 @@ export const entityFilterLogic = kea({
selectors: ({ selectors }) => ({
entities: [
() => [selectors.eventNames, selectors.actions],
() => [eventDefinitionsLogic.selectors.eventNames, selectors.actions],
(events, actions) => {
return {
[EntityTypes.ACTIONS]: actions,

View File

@ -1,17 +1,17 @@
import React, { useState } from 'react'
import { Tooltip, Select, Tabs, Popover, Button } from 'antd'
import { useValues } from 'kea'
import { teamLogic } from 'scenes/teamLogic'
import { propertyFilterLogic } from 'lib/components/PropertyFilters/propertyFilterLogic'
import { cohortsModel } from '../../models/cohortsModel'
import { PropertyKeyInfo } from 'lib/components/PropertyKeyInfo'
import { SelectGradientOverflow } from 'lib/components/SelectGradientOverflow'
import { ShownAsValue } from 'lib/constants'
import { propertyDefinitionsLogic } from 'scenes/events/propertyDefinitionsLogic'
const { TabPane } = Tabs
function PropertyFilter({ breakdown, onChange }) {
const { eventProperties } = useValues(teamLogic)
const { transformedPropertyDefinitions } = useValues(propertyDefinitionsLogic)
const { personProperties } = useValues(propertyFilterLogic({ pageKey: 'breakdown' }))
return (
<SelectGradientOverflow
@ -24,9 +24,9 @@ function PropertyFilter({ breakdown, onChange }) {
filterOption={(input, option) => option.value?.toLowerCase().indexOf(input.toLowerCase()) >= 0}
data-attr="prop-breakdown-select"
>
{eventProperties.length > 0 && (
{transformedPropertyDefinitions.length > 0 && (
<Select.OptGroup key="Event properties" label="Event properties">
{Object.entries(eventProperties).map(([key, item], index) => (
{Object.entries(transformedPropertyDefinitions).map(([key, item], index) => (
<Select.Option
key={'event_' + key}
value={'event_' + item.value}
@ -45,7 +45,7 @@ function PropertyFilter({ breakdown, onChange }) {
key={'person_' + key}
value={'person_' + item.value}
type="person"
data-attr={'prop-filter-person-' + (eventProperties.length + index)}
data-attr={'prop-filter-person-' + (transformedPropertyDefinitions.length + index)}
>
<PropertyKeyInfo value={item.value} />
</Select.Option>

View File

@ -12,10 +12,10 @@ import {
import { Select } from 'antd'
import { PropertyValue } from 'lib/components/PropertyFilters'
import { TestAccountFilter } from '../TestAccountFilter'
import { teamLogic } from 'scenes/teamLogic'
import { eventDefinitionsLogic } from 'scenes/events/eventDefinitionsLogic'
export function PathTab(): JSX.Element {
const { customEventNames } = useValues(teamLogic)
const { customEventNames } = useValues(eventDefinitionsLogic)
const { filter } = useValues(pathsLogic({ dashboardItemId: null }))
const { setFilter } = useActions(pathsLogic({ dashboardItemId: null }))

View File

@ -5,7 +5,7 @@ import { EventTypePropertyFilter } from '~/types'
import { keyMapping } from 'lib/components/PropertyKeyInfo'
import { OperatorValueSelect } from 'lib/components/PropertyFilters/OperatorValueSelect'
import { sessionsFiltersLogic } from 'scenes/sessions/filters/sessionsFiltersLogic'
import { teamLogic } from 'scenes/teamLogic'
import { propertyDefinitionsLogic } from 'scenes/events/propertyDefinitionsLogic'
interface Props {
filter: EventTypePropertyFilter
@ -13,7 +13,7 @@ interface Props {
}
export function EventPropertyFilter({ filter, selector }: Props): JSX.Element {
const { eventProperties } = useValues(teamLogic)
const { transformedPropertyDefinitions } = useValues(propertyDefinitionsLogic)
const { updateFilter } = useActions(sessionsFiltersLogic)
const property = filter.properties && filter.properties.length > 0 ? filter.properties[0] : null
@ -29,7 +29,7 @@ export function EventPropertyFilter({ filter, selector }: Props): JSX.Element {
{
type: 'event',
label: 'Event properties',
options: eventProperties,
options: transformedPropertyDefinitions,
},
]}
onChange={(_, key) => {

View File

@ -9,14 +9,14 @@ import { ActionInfo } from 'scenes/insights/ActionFilter/ActionFilterDropdown'
import { FilterSelector, sessionsFiltersLogic } from 'scenes/sessions/filters/sessionsFiltersLogic'
import { Link } from 'lib/components/Link'
import { cohortsModel } from '~/models/cohortsModel'
import { teamLogic } from 'scenes/teamLogic'
import { eventDefinitionsLogic } from 'scenes/events/eventDefinitionsLogic'
export function SessionsFilterBox({ selector }: { selector: FilterSelector }): JSX.Element | null {
const { openFilter, personProperties } = useValues(sessionsFiltersLogic)
const { closeFilterSelect, dropdownSelected } = useActions(sessionsFiltersLogic)
const { currentTeam } = useValues(teamLogic)
const { eventDefinitions } = useValues(eventDefinitionsLogic)
const { actions } = useValues(actionsModel)
const { cohorts } = useValues(cohortsModel)
@ -40,8 +40,8 @@ export function SessionsFilterBox({ selector }: { selector: FilterSelector }): J
})),
renderInfo: ActionInfo,
type: 'action_type',
getValue: (item: SelectedItem) => item.action?.id,
getLabel: (item: SelectedItem) => item.action?.name,
getValue: (item: SelectedItem) => item.action?.id || '',
getLabel: (item: SelectedItem) => item.action?.name || '',
},
{
name: (
@ -50,10 +50,9 @@ export function SessionsFilterBox({ selector }: { selector: FilterSelector }): J
</>
),
dataSource:
(currentTeam?.event_names_with_usage ?? []).map((event) => ({
key: EntityTypes.EVENTS + event.event,
name: event.event,
...event,
eventDefinitions.map((definition) => ({
key: EntityTypes.EVENTS + definition.name,
...definition,
})) || [],
renderInfo: function events({ item }) {
return (
@ -61,22 +60,22 @@ export function SessionsFilterBox({ selector }: { selector: FilterSelector }): J
<ContainerOutlined /> Events
<br />
<h3>{item.name}</h3>
{item?.volume && (
{item?.volume_30_day && (
<>
Seen <strong>{item.volume}</strong> times.{' '}
Seen <strong>{item.volume_30_day}</strong> times.{' '}
</>
)}
{item?.usage_count && (
{item?.query_usage_30_day && (
<>
Used in <strong>{item.usage_count}</strong> queries.
Used in <strong>{item.query_usage_30_day}</strong> queries.
</>
)}
</>
)
},
type: 'event_type',
getValue: (item: SelectedItem) => item.event,
getLabel: (item: SelectedItem) => item.event,
getValue: (item: SelectedItem) => item.name,
getLabel: (item: SelectedItem) => item.name,
},
{
name: (
@ -86,7 +85,7 @@ export function SessionsFilterBox({ selector }: { selector: FilterSelector }): J
),
dataSource: cohorts.map((cohort: CohortType) => ({
key: 'cohorts' + cohort.id,
name: cohort.name,
name: cohort.name || '',
id: cohort.id,
cohort,
})),
@ -113,7 +112,7 @@ export function SessionsFilterBox({ selector }: { selector: FilterSelector }): J
)
},
type: 'cohort',
getValue: (item: SelectedItem) => item.id,
getValue: (item: SelectedItem) => item.id || '',
getLabel: (item: SelectedItem) => item.name,
},
]
@ -136,9 +135,9 @@ export function SessionsFilterBox({ selector }: { selector: FilterSelector }): J
<UsergroupAddOutlined /> User property
<br />
<h3>{item.name}</h3>
{item?.usage_count && (
{item?.query_usage_30_day && (
<>
<strong>{item.usage_count}</strong> users have this property.
<strong>{item.query_usage_30_day}</strong> users have this property.
</>
)}
</>
@ -170,7 +169,7 @@ export function SessionsFilterBox({ selector }: { selector: FilterSelector }): J
)
},
type: 'recording',
getValue: (item: SelectedItem) => item.value,
getValue: (item: SelectedItem) => item.value || '',
getLabel: (item: SelectedItem) => item.name,
})

View File

@ -5,14 +5,9 @@ import { TeamType } from '~/types'
import { userLogic } from './userLogic'
import { toast } from 'react-toastify'
import React from 'react'
import { posthogEvents, identifierToHuman, resolveWebhookService } from 'lib/utils'
import { identifierToHuman, resolveWebhookService } from 'lib/utils'
export interface EventProperty {
value: string
label: string
}
export const teamLogic = kea<teamLogicType<TeamType, EventProperty>>({
export const teamLogic = kea<teamLogicType<TeamType>>({
actions: {
deleteTeam: (team: TeamType) => ({ team }),
deleteTeamSuccess: true,
@ -97,52 +92,6 @@ export const teamLogic = kea<teamLogicType<TeamType, EventProperty>>({
window.location.href = '/ingestion'
},
}),
selectors: {
eventProperties: [
(s) => [s.currentTeam],
(team): EventProperty[] =>
team
? team.event_properties.map(
(property: string) => ({ value: property, label: property } as EventProperty)
)
: [],
],
eventPropertiesNumerical: [
(s) => [s.currentTeam],
(team): EventProperty[] =>
team
? team.event_properties_numerical.map(
(property: string) => ({ value: property, label: property } as EventProperty)
)
: [],
],
eventNames: [(s) => [s.currentTeam], (team): string[] => team?.event_names ?? []],
customEventNames: [
(s) => [s.eventNames],
(eventNames): string[] => {
return eventNames.filter((event) => !event.startsWith('!'))
},
],
eventNamesGrouped: [
(s) => [s.currentTeam],
(team) => {
const data = [
{ label: 'Custom events', options: [] as EventProperty[] },
{ label: 'PostHog events', options: [] as EventProperty[] },
]
if (team) {
team.event_names.forEach((name: string) => {
const format = { label: name, value: name } as EventProperty
if (posthogEvents.includes(name)) {
return data[1].options.push(format)
}
data[0].options.push(format)
})
}
return data
},
],
},
events: ({ actions }) => ({
afterMount: [actions.loadCurrentTeam],
}),

View File

@ -21,7 +21,8 @@ import { ActionType, EntityType, FilterType, PersonType, PropertyFilter, TrendRe
import { cohortLogic } from 'scenes/persons/cohortLogic'
import { trendsLogicType } from './trendsLogicType'
import { dashboardItemsModel } from '~/models/dashboardItemsModel'
import { teamLogic } from 'scenes/teamLogic'
import { eventDefinitionsLogic } from 'scenes/events/eventDefinitionsLogic'
import { propertyDefinitionsLogic } from 'scenes/events/propertyDefinitionsLogic'
interface TrendResponse {
result: TrendResult[]
@ -167,7 +168,7 @@ export const trendsLogic = kea<
},
connect: {
values: [teamLogic, ['eventNames'], actionsModel, ['actions']],
values: [actionsModel, ['actions']],
},
loaders: ({ values, props }) => ({
@ -248,12 +249,11 @@ export const trendsLogic = kea<
FilterType
>,
{
setFilters: (state, { filters, mergeFilters }) => {
return cleanFilters({
setFilters: (state, { filters, mergeFilters }) =>
cleanFilters({
...(mergeFilters ? state : {}),
...filters,
})
},
}),
},
],
people: [
@ -317,8 +317,8 @@ export const trendsLogic = kea<
selectors: () => ({
filtersLoading: [
() => [teamLogic.selectors.currentTeamLoading],
(currentTeamLoading: any) => currentTeamLoading,
() => [eventDefinitionsLogic.selectors.loaded, propertyDefinitionsLogic.selectors.loaded],
(eventsLoaded, propertiesLoaded) => !eventsLoaded || !propertiesLoaded,
],
results: [(selectors) => [selectors._results], (response) => response.result],
resultsLoading: [(selectors) => [selectors._resultsLoading], (_resultsLoading) => _resultsLoading],
@ -511,8 +511,8 @@ export const trendsLogic = kea<
})
actions.setBreakdownValuesLoading(false)
},
[teamLogic.actionTypes.loadCurrentTeamSuccess]: async () => {
actions.setFilters(getDefaultFilters(values.filters, values.eventNames), true)
[eventDefinitionsLogic.actionTypes.loadEventDefinitionsSuccess]: async () => {
actions.setFilters(getDefaultFilters(values.filters, eventDefinitionsLogic.values.eventNames), true)
},
}),
@ -569,7 +569,10 @@ export const trendsLogic = kea<
cleanSearchParams['compare'] = false
}
Object.assign(cleanSearchParams, getDefaultFilters(cleanSearchParams, values.eventNames))
Object.assign(
cleanSearchParams,
getDefaultFilters(cleanSearchParams, eventDefinitionsLogic.values.eventNames)
)
if (!objectsEqual(cleanSearchParams, values.filters)) {
actions.setFilters(cleanSearchParams, false)

View File

@ -120,11 +120,6 @@ export interface TeamBasicType {
export interface TeamType extends TeamBasicType {
anonymize_ips: boolean
app_urls: string[]
event_names: string[]
event_properties: string[]
event_properties_numerical: string[]
event_names_with_usage: EventUsageType[]
event_properties_with_usage: PropertyUsageType[]
slack_incoming_webhook: string
session_recording_opt_in: boolean
session_recording_retention_period_days: number | null
@ -675,3 +670,30 @@ export interface LicenseType {
max_users: string | null
created_at: string
}
export interface EventDefinition {
id: string
name: string
volume_30_day: number | null
query_usage_30_day: number | null
}
export interface PropertyDefinition {
id: string
name: string
volume_30_day: number | null
query_usage_30_day: number | null
is_numerical?: boolean // Marked as optional to allow merge of EventDefinition & PropertyDefinition
}
export interface SelectOption {
value: string
label: string
}
export interface KeyMapping {
label: string
description: string | JSX.Element
examples?: string[]
hide?: boolean
}

View File

@ -1,4 +1,4 @@
from typing import Any, Dict, List, Optional, Type, cast
from typing import Any, Dict, Optional, Type, cast
from django.db import transaction
from django.shortcuts import get_object_or_404
@ -10,12 +10,7 @@ from posthog.mixins import AnalyticsDestroyModelMixin
from posthog.models import Organization, Team
from posthog.models.user import User
from posthog.models.utils import generate_random_token
from posthog.permissions import (
CREATE_METHODS,
OrganizationAdminWritePermissions,
OrganizationMemberPermissions,
ProjectMembershipNecessaryPermissions,
)
from posthog.permissions import CREATE_METHODS, OrganizationAdminWritePermissions, ProjectMembershipNecessaryPermissions
class PremiumMultiprojectPermissions(permissions.BasePermission):
@ -37,10 +32,6 @@ class PremiumMultiprojectPermissions(permissions.BasePermission):
class TeamSerializer(serializers.ModelSerializer):
event_names_with_usage = serializers.SerializerMethodField()
event_properties_with_usage = serializers.SerializerMethodField()
class Meta:
model = Team
fields = (
@ -62,11 +53,6 @@ class TeamSerializer(serializers.ModelSerializer):
"data_attributes",
"session_recording_opt_in",
"session_recording_retention_period_days",
"event_names",
"event_properties",
"event_properties_numerical",
"event_names_with_usage",
"event_properties_with_usage",
)
read_only_fields = (
"id",
@ -77,9 +63,6 @@ class TeamSerializer(serializers.ModelSerializer):
"created_at",
"updated_at",
"ingested_event",
"event_names",
"event_properties",
"event_properties_numerical",
)
def create(self, validated_data: Dict[str, Any], **kwargs) -> Team:
@ -92,12 +75,6 @@ class TeamSerializer(serializers.ModelSerializer):
request.user.save()
return team
def get_event_names_with_usage(self, instance: Team) -> List:
return instance.get_latest_event_names_with_usage()
def get_event_properties_with_usage(self, instance: Team) -> List:
return instance.get_latest_event_properties_with_usage()
class TeamViewSet(AnalyticsDestroyModelMixin, viewsets.ModelViewSet):
serializer_class = TeamSerializer

View File

@ -17,6 +17,8 @@ class TestTeamAPI(APIBaseTest):
self.assertEqual(response_data["results"][0]["name"], self.team.name)
self.assertNotIn("test_account_filters", response_data["results"][0])
self.assertNotIn("data_attributes", response_data["results"][0])
# TODO: #4070 These assertions will no longer make sense when we fully remove these attributes from the model
self.assertNotIn("event_names", response_data["results"][0])
self.assertNotIn("event_properties", response_data["results"][0])
self.assertNotIn("event_properties_numerical", response_data["results"][0])
@ -31,11 +33,13 @@ class TestTeamAPI(APIBaseTest):
self.assertEqual(response_data["timezone"], "UTC")
self.assertEqual(response_data["is_demo"], False)
self.assertEqual(response_data["slack_incoming_webhook"], self.team.slack_incoming_webhook)
self.assertIn("event_names", response_data)
self.assertIn("event_properties", response_data)
self.assertIn("event_properties_numerical", response_data)
self.assertIn("event_names_with_usage", response_data)
self.assertIn("event_properties_with_usage", response_data)
# The properties below are no longer included as part of the request
# TODO: #4070 These assertions will no longer make sense when we fully remove these attributes from the model
self.assertNotIn("event_names", response_data)
self.assertNotIn("event_properties", response_data)
self.assertNotIn("event_properties_numerical", response_data)
self.assertNotIn("event_names_with_usage", response_data)
self.assertNotIn("event_properties_with_usage", response_data)
def test_cant_retrieve_project_from_another_org(self):
org = Organization.objects.create(name="New Org")

View File

@ -32,8 +32,8 @@ def sync_event_and_properties_definitions(team_uuid: str) -> None:
# Add or update any existing events
for event in team.event_names:
instance, _ = EventDefinition.objects.get_or_create(team=team, name=event)
instance.volume_30_day = transformed_event_usage.get(event, {}).get("volume") or 0
instance.query_usage_30_day = transformed_event_usage.get(event, {}).get("usage_count") or 0
instance.volume_30_day = transformed_event_usage.get(event, {}).get("volume")
instance.query_usage_30_day = transformed_event_usage.get(event, {}).get("usage_count")
instance.save()
# Remove any deleted events
@ -42,8 +42,8 @@ def sync_event_and_properties_definitions(team_uuid: str) -> None:
# Add or update any existing properties
for property in team.event_properties:
property_instance, _ = PropertyDefinition.objects.get_or_create(team=team, name=property)
property_instance.volume_30_day = transformed_property_usage.get(property, {}).get("volume") or 0
property_instance.query_usage_30_day = transformed_property_usage.get(property, {}).get("usage_count") or 0
property_instance.volume_30_day = transformed_property_usage.get(property, {}).get("volume")
property_instance.query_usage_30_day = transformed_property_usage.get(property, {}).get("usage_count")
property_instance.is_numerical = property in team.event_properties_numerical
property_instance.save()

View File

@ -56,8 +56,8 @@ class TestTeam(BaseTest):
for obj in EventDefinition.objects.filter(team=team):
self.assertIn(obj.name, expected_events)
self.assertEqual(obj.volume_30_day, 0)
self.assertEqual(obj.query_usage_30_day, 0)
self.assertEqual(obj.volume_30_day, None)
self.assertEqual(obj.query_usage_30_day, None)
# Test adding and removing one event
team.event_names.pop(0)
@ -111,8 +111,8 @@ class TestTeam(BaseTest):
for obj in PropertyDefinition.objects.filter(team=team):
self.assertIn(obj.name, expected_properties)
self.assertEqual(obj.volume_30_day, 0)
self.assertEqual(obj.query_usage_30_day, 0)
self.assertEqual(obj.volume_30_day, None)
self.assertEqual(obj.query_usage_30_day, None)
self.assertEqual(obj.is_numerical, obj.name in numerical_properties)
# Test adding and removing one event