Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Always show event stats and add warnings #3908

Merged
merged 8 commits into from
Apr 9, 2021
Merged
Show file tree
Hide file tree
Changes from 3 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
30 changes: 2 additions & 28 deletions frontend/src/scenes/events/Events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import React from 'react'
import { kea, useActions, useValues } from 'kea'

import { PageHeader } from 'lib/components/PageHeader'
import { Alert, Tabs } from 'antd'
import { Tabs } from 'antd'
import { ActionsTable } from 'scenes/actions/ActionsTable'
import { EventsTable } from './EventsTable'
import { EventsVolumeTable } from './EventsVolumeTable'
import { PropertiesVolumeTable } from './PropertiesVolumeTable'
import { eventsLogicType } from './EventsType'
import { userLogic } from 'scenes/userLogic'

const eventsLogic = kea<eventsLogicType>({
actions: {
Expand All @@ -35,29 +34,9 @@ const eventsLogic = kea<eventsLogicType>({
}),
})

function UsageDisabledWarning({ tab }: { tab: string }): JSX.Element {
return (
<Alert
type="warning"
message={
<>
{tab} is not enabled on your instance. If you want to enable event usage please set the follow
environment variable: <pre style={{ display: 'inline' }}>ASYNC_EVENT_PROPERTY_USAGE=1</pre>
<br />
<br />
Please note, enabling this environment variable can increase load considerably if you have a large
volume of events.
</>
}
/>
)
}

export function ManageEvents(): JSX.Element {
const { tab } = useValues(eventsLogic)
const { setTab } = useActions(eventsLogic)

const { user } = useValues(userLogic)
return (
<div className="manage-events" data-attr="manage-events-table">
<PageHeader title="Events" />
Expand All @@ -75,18 +54,13 @@ export function ManageEvents(): JSX.Element {
<br />
<br />
<EventsVolumeTable />
{!user?.is_event_property_usage_enabled && <UsageDisabledWarning tab="Events Stats" />}
</Tabs.TabPane>
<Tabs.TabPane tab="Properties Stats" key="properties">
See all property keys that have ever been sent to this team, including the volume and how often
queries where made using this property key.
<br />
<br />
{user?.is_event_property_usage_enabled ? (
<PropertiesVolumeTable />
) : (
<UsageDisabledWarning tab="Properties Stats" />
)}
<PropertiesVolumeTable />
</Tabs.TabPane>
</Tabs>
</div>
Expand Down
153 changes: 125 additions & 28 deletions frontend/src/scenes/events/EventsVolumeTable.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,57 @@
import React from 'react'
import React, { useEffect, useState } from 'react'
import { useValues } from 'kea'
import { Alert, Table, Tooltip } from 'antd'
import { Alert, Input, Table, Tooltip } from 'antd'
import { userLogic } from 'scenes/userLogic'
import { InfoCircleOutlined } from '@ant-design/icons'
import { EventUsageType } from '~/types'
import Fuse from 'fuse.js'
import { InfoCircleOutlined, WarningOutlined } from '@ant-design/icons'
import { humanizeNumber } from 'lib/utils'

export function EventsVolumeTable(): JSX.Element {
const searchEvents = (sources: EventOrPropType[], search: string, key: 'event' | 'key'): EventOrPropType[] => {
return new Fuse(sources, {
keys: [key],
threshold: 0.3,
})
.search(search)
.map((result) => result.item)
}

export interface EventOrPropType {
event?: string
key?: string
usage_count: number
volume: number
warnings: string[]
}

export function VolumeTable({ type, data }: { type: 'event' | 'key'; data: EventOrPropType[] }): JSX.Element {
const [searchTerm, setSearchTerm] = useState(false as string | false)
const [dataWithWarnings, setDataWithWarnings] = useState([] as EventOrPropType[])
const num_warnings = dataWithWarnings.reduce((prev, item) => {
return prev + (item.warnings?.length || 0)
}, 0)
const columns = [
{
title: 'Event',
dataIndex: 'event',

sorter: (a: EventUsageType, b: EventUsageType) => ('' + a.event).localeCompare(b.event),
title: type,
render: function RenderEvent(item: EventOrPropType): JSX.Element {
return <span className="ph-no-capture">{item[type]}</span>
},
sorter: (a: EventOrPropType, b: EventOrPropType) => ('' + a[type]).localeCompare(b[type] || ''),
},
{
title: `Warnings (${num_warnings})`,
render: function RenderEvent(item: EventOrPropType): JSX.Element {
return (
<>
{!item.warnings?.length && '-'}
{item.warnings?.map((warning) => (
<Tooltip key={warning} color="orange" title={<>Warning! {warning}</>}>
<WarningOutlined style={{ color: 'var(--warning)' }} />
</Tooltip>
))}
</>
)
},
sorter: (a: EventOrPropType, b: EventOrPropType) => b.warnings.length - a.warnings.length,
},
{
title: function VolumeTitle() {
Expand All @@ -27,50 +66,108 @@ export function EventsVolumeTable(): JSX.Element {
)
},
// eslint-disable-next-line react/display-name
render: (
item: EventUsageType // https://stackoverflow.com/questions/55620562/eslint-component-definition-is-missing-displayname-react-display-name
) => <span className="ph-no-capture">{humanizeNumber(item.volume)}</span>,
sorter: (a: EventUsageType, b: EventUsageType) =>
render: function RenderVolume(item: EventOrPropType) {
return <span className="ph-no-capture">{humanizeNumber(item.volume)}</span>
},
sorter: (a: EventOrPropType, b: EventOrPropType) =>
a.volume == b.volume ? a.usage_count - b.usage_count : a.volume - b.volume,
},
{
title: function QueriesTitle() {
return (
<Tooltip
placement="right"
title="Number of queries in PostHog that included a filter on this event."
title={<>Number of queries in PostHog that included a filter on this {type}.</>}
>
30 day queries (delayed by up to an hour)
<InfoCircleOutlined className="info-indicator" />
</Tooltip>
)
},
// eslint-disable-next-line react/display-name
render: (item: EventUsageType) => <span className="ph-no-capture">{humanizeNumber(item.usage_count)}</span>,
sorter: (a: EventUsageType, b: EventUsageType) =>
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,
},
]
const { user } = useValues(userLogic)
useEffect(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a small improvement for later, I think this logic can be moved to a selector.

setDataWithWarnings(
data.map(
(item): EventOrPropType => {
item.warnings = []
if (item[type]?.endsWith(' ')) {
item.warnings.push(`This ${type} ends with a space.`)
}
if (item[type]?.startsWith(' ')) {
item.warnings.push(`This ${type} starts with a space.`)
}
return item
}
) || []
)
}, [])
return (
<>
{user?.team?.event_names_with_usage[0]?.volume === null && (
<>
<Alert
type="warning"
message="We haven't been able to get usage and volume data yet. Please check back later"
/>
<br />
</>
)}
<Input.Search
allowClear
enterButton
style={{ marginTop: '1.5rem', maxWidth: 400, width: 'initial', flexGrow: 1 }}
onChange={(e) => {
setSearchTerm(e.target.value)
}}
/>
<br />
<br />
<Table
dataSource={user?.team?.event_names_with_usage}
dataSource={searchTerm ? searchEvents(dataWithWarnings, searchTerm, type) : dataWithWarnings}
columns={columns}
rowKey="event"
rowKey={type}
size="small"
style={{ marginBottom: '4rem' }}
pagination={{ pageSize: 99999, hideOnSinglePage: true }}
/>
</>
)
}

export function UsageDisabledWarning({ tab }: { tab: string }): JSX.Element {
return (
<Alert
type="warning"
message={
<>
{tab} is not enabled on your instance. If you want to enable event usage please set the follow
environment variable: <pre style={{ display: 'inline' }}>ASYNC_EVENT_PROPERTY_USAGE=1</pre>
<br />
<br />
Please note, enabling this environment variable can increase load considerably if you have a large
volume of events.
</>
}
/>
)
}

export function EventsVolumeTable(): JSX.Element | null {
const { user } = useValues(userLogic)

return user?.team?.event_names_with_usage ? (
<>
{!user?.is_event_property_usage_enabled ? (
<UsageDisabledWarning tab="Properties Stats" />
) : (
user?.team?.event_names_with_usage[0]?.volume === null && (
<>
<Alert
type="warning"
message="We haven't been able to get usage and volume data yet. Please check back later"
/>
</>
)
)}
<VolumeTable data={user?.team?.event_names_with_usage as EventOrPropType[]} type="event" />
</>
) : null
}
109 changes: 20 additions & 89 deletions frontend/src/scenes/events/PropertiesVolumeTable.tsx
Original file line number Diff line number Diff line change
@@ -1,95 +1,26 @@
import React, { useState } from 'react'
import React from 'react'
import { useValues } from 'kea'
import { Alert, Button, Table, Tooltip } from 'antd'
import { userLogic } from 'scenes/userLogic'
import { InfoCircleOutlined } from '@ant-design/icons'
import { PropertyUsageType } from '~/types'
import { keyMapping, PropertyKeyInfo } from 'lib/components/PropertyKeyInfo'
import { humanizeNumber } from 'lib/utils'
import { VolumeTable, UsageDisabledWarning, EventOrPropType } from './EventsVolumeTable'
import { Alert } from 'antd'

const columns = [
{
title: 'Property',
render: function PropName(item: PropertyUsageType) {
return <PropertyKeyInfo value={item.key} />
},
sorter: (a: PropertyUsageType, b: PropertyUsageType) => {
// If PostHog property, put at end of list
if (keyMapping.event[a.key] && !keyMapping.event[b.key]) {
return 1
}
if (!keyMapping.event[a.key] && keyMapping.event[b.key]) {
return -1
}
return ('' + a.key).localeCompare(b.key)
},
defaultSortOrder: 'ascend',
},
{
title: function VolumeTitle() {
return (
<Tooltip
placement="right"
title="Total number of events that included this property in the last 30 days. Can be delayed by up to an hour."
>
30 day volume (delayed by up to an hour)
<InfoCircleOutlined className="info-indicator" />
</Tooltip>
)
},
render: (item: PropertyUsageType) => humanizeNumber(item.volume),
sorter: (a: PropertyUsageType, b: PropertyUsageType) =>
a.volume == b.volume ? a.usage_count - b.usage_count : a.volume - b.volume,
},
{
title: function QueriesTitle() {
return (
<Tooltip
placement="right"
title="Number of queries in PostHog that included a filter on this property."
>
30 day queries (delayed by up to an hour)
<InfoCircleOutlined className="info-indicator" />
</Tooltip>
)
},
render: (item: PropertyUsageType) => humanizeNumber(item.usage_count),
sorter: (a: PropertyUsageType, b: PropertyUsageType) =>
a.usage_count == b.usage_count ? a.volume - b.volume : a.usage_count - b.usage_count,
},
]

export function PropertiesVolumeTable(): JSX.Element {
export function PropertiesVolumeTable(): JSX.Element | null {
const { user } = useValues(userLogic)
const [showPostHogProps, setShowPostHogProps] = useState(true)
return (
<div>
<Button size="small" type="default" onClick={() => setShowPostHogProps(!showPostHogProps)}>
{showPostHogProps ? 'Hide' : 'Show'} PostHog properties
</Button>
<br />
<br />
{user?.team.event_properties_with_usage[0]?.volume === null && (
<>
<Alert
type="warning"
description="We haven't been able to get usage and volume data yet. Please check back later"
/>
<br />
</>
return user?.team?.event_properties_with_usage ? (
<>
{!user?.is_event_property_usage_enabled ? (
<UsageDisabledWarning tab="Properties Stats" />
) : (
user?.team?.event_properties_with_usage[0]?.volume === null && (
<>
<Alert
type="warning"
message="We haven't been able to get usage and volume data yet. Please check back later"
/>
</>
)
)}
<Table
dataSource={user?.team.event_properties_with_usage
.filter((item: PropertyUsageType) => (keyMapping.event[item.key] ? showPostHogProps : true))
.filter((item: PropertyUsageType) =>
keyMapping.event[item.key] && keyMapping.event[item.key].hide ? false : true
)}
rowKey="event"
columns={columns}
style={{ marginBottom: '4rem' }}
size="small"
pagination={{ pageSize: 99999, hideOnSinglePage: true }}
/>
</div>
)
<VolumeTable data={user?.team?.event_properties_with_usage as EventOrPropType[]} type="key" />
</>
) : null
}