- {alert && id !== 'new' && (
+ {alert && id && (
Delete alert
@@ -92,7 +156,7 @@ export function EditAlert(props: EditAlertProps): JSX.Element {
Cancel
- {id === 'new' ? 'Create alert' : 'Save'}
+ {!id ? 'Create alert' : 'Save'}
diff --git a/frontend/src/lib/components/Alerts/views/ManageAlerts.tsx b/frontend/src/lib/components/Alerts/views/ManageAlerts.tsx
index 6f7c8f953ffa3..6d904f0133105 100644
--- a/frontend/src/lib/components/Alerts/views/ManageAlerts.tsx
+++ b/frontend/src/lib/components/Alerts/views/ManageAlerts.tsx
@@ -1,14 +1,26 @@
-import { IconEllipsis } from '@posthog/icons'
+import { IconEllipsis, IconPause } from '@posthog/icons'
import { useActions, useValues } from 'kea'
+import { router } from 'kea-router'
+import { IconPlayCircle } from 'lib/lemon-ui/icons'
import { LemonButton } from 'lib/lemon-ui/LemonButton'
import { LemonModal } from 'lib/lemon-ui/LemonModal'
+import { LemonTag } from 'lib/lemon-ui/LemonTag'
import { ProfileBubbles } from 'lib/lemon-ui/ProfilePicture'
import { pluralize } from 'lib/utils'
+import { urls } from 'scenes/urls'
-import { AlertType } from '~/types'
+import { AlertType } from '~/queries/schema'
import { alertsLogic, AlertsLogicProps } from '../alertsLogic'
+export function AlertStateIndicator({ alert }: { alert: AlertType }): JSX.Element {
+ return alert.state === 'firing' ? (
+
+ ) : (
+
+ )
+}
+
interface AlertListItemProps {
alert: AlertType
onClick: () => void
@@ -16,15 +28,16 @@ interface AlertListItemProps {
}
export function AlertListItem({ alert, onClick, onDelete }: AlertListItemProps): JSX.Element {
+ const absoluteThreshold = alert.threshold?.configuration?.absoluteThreshold
return (
:
}
sideAction={{
icon:
,
-
dropdown: {
overlay: (
<>
@@ -45,9 +58,23 @@ export function AlertListItem({ alert, onClick, onDelete }: AlertListItemProps):
>
-
{alert.name}
+
+ {alert.name}
+ {alert.enabled ? (
+ <>
+
+
+ {absoluteThreshold?.lower && `Low ${absoluteThreshold.lower}`}
+ {absoluteThreshold?.lower && absoluteThreshold?.upper ? ' · ' : ''}
+ {absoluteThreshold?.upper && `High ${absoluteThreshold.upper}`}
+
+ >
+ ) : (
+
Disabled
+ )}
+
-
({ email }))} />
+ ({ email }))} />
)
@@ -55,10 +82,11 @@ export function AlertListItem({ alert, onClick, onDelete }: AlertListItemProps):
interface ManageAlertsProps extends AlertsLogicProps {
onCancel: () => void
- onSelect: (value: number | 'new') => void
+ onSelect: (value?: string) => void
}
export function ManageAlerts(props: ManageAlertsProps): JSX.Element {
+ const { push } = useActions(router)
const logic = alertsLogic(props)
const { alerts } = useValues(logic)
@@ -67,15 +95,20 @@ export function ManageAlerts(props: ManageAlertsProps): JSX.Element {
return (
<>
- Manage Alerts
+
+ Manage Alerts ALPHA
+
+
+ With alerts, PostHog will monitor your insight and notify you when certain conditions are met. We do
+ not evaluate alerts in real-time, but rather on a schedule of once every hour. Please note that
+ alerts are in alpha and may not be fully reliable.
+
{alerts.length ? (
- {alerts?.length}
- {' active '}
- {pluralize(alerts.length || 0, 'alert', 'alerts', false)}
+ {alerts?.length} {pluralize(alerts.length || 0, 'alert', 'alerts', false)}
{alerts.map((alert) => (
@@ -97,6 +130,9 @@ export function ManageAlerts(props: ManageAlertsProps): JSX.Element {
+ push(urls.alert(props.insightShortId, 'new'))}>
+ New alert
+
Close
diff --git a/frontend/src/lib/components/Cards/InsightCard/InsightCard.stories.tsx b/frontend/src/lib/components/Cards/InsightCard/InsightCard.stories.tsx
index b2e68d9bd6715..6ffbcf8f51e94 100644
--- a/frontend/src/lib/components/Cards/InsightCard/InsightCard.stories.tsx
+++ b/frontend/src/lib/components/Cards/InsightCard/InsightCard.stories.tsx
@@ -17,7 +17,6 @@ import EXAMPLE_TRENDS_PIE from '../../../../mocks/fixtures/api/projects/team_id/
import EXAMPLE_TRENDS_TABLE from '../../../../mocks/fixtures/api/projects/team_id/insights/trendsTable.json'
import EXAMPLE_TRENDS_HORIZONTAL_BAR from '../../../../mocks/fixtures/api/projects/team_id/insights/trendsValue.json'
import EXAMPLE_TRENDS_WORLD_MAP from '../../../../mocks/fixtures/api/projects/team_id/insights/trendsWorldMap.json'
-import EXAMPLE_PATHS from '../../../../mocks/fixtures/api/projects/team_id/insights/userPaths.json'
import { InsightCard as InsightCardComponent } from './index'
const examples = [
@@ -30,7 +29,6 @@ const examples = [
EXAMPLE_TRENDS_WORLD_MAP,
EXAMPLE_FUNNEL,
EXAMPLE_RETENTION,
- EXAMPLE_PATHS,
EXAMPLE_STICKINESS,
EXAMPLE_LIFECYCLE,
EXAMPLE_DATA_TABLE_NODE_HOGQL_QUERY,
@@ -64,7 +62,6 @@ const meta: Meta = {
control: { type: 'boolean' },
},
},
- tags: ['test-skip'], // :FIXME: flaky tests, most likely due to resize observer changes
}
export default meta
export const InsightCard: Story = (args) => {
diff --git a/frontend/src/lib/components/Cards/InsightCard/InsightCard.tsx b/frontend/src/lib/components/Cards/InsightCard/InsightCard.tsx
index 5cd43a5317597..833795d4f24b5 100644
--- a/frontend/src/lib/components/Cards/InsightCard/InsightCard.tsx
+++ b/frontend/src/lib/components/Cards/InsightCard/InsightCard.tsx
@@ -33,8 +33,6 @@ export interface InsightCardProps extends Resizeable, React.HTMLAttributes
diff --git a/frontend/src/lib/components/MemberSelectMultiple.tsx b/frontend/src/lib/components/MemberSelectMultiple.tsx
new file mode 100644
index 0000000000000..97900f97947b1
--- /dev/null
+++ b/frontend/src/lib/components/MemberSelectMultiple.tsx
@@ -0,0 +1,48 @@
+import { LemonInputSelect, ProfilePicture } from '@posthog/lemon-ui'
+import { useActions, useValues } from 'kea'
+import { fullName } from 'lib/utils'
+import { useEffect } from 'react'
+import { membersLogic } from 'scenes/organization/membersLogic'
+
+import { UserBasicType } from '~/types'
+
+type UserIdType = string | number
+
+export type MemberSelectMultipleProps = {
+ idKey: 'email' | 'uuid' | 'id'
+ value: UserIdType[]
+ onChange: (values: UserBasicType[]) => void
+}
+
+export function MemberSelectMultiple({ idKey, value, onChange }: MemberSelectMultipleProps): JSX.Element {
+ const { filteredMembers, membersLoading } = useValues(membersLogic)
+ const { ensureAllMembersLoaded } = useActions(membersLogic)
+
+ useEffect(() => {
+ ensureAllMembersLoaded()
+ }, [])
+
+ const options = filteredMembers.map((member) => ({
+ key: member.user[idKey].toString(),
+ label: fullName(member.user),
+ value: member.user[idKey],
+ icon: ,
+ }))
+
+ return (
+ v.toString())}
+ loading={membersLoading}
+ onChange={(newValues: UserIdType[]) => {
+ const selectedUsers = filteredMembers.filter((member) =>
+ newValues.includes(member.user[idKey].toString())
+ )
+ onChange(selectedUsers.map((member) => member.user))
+ }}
+ mode="multiple"
+ options={options}
+ data-attr="subscribed-users"
+ />
+ )
+}
diff --git a/frontend/src/lib/components/Playlist/Playlist.scss b/frontend/src/lib/components/Playlist/Playlist.scss
index d297c5928cb0b..6c4a6d4b9954f 100644
--- a/frontend/src/lib/components/Playlist/Playlist.scss
+++ b/frontend/src/lib/components/Playlist/Playlist.scss
@@ -51,7 +51,7 @@
.SessionRecordingPlaylistHeightWrapper {
// NOTE: Somewhat random way to offset the various headers and tabs above the playlist
- height: calc(100vh - 14rem);
+ height: calc(100vh - 15rem);
min-height: 25rem;
}
diff --git a/frontend/src/lib/components/PropertyFilters/utils.ts b/frontend/src/lib/components/PropertyFilters/utils.ts
index 3febc534c7df8..2c022d570e9ed 100644
--- a/frontend/src/lib/components/PropertyFilters/utils.ts
+++ b/frontend/src/lib/components/PropertyFilters/utils.ts
@@ -19,6 +19,7 @@ import {
FilterLogicalOperator,
GroupPropertyFilter,
HogQLPropertyFilter,
+ LogEntryPropertyFilter,
PersonPropertyFilter,
PropertyDefinitionType,
PropertyFilterType,
@@ -103,6 +104,7 @@ export const PROPERTY_FILTER_TYPE_TO_TAXONOMIC_FILTER_GROUP_TYPE: Record v === filterType)?.[0] as
| PropertyFilterType
| undefined
diff --git a/frontend/src/lib/components/TaxonomicFilter/types.ts b/frontend/src/lib/components/TaxonomicFilter/types.ts
index 6ee6afd304203..a496095b8471b 100644
--- a/frontend/src/lib/components/TaxonomicFilter/types.ts
+++ b/frontend/src/lib/components/TaxonomicFilter/types.ts
@@ -109,6 +109,7 @@ export enum TaxonomicFilterGroupType {
SessionProperties = 'session_properties',
HogQLExpression = 'hogql_expression',
Notebooks = 'notebooks',
+ LogEntries = 'log_entries',
// Misc
Replay = 'replay',
}
diff --git a/frontend/src/lib/components/UniversalFilters/utils.ts b/frontend/src/lib/components/UniversalFilters/utils.ts
index 923ca44767385..f8b63af80ce5e 100644
--- a/frontend/src/lib/components/UniversalFilters/utils.ts
+++ b/frontend/src/lib/components/UniversalFilters/utils.ts
@@ -1,4 +1,4 @@
-import { ActionFilter, FilterLogicalOperator, RecordingPropertyFilter } from '~/types'
+import { ActionFilter, FilterLogicalOperator, LogEntryPropertyFilter, RecordingPropertyFilter } from '~/types'
import { isCohortPropertyFilter } from '../PropertyFilters/utils'
import { UniversalFiltersGroup, UniversalFiltersGroupValue, UniversalFilterValue } from './UniversalFilters'
@@ -6,23 +6,21 @@ import { UniversalFiltersGroup, UniversalFiltersGroupValue, UniversalFilterValue
export function isUniversalGroupFilterLike(filter?: UniversalFiltersGroupValue): filter is UniversalFiltersGroup {
return filter?.type === FilterLogicalOperator.And || filter?.type === FilterLogicalOperator.Or
}
-
export function isEntityFilter(filter: UniversalFilterValue): filter is ActionFilter {
return isEventFilter(filter) || isActionFilter(filter)
}
-
export function isEventFilter(filter: UniversalFilterValue): filter is ActionFilter {
return filter.type === 'events'
}
-
export function isActionFilter(filter: UniversalFilterValue): filter is ActionFilter {
return filter.type === 'actions'
}
-
export function isRecordingPropertyFilter(filter: UniversalFilterValue): filter is RecordingPropertyFilter {
return filter.type === 'recording'
}
-
+export function isLogEntryPropertyFilter(filter: UniversalFilterValue): filter is LogEntryPropertyFilter {
+ return filter.type === 'log_entry'
+}
export function isEditableFilter(filter: UniversalFilterValue): boolean {
return isEntityFilter(filter) ? false : !isCohortPropertyFilter(filter)
}
diff --git a/frontend/src/lib/components/VersionChecker/versionCheckerLogic.test.ts b/frontend/src/lib/components/VersionChecker/versionCheckerLogic.test.ts
index cc8262a73e2f0..2107640885e26 100644
--- a/frontend/src/lib/components/VersionChecker/versionCheckerLogic.test.ts
+++ b/frontend/src/lib/components/VersionChecker/versionCheckerLogic.test.ts
@@ -72,11 +72,11 @@ describe('versionCheckerLogic', () => {
it.each([
{ versionCount: 1, expectation: null },
{
- versionCount: 10,
+ versionCount: 11,
expectation: {
latestUsedVersion: '1.0.0',
- latestAvailableVersion: '1.0.9',
- numVersionsBehind: 9,
+ latestAvailableVersion: '1.0.10',
+ numVersionsBehind: 10,
level: 'info',
},
},
@@ -86,7 +86,7 @@ describe('versionCheckerLogic', () => {
latestUsedVersion: '1.0.0',
latestAvailableVersion: '1.0.14',
numVersionsBehind: 14,
- level: 'warning',
+ level: 'info',
},
},
{
@@ -127,12 +127,7 @@ describe('versionCheckerLogic', () => {
{ version: '1.9.0', timestamp: '2023-01-01T12:00:00Z' },
{ version: '1.83.1', timestamp: '2023-01-01T10:00:00Z' },
],
- expectation: {
- latestAvailableVersion: '1.84.0',
- latestUsedVersion: '1.83.1',
- level: 'info',
- numVersionsBehind: 1,
- },
+ expectation: null,
},
{
usedVersions: [
diff --git a/frontend/src/lib/components/VersionChecker/versionCheckerLogic.ts b/frontend/src/lib/components/VersionChecker/versionCheckerLogic.ts
index 4b08c9e3be675..3a86eedbd95a9 100644
--- a/frontend/src/lib/components/VersionChecker/versionCheckerLogic.ts
+++ b/frontend/src/lib/components/VersionChecker/versionCheckerLogic.ts
@@ -184,19 +184,20 @@ export const versionCheckerLogic = kea([
let level: 'warning' | 'info' | 'error' | undefined
if (diff.kind === 'major' || numVersionsBehind >= 20) {
level = 'error'
- } else if ((diff.kind === 'minor' && diff.diff >= 5) || numVersionsBehind >= 10) {
+ } else if (diff.kind === 'minor' && diff.diff >= 15) {
level = 'warning'
- } else if (diff.kind === 'minor' || numVersionsBehind >= 5) {
+ } else if ((diff.kind === 'minor' && diff.diff >= 10) || numVersionsBehind >= 10) {
level = 'info'
} else if (latestUsedVersion.extra) {
- // if we have an extra (alpha/beta/rc/etc) version, we should always show a warning if they aren't on the latest
+ // if we have an extra (alpha/beta/rc/etc.) version, we should always show a warning if they aren't on the latest
level = 'warning'
} else {
// don't warn for a small number of patch versions behind
level = undefined
}
- if (level) {
+ // we check if there is a "latest user version string" to avoid returning odd data in unexpected cases
+ if (level && !!versionToString(latestUsedVersion).trim().length) {
warning = {
latestUsedVersion: versionToString(latestUsedVersion),
latestAvailableVersion: versionToString(latestAvailableVersion),
diff --git a/frontend/src/lib/constants.tsx b/frontend/src/lib/constants.tsx
index d9910233b794d..e33b6ee88bba7 100644
--- a/frontend/src/lib/constants.tsx
+++ b/frontend/src/lib/constants.tsx
@@ -162,8 +162,6 @@ export const FEATURE_FLAGS = {
PRODUCT_SPECIFIC_ONBOARDING: 'product-specific-onboarding', // owner: @raquelmsmith
REDIRECT_SIGNUPS_TO_INSTANCE: 'redirect-signups-to-instance', // owner: @raquelmsmith
APPS_AND_EXPORTS_UI: 'apps-and-exports-ui', // owner: @benjackwhite
- QUERY_BASED_DASHBOARD_CARDS: 'query-based-dashboard-cards', // owner: @thmsobrmlr
- QUERY_BASED_INSIGHTS_SAVING: 'query-based-insights-saving', // owner: @thmsobrmlr
HOGQL_DASHBOARD_ASYNC: 'hogql-dashboard-async', // owner: @webjunkie
WEBHOOKS_DENYLIST: 'webhooks-denylist', // owner: #team-pipeline
PIPELINE_UI: 'pipeline-ui', // owner: #team-pipeline
diff --git a/frontend/src/lib/lemon-ui/LemonMenu/LemonMenu.tsx b/frontend/src/lib/lemon-ui/LemonMenu/LemonMenu.tsx
index 870c0946279ab..71e743d5b6a57 100644
--- a/frontend/src/lib/lemon-ui/LemonMenu/LemonMenu.tsx
+++ b/frontend/src/lib/lemon-ui/LemonMenu/LemonMenu.tsx
@@ -20,13 +20,15 @@ export interface LemonMenuItemBase
custom?: boolean
}
export interface LemonMenuItemNode extends LemonMenuItemBase {
- items: (LemonMenuItemLeaf | false | null)[]
+ items: (LemonMenuItem | false | null)[]
+ placement?: LemonDropdownProps['placement']
keyboardShortcut?: never
}
export type LemonMenuItemLeaf =
| (LemonMenuItemBase & {
onClick: () => void
items?: never
+ placement?: never
keyboardShortcut?: KeyboardShortcut
})
| (LemonMenuItemBase & {
@@ -34,6 +36,7 @@ export type LemonMenuItemLeaf =
disableClientSideRouting?: boolean
targetBlank?: boolean
items?: never
+ placement?: never
keyboardShortcut?: KeyboardShortcut
})
| (LemonMenuItemBase & {
@@ -42,6 +45,7 @@ export type LemonMenuItemLeaf =
disableClientSideRouting?: boolean
targetBlank?: boolean
items?: never
+ placement?: never
keyboardShortcut?: KeyboardShortcut
})
export interface LemonMenuItemCustom {
@@ -52,6 +56,7 @@ export interface LemonMenuItemCustom {
keyboardShortcut?: never
/** True if the item is a custom element. */
custom?: boolean
+ placement?: never
}
export type LemonMenuItem = LemonMenuItemLeaf | LemonMenuItemCustom | LemonMenuItemNode
@@ -243,7 +248,7 @@ interface LemonMenuItemButtonProps {
const LemonMenuItemButton: FunctionComponent> =
React.forwardRef(
(
- { item: { label, items, keyboardShortcut, custom, ...buttonProps }, size, tooltipPlacement },
+ { item: { label, items, placement, keyboardShortcut, custom, ...buttonProps }, size, tooltipPlacement },
ref
): JSX.Element => {
const Label = typeof label === 'function' ? label : null
@@ -272,7 +277,7 @@ const LemonMenuItemButton: FunctionComponent
diff --git a/frontend/src/lib/lemon-ui/Link/Link.tsx b/frontend/src/lib/lemon-ui/Link/Link.tsx
index 08a37f4366add..a8ce49710e678 100644
--- a/frontend/src/lib/lemon-ui/Link/Link.tsx
+++ b/frontend/src/lib/lemon-ui/Link/Link.tsx
@@ -1,7 +1,6 @@
import './Link.scss'
import clsx from 'clsx'
-import { useActions } from 'kea'
import { router } from 'kea-router'
import { isExternalLink } from 'lib/utils'
import { getCurrentTeamId } from 'lib/utils/getAppContext'
@@ -96,8 +95,6 @@ export const Link: React.FC> = Reac
href: typeof to === 'string' ? to : undefined,
})
- const { openSidePanel } = useActions(sidePanelStateLogic)
-
const onClick = (event: React.MouseEvent): void => {
if (event.metaKey || event.ctrlKey) {
event.stopPropagation()
@@ -111,8 +108,26 @@ export const Link: React.FC> = Reac
return
}
- if (typeof to === 'string' && isPostHogComDocs(to)) {
+ const mountedSidePanelLogic = sidePanelStateLogic.findMounted()
+
+ if (typeof to === 'string' && isPostHogComDocs(to) && mountedSidePanelLogic) {
+ // TRICKY: We do this instead of hooks as there is some weird cyclic issue in tests
+ const { sidePanelOpen } = mountedSidePanelLogic.values
+ const { openSidePanel } = mountedSidePanelLogic.actions
+
event.preventDefault()
+
+ const target = event.currentTarget
+ const container = document.getElementsByTagName('main')[0]
+ const topBar = document.getElementsByClassName('TopBar3000')[0]
+ if (!sidePanelOpen && container.contains(target)) {
+ setTimeout(() => {
+ // Little delay to allow the rendering of the side panel
+ const y = container.scrollTop + target.getBoundingClientRect().top - topBar.clientHeight
+ container.scrollTo({ top: y })
+ }, 50)
+ }
+
openSidePanel(SidePanelTab.Docs, to)
return
}
diff --git a/frontend/src/lib/utils/deleteWithUndo.tsx b/frontend/src/lib/utils/deleteWithUndo.tsx
index 0977a1e3d6bb8..7812a880edd6f 100644
--- a/frontend/src/lib/utils/deleteWithUndo.tsx
+++ b/frontend/src/lib/utils/deleteWithUndo.tsx
@@ -1,6 +1,5 @@
import { lemonToast } from '@posthog/lemon-ui'
import api from 'lib/api'
-import { getInsightModel, InsightsApiOptions } from 'scenes/insights/utils/api'
import { QueryBasedInsightModel } from '~/types'
@@ -40,7 +39,6 @@ export async function deleteWithUndo>({
* when given a query based insight */
export async function deleteInsightWithUndo({
undo = false,
- options,
...props
}: {
undo?: boolean
@@ -48,10 +46,9 @@ export async function deleteInsightWithUndo({
object: QueryBasedInsightModel
idField?: keyof QueryBasedInsightModel
callback?: (undo: boolean, object: QueryBasedInsightModel) => void
- options: InsightsApiOptions
}): Promise {
await api.update(`api/${props.endpoint}/${props.object[props.idField || 'id']}`, {
- ...getInsightModel(props.object, options.writeAsQuery),
+ ...props.object,
deleted: !undo,
})
props.callback?.(undo, props.object)
@@ -66,7 +63,7 @@ export async function deleteInsightWithUndo({
? undefined
: {
label: 'Undo',
- action: () => deleteInsightWithUndo({ undo: true, options, ...props }),
+ action: () => deleteInsightWithUndo({ undo: true, ...props }),
},
}
)
diff --git a/frontend/src/lib/utils/eventUsageLogic.ts b/frontend/src/lib/utils/eventUsageLogic.ts
index 63b62be00ee67..2e6db6758873d 100644
--- a/frontend/src/lib/utils/eventUsageLogic.ts
+++ b/frontend/src/lib/utils/eventUsageLogic.ts
@@ -3,7 +3,7 @@ import { BarStatus, ResultType } from 'lib/components/CommandBar/types'
import {
convertPropertyGroupToProperties,
isGroupPropertyFilter,
- isRecordingPropertyFilter,
+ isLogEntryPropertyFilter,
isValidPropertyFilter,
} from 'lib/components/PropertyFilters/utils'
import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types'
@@ -71,7 +71,9 @@ import type { eventUsageLogicType } from './eventUsageLogicType'
export enum DashboardEventSource {
LongPress = 'long_press',
MoreDropdown = 'more_dropdown',
- DashboardHeader = 'dashboard_header',
+ DashboardHeaderSaveDashboard = 'dashboard_header_save_dashboard',
+ DashboardHeaderDiscardChanges = 'dashboard_header_discard_changes',
+ DashboardHeaderExitFullscreen = 'dashboard_header_exit_fullscreen',
Hotkey = 'hotkey',
InputEnter = 'input_enter',
Toast = 'toast',
@@ -930,9 +932,7 @@ export const eventUsageLogic = kea([
const eventFilters = filterValues.filter(isEventFilter)
const actionFilters = filterValues.filter(isActionFilter)
const propertyFilters = filterValues.filter(isValidPropertyFilter)
- const consoleLogFilters = propertyFilters
- .filter(isRecordingPropertyFilter)
- .filter((f) => ['console_log_level', 'console_log_query'].includes(f.key))
+ const consoleLogFilters = propertyFilters.filter(isLogEntryPropertyFilter)
const filterBreakdown =
filters && defaultDurationFilter
diff --git a/frontend/src/lib/utils/semver.test.ts b/frontend/src/lib/utils/semver.test.ts
index 31bcc58cdf5c9..67ee07860fc7f 100644
--- a/frontend/src/lib/utils/semver.test.ts
+++ b/frontend/src/lib/utils/semver.test.ts
@@ -1,4 +1,4 @@
-import { highestVersion, lowestVersion, parseVersion, versionToString } from './semver'
+import { createVersionChecker, highestVersion, lowestVersion, parseVersion, versionToString } from './semver'
describe('semver', () => {
describe('parseVersion', () => {
@@ -52,4 +52,14 @@ describe('semver', () => {
expect(versionToString({ major: 1 })).toEqual('1')
})
})
+ describe('createVersionChecker', () => {
+ it('should create a version checker that checks that a version is above or equal to a specified version', () => {
+ const isSupportedVersion = createVersionChecker('4.5.6')
+ expect(isSupportedVersion('1.2.3')).toEqual(false)
+ expect(isSupportedVersion('4.5.6')).toEqual(true)
+ expect(isSupportedVersion('4.5.7')).toEqual(true)
+ expect(isSupportedVersion('7.8.9')).toEqual(true)
+ expect(isSupportedVersion('4.5.6-alpha')).toEqual(false)
+ })
+ })
})
diff --git a/frontend/src/lib/utils/semver.ts b/frontend/src/lib/utils/semver.ts
index 5a8f4606d7247..79cf377c51584 100644
--- a/frontend/src/lib/utils/semver.ts
+++ b/frontend/src/lib/utils/semver.ts
@@ -106,3 +106,10 @@ export function versionToString(version: SemanticVersion): string {
}
return versionPart
}
+
+export function createVersionChecker(requiredVersion: string | SemanticVersion) {
+ return (version: string | SemanticVersion): boolean => {
+ const diff = diffVersions(version, requiredVersion)
+ return !diff || diff.diff > 0
+ }
+}
diff --git a/frontend/src/models/insightsModel.tsx b/frontend/src/models/insightsModel.tsx
index 976a952ea5f70..f72e48c4ff35c 100644
--- a/frontend/src/models/insightsModel.tsx
+++ b/frontend/src/models/insightsModel.tsx
@@ -1,9 +1,7 @@
import { LemonDialog, LemonInput } from '@posthog/lemon-ui'
-import { actions, connect, kea, listeners, path, selectors } from 'kea'
-import { FEATURE_FLAGS } from 'lib/constants'
+import { actions, connect, kea, listeners, path } from 'kea'
import { LemonField } from 'lib/lemon-ui/LemonField'
import { lemonToast } from 'lib/lemon-ui/LemonToast/LemonToast'
-import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { insightsApi } from 'scenes/insights/utils/api'
import { teamLogic } from 'scenes/teamLogic'
@@ -13,7 +11,7 @@ import type { insightsModelType } from './insightsModelType'
export const insightsModel = kea([
path(['models', 'insightsModel']),
- connect({ values: [featureFlagLogic, ['featureFlags']], logic: [teamLogic] }),
+ connect({ logic: [teamLogic] }),
actions(() => ({
renameInsight: (item: QueryBasedInsightModel) => ({ item }),
renameInsightSuccess: (item: QueryBasedInsightModel) => ({ item }),
@@ -25,13 +23,7 @@ export const insightsModel = kea([
insightIds,
}),
})),
- selectors({
- queryBasedInsightSaving: [
- (s) => [s.featureFlags],
- (featureFlags) => !!featureFlags[FEATURE_FLAGS.QUERY_BASED_INSIGHTS_SAVING],
- ],
- }),
- listeners(({ actions, values }) => ({
+ listeners(({ actions }) => ({
renameInsight: async ({ item }) => {
LemonDialog.openForm({
title: 'Rename insight',
@@ -45,11 +37,7 @@ export const insightsModel = kea([
insightName: (name) => (!name ? 'You must enter a name' : undefined),
},
onSubmit: async ({ insightName }) => {
- const updatedItem = await insightsApi.update(
- item.id,
- { name: insightName },
- { writeAsQuery: values.queryBasedInsightSaving, readAsQuery: true }
- )
+ const updatedItem = await insightsApi.update(item.id, { name: insightName })
lemonToast.success(
<>
Renamed insight from {item.name} to {insightName}
@@ -60,10 +48,7 @@ export const insightsModel = kea([
})
},
duplicateInsight: async ({ item }) => {
- const addedItem = await insightsApi.duplicate(item, {
- writeAsQuery: values.queryBasedInsightSaving,
- readAsQuery: true,
- })
+ const addedItem = await insightsApi.duplicate(item)
actions.duplicateInsightSuccess(addedItem)
lemonToast.success('Insight duplicated')
diff --git a/frontend/src/queries/Query/Query.tsx b/frontend/src/queries/Query/Query.tsx
index 3828f98f2e431..d5a4bb5755a99 100644
--- a/frontend/src/queries/Query/Query.tsx
+++ b/frontend/src/queries/Query/Query.tsx
@@ -1,5 +1,4 @@
import { LemonDivider } from 'lib/lemon-ui/LemonDivider'
-import { SpinnerOverlay } from 'lib/lemon-ui/Spinner'
import { useEffect, useState } from 'react'
import { HogDebug } from 'scenes/debug/HogDebug'
@@ -38,14 +37,12 @@ export interface QueryProps {
cachedResults?: AnyResponseType
/** Disable any changes to the query */
readOnly?: boolean
- /** Show a stale overlay */
- stale?: boolean
/** Reduce UI elements to only show data */
embedded?: boolean
}
export function Query(props: QueryProps): JSX.Element | null {
- const { query: propsQuery, setQuery: propsSetQuery, readOnly, stale, embedded } = props
+ const { query: propsQuery, setQuery: propsSetQuery, readOnly, embedded } = props
const [localQuery, localSetQuery] = useState(propsQuery)
useEffect(() => {
@@ -139,7 +136,6 @@ export function Query(props: QueryProps): JSX.Element | null
>
) : null}
- {stale && }
{component}
>
diff --git a/frontend/src/queries/nodes/DataVisualization/dataVisualizationLogic.ts b/frontend/src/queries/nodes/DataVisualization/dataVisualizationLogic.ts
index a0b3467f458df..79b3991486d5d 100644
--- a/frontend/src/queries/nodes/DataVisualization/dataVisualizationLogic.ts
+++ b/frontend/src/queries/nodes/DataVisualization/dataVisualizationLogic.ts
@@ -537,57 +537,52 @@ export const dataVisualizationLogic = kea([
},
],
tabularData: [
- (state) => [state.selectedTabularSeries, state.response, state.columns],
- (selectedTabularSeries, response, columns): any[][] => {
- if (!response || selectedTabularSeries === null || selectedTabularSeries.length === 0) {
+ (state) => [state.tabularColumns, state.response],
+ (tabularColumns, response): any[][] => {
+ if (!response || tabularColumns === null) {
return []
}
const data: any[] = response?.['results'] ?? response?.['result'] ?? []
return data.map((row: any[]) => {
- return selectedTabularSeries.map((series) => {
- if (!series) {
- return null
- }
-
- const column = columns.find((n) => n.name === series.name)
+ return tabularColumns.map((column) => {
if (!column) {
return null
}
- const value = row[column.dataIndex]
+ const value = row[column.column.dataIndex]
- if (column.type.isNumerical) {
+ if (column.column.type.isNumerical) {
try {
if (value === null) {
return value
}
- const multiplier = series.settings.formatting?.style === 'percent' ? 100 : 1
+ const multiplier = column.settings?.formatting?.style === 'percent' ? 100 : 1
- if (series.settings.formatting?.decimalPlaces) {
+ if (column.settings?.formatting?.decimalPlaces) {
return formatDataWithSettings(
parseFloat(
(parseFloat(value) * multiplier).toFixed(
- series.settings.formatting.decimalPlaces
+ column.settings.formatting.decimalPlaces
)
),
- series.settings
+ column.settings
)
}
const isInt = Number.isInteger(value)
return formatDataWithSettings(
isInt ? parseInt(value, 10) * multiplier : parseFloat(value) * multiplier,
- series.settings
+ column.settings
)
} catch {
return 0
}
}
- return formatDataWithSettings(value, series.settings)
+ return formatDataWithSettings(value, column.settings)
})
})
},
@@ -595,11 +590,11 @@ export const dataVisualizationLogic = kea([
tabularColumns: [
(state) => [state.selectedTabularSeries, state.response, state.columns],
(selectedTabularSeries, response, columns): AxisSeries[] => {
- if (!response || selectedTabularSeries === null || selectedTabularSeries.length === 0) {
+ if (!response) {
return []
}
- return selectedTabularSeries
+ const selectedColumns = (selectedTabularSeries || [])
.map((series): AxisSeries | null => {
if (!series) {
return null
@@ -617,6 +612,15 @@ export const dataVisualizationLogic = kea([
}
})
.filter((series): series is AxisSeries => Boolean(series))
+
+ if (selectedColumns.length === 0) {
+ return columns.map((column) => ({
+ column,
+ data: [],
+ settings: { formatting: { prefix: '', suffix: '' } },
+ }))
+ }
+ return selectedColumns
},
],
dataVisualizationProps: [() => [(_, props) => props], (props): DataVisualizationLogicProps => props],
diff --git a/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.ts b/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.ts
index 01b4cfadfd526..9a8f36c6cb548 100644
--- a/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.ts
+++ b/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.ts
@@ -10,7 +10,6 @@ import {
InsightNodeKind,
InsightQueryNode,
LifecycleFilterLegacy,
- Node,
NodeKind,
PathsFilterLegacy,
RetentionFilterLegacy,
@@ -22,14 +21,13 @@ import {
isDataWarehouseNode,
isEventsNode,
isFunnelsQuery,
- isInsightVizNode,
isLifecycleQuery,
isPathsQuery,
isRetentionQuery,
isStickinessQuery,
isTrendsQuery,
} from '~/queries/utils'
-import { ActionFilter, EntityTypes, FilterType, InsightType, QueryBasedInsightModel } from '~/types'
+import { ActionFilter, EntityTypes, FilterType, InsightType } from '~/types'
type FilterTypeActionsAndEvents = {
events?: ActionFilter[]
@@ -123,28 +121,6 @@ const nodeKindToFilterKey: Record = {
[NodeKind.LifecycleQuery]: 'lifecycleFilter',
}
-/** Returns a `query` or converted `filters` for a query based insight,
- * depending on the feature flag. This is necessary as we want to
- * transition to query based insights on the frontend, while the backend
- * still has filter based insights (and or conversion function is frontend side).
- *
- * The feature flag can be enabled once we want to persist query based insights
- * backend side. Once the flag is rolled out 100% this function becomes obsolete.
- */
-export const getInsightFilterOrQueryForPersistance = (
- insight: QueryBasedInsightModel,
- queryBasedInsightSavingFlag: boolean
-): { filters: Partial | undefined; query: Node> | null | undefined } => {
- let filters
- let query
- if (!queryBasedInsightSavingFlag && isInsightVizNode(insight.query)) {
- filters = queryNodeToFilter(insight.query.source)
- } else {
- query = insight.query
- }
- return { filters, query }
-}
-
export const queryNodeToFilter = (query: InsightQueryNode): Partial => {
const filters: Partial = objectClean({
insight: nodeKindToInsightType[query.kind],
diff --git a/frontend/src/queries/nodes/InsightViz/InsightViz.tsx b/frontend/src/queries/nodes/InsightViz/InsightViz.tsx
index 7bbd78602377c..694c206dd1333 100644
--- a/frontend/src/queries/nodes/InsightViz/InsightViz.tsx
+++ b/frontend/src/queries/nodes/InsightViz/InsightViz.tsx
@@ -3,7 +3,7 @@ import './InsightViz.scss'
import clsx from 'clsx'
import { BindLogic, useValues } from 'kea'
import { useFeatureFlag } from 'lib/hooks/useFeatureFlag'
-import React, { useState } from 'react'
+import { useState } from 'react'
import { insightLogic } from 'scenes/insights/insightLogic'
import { insightSceneLogic } from 'scenes/insights/insightSceneLogic'
import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic'
@@ -78,9 +78,19 @@ export function InsightViz({ uniqueKey, query, setQuery, context, readOnly, embe
const showingResults = query.showResults ?? true
const isEmbedded = embedded || (query.embedded ?? false)
- const Wrapper = ({ children }: { children: React.ReactElement }): JSX.Element => {
- return isEmbedded ? <>{children}> : {children}
- }
+ const display = (
+
+ )
return (
@@ -98,20 +108,7 @@ export function InsightViz({ uniqueKey, query, setQuery, context, readOnly, embe
{!readOnly && (
)}
-
-
-
-
+ {!isEmbedded ? {display}
: display}
diff --git a/frontend/src/queries/nodes/InsightViz/TrendsSeries.tsx b/frontend/src/queries/nodes/InsightViz/TrendsSeries.tsx
index 3c5748cba6dd6..f4516279a254c 100644
--- a/frontend/src/queries/nodes/InsightViz/TrendsSeries.tsx
+++ b/frontend/src/queries/nodes/InsightViz/TrendsSeries.tsx
@@ -19,7 +19,9 @@ import { queryNodeToFilter } from '../InsightQuery/utils/queryNodeToFilter'
export function TrendsSeries(): JSX.Element | null {
const { insightProps } = useValues(insightLogic)
- const { querySource, isLifecycle, isStickiness, display, hasFormula } = useValues(insightVizDataLogic(insightProps))
+ const { querySource, isLifecycle, isStickiness, display, hasFormula, series } = useValues(
+ insightVizDataLogic(insightProps)
+ )
const { updateQuerySource } = useActions(insightVizDataLogic(insightProps))
const { showGroupsOptions, groupsTaxonomicTypes } = useValues(groupsModel)
@@ -88,6 +90,7 @@ export function TrendsSeries(): JSX.Element | null {
TaxonomicFilterGroupType.Actions,
TaxonomicFilterGroupType.DataWarehouse,
]}
+ hideDeleteBtn={series?.length === 1}
/>
>
)
diff --git a/frontend/src/queries/schema.json b/frontend/src/queries/schema.json
index c6bd48628bd45..a77404a9ffd1a 100644
--- a/frontend/src/queries/schema.json
+++ b/frontend/src/queries/schema.json
@@ -1,18 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
- "AbsoluteThreshold": {
- "additionalProperties": false,
- "properties": {
- "lower": {
- "type": ["number", "null"]
- },
- "upper": {
- "type": ["number", "null"]
- }
- },
- "type": "object"
- },
"ActionsNode": {
"additionalProperties": false,
"properties": {
@@ -195,14 +183,143 @@
"enum": ["numeric", "duration", "duration_ms", "percentage", "percentage_scaled"],
"type": "string"
},
- "AnomalyCondition": {
+ "AlertCheck": {
"additionalProperties": false,
"properties": {
- "absoluteThreshold": {
- "$ref": "#/definitions/AbsoluteThreshold"
+ "calculated_value": {
+ "type": "number"
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "state": {
+ "type": "string"
+ },
+ "targets_notified": {
+ "type": "boolean"
+ }
+ },
+ "required": ["id", "created_at", "calculated_value", "state", "targets_notified"],
+ "type": "object"
+ },
+ "AlertCondition": {
+ "additionalProperties": false,
+ "type": "object"
+ },
+ "AlertType": {
+ "additionalProperties": false,
+ "properties": {
+ "checks": {
+ "items": {
+ "$ref": "#/definitions/AlertCheck"
+ },
+ "type": "array"
+ },
+ "condition": {
+ "$ref": "#/definitions/AlertCondition"
+ },
+ "created_at": {
+ "type": "string"
+ },
+ "created_by": {
+ "$ref": "#/definitions/UserBasicType"
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "id": {
+ "type": "string"
+ },
+ "insight": {
+ "type": "number"
+ },
+ "last_notified_at": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "state": {
+ "type": "string"
+ },
+ "subscribed_users": {
+ "items": {
+ "$ref": "#/definitions/UserBasicType"
+ },
+ "type": "array"
+ },
+ "threshold": {
+ "additionalProperties": false,
+ "properties": {
+ "configuration": {
+ "$ref": "#/definitions/InsightThreshold"
+ }
+ },
+ "required": ["configuration"],
+ "type": "object"
+ }
+ },
+ "required": [
+ "checks",
+ "condition",
+ "created_at",
+ "created_by",
+ "enabled",
+ "id",
+ "insight",
+ "last_notified_at",
+ "name",
+ "state",
+ "subscribed_users",
+ "threshold"
+ ],
+ "type": "object"
+ },
+ "AlertTypeBase": {
+ "additionalProperties": false,
+ "properties": {
+ "condition": {
+ "$ref": "#/definitions/AlertCondition"
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "insight": {
+ "type": "number"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": ["name", "condition", "enabled", "insight"],
+ "type": "object"
+ },
+ "AlertTypeWrite": {
+ "additionalProperties": false,
+ "properties": {
+ "condition": {
+ "$ref": "#/definitions/AlertCondition"
+ },
+ "enabled": {
+ "type": "boolean"
+ },
+ "insight": {
+ "type": "number"
+ },
+ "name": {
+ "type": "string"
+ },
+ "subscribed_users": {
+ "items": {
+ "type": "integer"
+ },
+ "type": "array"
}
},
- "required": ["absoluteThreshold"],
+ "required": ["condition", "enabled", "insight", "name", "subscribed_users"],
"type": "object"
},
"AnyDataNode": {
@@ -310,6 +427,9 @@
{
"$ref": "#/definitions/RecordingPropertyFilter"
},
+ {
+ "$ref": "#/definitions/LogEntryPropertyFilter"
+ },
{
"$ref": "#/definitions/GroupPropertyFilter"
},
@@ -4742,6 +4862,10 @@
}
]
},
+ "HedgehogColorOptions": {
+ "enum": ["green", "red", "blue", "purple", "dark", "light", "sepia", "invert", "invert-hue", "greyscale"],
+ "type": "string"
+ },
"HogLanguage": {
"enum": ["hog", "hogJson", "hogQL", "hogQLExpr", "hogTemplate"],
"type": "string"
@@ -4994,7 +5118,7 @@
"description": "HogQL Query Options are automatically set per team. However, they can be overriden in the query.",
"properties": {
"bounceRatePageViewMode": {
- "enum": ["count_pageviews", "uniq_urls"],
+ "enum": ["count_pageviews", "uniq_urls", "uniq_page_screen_autocaptures"],
"type": "string"
},
"dataWarehouseEventsModifiers": {
@@ -5035,7 +5159,7 @@
"type": "string"
},
"propertyGroupsMode": {
- "enum": ["enabled", "disabled"],
+ "enum": ["enabled", "disabled", "optimized"],
"type": "string"
},
"s3TableUseInvalidColumns": {
@@ -5475,6 +5599,15 @@
"InsightShortId": {
"type": "string"
},
+ "InsightThreshold": {
+ "additionalProperties": false,
+ "properties": {
+ "absoluteThreshold": {
+ "$ref": "#/definitions/InsightsThresholdAbsolute"
+ }
+ },
+ "type": "object"
+ },
"InsightVizNode": {
"additionalProperties": false,
"properties": {
@@ -5777,6 +5910,18 @@
"required": ["kind"],
"type": "object"
},
+ "InsightsThresholdAbsolute": {
+ "additionalProperties": false,
+ "properties": {
+ "lower": {
+ "type": "number"
+ },
+ "upper": {
+ "type": "number"
+ }
+ },
+ "type": "object"
+ },
"IntervalType": {
"enum": ["minute", "hour", "day", "week", "month"],
"type": "string"
@@ -5925,6 +6070,55 @@
"enum": ["new", "resurrecting", "returning", "dormant"],
"type": "string"
},
+ "LogEntryPropertyFilter": {
+ "additionalProperties": false,
+ "properties": {
+ "key": {
+ "type": "string"
+ },
+ "label": {
+ "type": "string"
+ },
+ "operator": {
+ "$ref": "#/definitions/PropertyOperator"
+ },
+ "type": {
+ "const": "log_entry",
+ "type": "string"
+ },
+ "value": {
+ "$ref": "#/definitions/PropertyFilterValue"
+ }
+ },
+ "required": ["key", "operator", "type"],
+ "type": "object"
+ },
+ "MatchedRecording": {
+ "additionalProperties": false,
+ "properties": {
+ "events": {
+ "items": {
+ "$ref": "#/definitions/MatchedRecordingEvent"
+ },
+ "type": "array"
+ },
+ "session_id": {
+ "type": "string"
+ }
+ },
+ "required": ["events"],
+ "type": "object"
+ },
+ "MatchedRecordingEvent": {
+ "additionalProperties": false,
+ "properties": {
+ "uuid": {
+ "type": "string"
+ }
+ },
+ "required": ["uuid"],
+ "type": "object"
+ },
"MathType": {
"anyOf": [
{
@@ -5944,6 +6138,32 @@
}
]
},
+ "MinimalHedgehogConfig": {
+ "additionalProperties": false,
+ "properties": {
+ "accessories": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ "color": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/HedgehogColorOptions"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "use_as_profile": {
+ "type": "boolean"
+ }
+ },
+ "required": ["use_as_profile", "color", "accessories"],
+ "type": "object"
+ },
"MultipleBreakdownOptions": {
"additionalProperties": false,
"properties": {
@@ -5977,6 +6197,7 @@
"FunnelsActorsQuery",
"FunnelCorrelationActorsQuery",
"SessionsTimelineQuery",
+ "RecordingsQuery",
"SessionAttributionExplorerQuery",
"ErrorTrackingQuery",
"DataTableNode",
@@ -6266,8 +6487,40 @@
"required": ["key", "operator", "type"],
"type": "object"
},
+ "PersonType": {
+ "additionalProperties": false,
+ "properties": {
+ "created_at": {
+ "type": "string"
+ },
+ "distinct_ids": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ "id": {
+ "type": "string"
+ },
+ "is_identified": {
+ "type": "boolean"
+ },
+ "name": {
+ "type": "string"
+ },
+ "properties": {
+ "type": "object"
+ },
+ "uuid": {
+ "type": "string"
+ }
+ },
+ "required": ["distinct_ids", "properties"],
+ "type": "object"
+ },
"PersonsNode": {
"additionalProperties": false,
+ "deprecated": "Use `ActorsQuery` instead.",
"properties": {
"cohort": {
"type": "integer"
@@ -6323,6 +6576,7 @@
"session",
"cohort",
"recording",
+ "log_entry",
"group",
"hogql",
"data_warehouse",
@@ -8039,14 +8293,6 @@
{
"$ref": "#/definitions/DurationType"
},
- {
- "const": "console_log_level",
- "type": "string"
- },
- {
- "const": "console_log_query",
- "type": "string"
- },
{
"const": "snapshot_source",
"type": "string"
@@ -8074,6 +8320,112 @@
"required": ["key", "operator", "type"],
"type": "object"
},
+ "RecordingsQuery": {
+ "additionalProperties": false,
+ "properties": {
+ "actions": {
+ "items": {
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "console_log_filters": {
+ "items": {
+ "$ref": "#/definitions/LogEntryPropertyFilter"
+ },
+ "type": "array"
+ },
+ "date_from": {
+ "type": ["string", "null"]
+ },
+ "date_to": {
+ "type": ["string", "null"]
+ },
+ "events": {
+ "items": {
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "filter_test_accounts": {
+ "type": "boolean"
+ },
+ "having_predicates": {
+ "items": {
+ "$ref": "#/definitions/AnyPropertyFilter"
+ },
+ "type": "array"
+ },
+ "kind": {
+ "const": "RecordingsQuery",
+ "type": "string"
+ },
+ "limit": {
+ "type": "integer"
+ },
+ "modifiers": {
+ "$ref": "#/definitions/HogQLQueryModifiers",
+ "description": "Modifiers used when performing the query"
+ },
+ "offset": {
+ "type": "integer"
+ },
+ "operand": {
+ "$ref": "#/definitions/FilterLogicalOperator"
+ },
+ "order": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/DurationType"
+ },
+ {
+ "const": "start_time",
+ "type": "string"
+ },
+ {
+ "const": "console_error_count",
+ "type": "string"
+ }
+ ]
+ },
+ "person_uuid": {
+ "type": "string"
+ },
+ "properties": {
+ "items": {
+ "$ref": "#/definitions/AnyPropertyFilter"
+ },
+ "type": "array"
+ },
+ "response": {
+ "$ref": "#/definitions/RecordingsQueryResponse"
+ },
+ "session_ids": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ }
+ },
+ "required": ["kind", "order"],
+ "type": "object"
+ },
+ "RecordingsQueryResponse": {
+ "additionalProperties": false,
+ "properties": {
+ "has_next": {
+ "type": "boolean"
+ },
+ "results": {
+ "items": {
+ "$ref": "#/definitions/SessionRecordingType"
+ },
+ "type": "array"
+ }
+ },
+ "required": ["results", "has_next"],
+ "type": "object"
+ },
"RefreshType": {
"anyOf": [
{
@@ -8597,6 +8949,88 @@
"required": ["key", "operator", "type"],
"type": "object"
},
+ "SessionRecordingType": {
+ "additionalProperties": false,
+ "properties": {
+ "active_seconds": {
+ "type": "number"
+ },
+ "click_count": {
+ "type": "number"
+ },
+ "console_error_count": {
+ "type": "number"
+ },
+ "console_log_count": {
+ "type": "number"
+ },
+ "console_warn_count": {
+ "type": "number"
+ },
+ "distinct_id": {
+ "type": "string"
+ },
+ "email": {
+ "type": "string"
+ },
+ "end_time": {
+ "description": "When the recording ends in ISO format.",
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "inactive_seconds": {
+ "type": "number"
+ },
+ "keypress_count": {
+ "type": "number"
+ },
+ "matching_events": {
+ "description": "List of matching events. *",
+ "items": {
+ "$ref": "#/definitions/MatchedRecording"
+ },
+ "type": "array"
+ },
+ "mouse_activity_count": {
+ "description": "count of all mouse activity in the recording, not just clicks",
+ "type": "number"
+ },
+ "person": {
+ "$ref": "#/definitions/PersonType"
+ },
+ "recording_duration": {
+ "description": "Length of recording in seconds.",
+ "type": "number"
+ },
+ "snapshot_source": {
+ "enum": ["web", "mobile", "unknown"],
+ "type": "string"
+ },
+ "start_time": {
+ "description": "When the recording starts in ISO format.",
+ "type": "string"
+ },
+ "start_url": {
+ "type": "string"
+ },
+ "storage": {
+ "description": "Where this recording information was loaded from",
+ "enum": ["object_storage_lts", "object_storage"],
+ "type": "string"
+ },
+ "summary": {
+ "type": "string"
+ },
+ "viewed": {
+ "description": "Whether this recording has been viewed already.",
+ "type": "boolean"
+ }
+ },
+ "required": ["id", "viewed", "recording_duration", "start_time", "end_time", "snapshot_source"],
+ "type": "object"
+ },
"SessionsTimelineQuery": {
"additionalProperties": false,
"properties": {
@@ -8870,6 +9304,7 @@
"session_properties",
"hogql_expression",
"notebooks",
+ "log_entries",
"replay"
],
"type": "string"
@@ -9235,6 +9670,35 @@
"required": ["results"],
"type": "object"
},
+ "UserBasicType": {
+ "additionalProperties": false,
+ "properties": {
+ "distinct_id": {
+ "type": "string"
+ },
+ "email": {
+ "type": "string"
+ },
+ "first_name": {
+ "type": "string"
+ },
+ "hedgehog_config": {
+ "$ref": "#/definitions/MinimalHedgehogConfig"
+ },
+ "id": {
+ "type": "number"
+ },
+ "is_email_verified": {},
+ "last_name": {
+ "type": "string"
+ },
+ "uuid": {
+ "type": "string"
+ }
+ },
+ "required": ["distinct_id", "email", "first_name", "id", "uuid"],
+ "type": "object"
+ },
"VizSpecificOptions": {
"additionalProperties": false,
"description": "Chart specific rendering options. Use ChartRenderingMetadata for non-serializable values, e.g. onClick handlers",
diff --git a/frontend/src/queries/schema.ts b/frontend/src/queries/schema.ts
index a0ba5618fbbcc..ee85f362b7472 100644
--- a/frontend/src/queries/schema.ts
+++ b/frontend/src/queries/schema.ts
@@ -9,8 +9,10 @@ import {
ChartDisplayCategory,
ChartDisplayType,
CountPerActorMathType,
+ DurationType,
EventPropertyFilter,
EventType,
+ FilterLogicalOperator,
FilterType,
FunnelsFilterType,
GroupMathType,
@@ -20,14 +22,17 @@ import {
IntervalType,
LifecycleFilterType,
LifecycleToggle,
+ LogEntryPropertyFilter,
PathsFilterType,
PersonPropertyFilter,
PropertyGroupFilter,
PropertyMathType,
RetentionFilterType,
SessionPropertyFilter,
+ SessionRecordingType,
StickinessFilterType,
TrendsFilterType,
+ UserBasicType,
} from '~/types'
export { ChartDisplayCategory }
@@ -63,6 +68,7 @@ export enum NodeKind {
FunnelsActorsQuery = 'FunnelsActorsQuery',
FunnelCorrelationActorsQuery = 'FunnelCorrelationActorsQuery',
SessionsTimelineQuery = 'SessionsTimelineQuery',
+ RecordingsQuery = 'RecordingsQuery',
SessionAttributionExplorerQuery = 'SessionAttributionExplorerQuery',
ErrorTrackingQuery = 'ErrorTrackingQuery',
@@ -202,9 +208,9 @@ export interface HogQLQueryModifiers {
debug?: boolean
s3TableUseInvalidColumns?: boolean
personsJoinMode?: 'inner' | 'left'
- bounceRatePageViewMode?: 'count_pageviews' | 'uniq_urls'
+ bounceRatePageViewMode?: 'count_pageviews' | 'uniq_urls' | 'uniq_page_screen_autocaptures'
sessionTableVersion?: 'auto' | 'v1' | 'v2'
- propertyGroupsMode?: 'enabled' | 'disabled'
+ propertyGroupsMode?: 'enabled' | 'disabled' | 'optimized'
}
export interface DataWarehouseEventsModifier {
@@ -264,6 +270,29 @@ export interface HogQuery extends DataNode