Skip to content

Commit

Permalink
feat(replay): add "what to watch" screen (#25717)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Paul D'Ambra <[email protected]>
  • Loading branch information
3 people authored Oct 22, 2024
1 parent 859f722 commit 45257c1
Show file tree
Hide file tree
Showing 20 changed files with 737 additions and 57 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ import React from 'react'

import { cohortsModel } from '~/models/cohortsModel'
import { propertyDefinitionsModel } from '~/models/propertyDefinitionsModel'
import { ActionFilter, AnyPropertyFilter } from '~/types'
import { ActionFilter, AnyPropertyFilter, FeaturePropertyFilter, UniversalFilterValue } from '~/types'

import { EntityFilterInfo } from '../EntityFilterInfo'
import { formatPropertyLabel } from '../PropertyFilters/utils'
import { UniversalFilterValue } from './UniversalFilters'
import { isActionFilter, isEditableFilter, isEventFilter } from './utils'
import { isActionFilter, isEditableFilter, isEventFilter, isFeatureFlagFilter } from './utils'

export interface UniversalFilterButtonProps {
onClick?: () => void
Expand All @@ -33,7 +32,7 @@ export const UniversalFilterButton = React.forwardRef<HTMLElement, UniversalFilt
const isEditable = isEditableFilter(filter)
const isAction = isActionFilter(filter)
const isEvent = isEventFilter(filter)

const isFeatureFlag = isFeatureFlagFilter(filter)
const button = (
<div
ref={ref as any}
Expand All @@ -48,6 +47,8 @@ export const UniversalFilterButton = React.forwardRef<HTMLElement, UniversalFilt
<EventLabel filter={filter} onClick={onClick} />
) : isAction ? (
<EntityFilterInfo filter={filter} />
) : isFeatureFlag ? (
<FeatureFlagLabel filter={filter} />
) : (
<PropertyLabel filter={filter} />
)}
Expand Down Expand Up @@ -116,3 +117,7 @@ const EventLabel = ({
</div>
)
}

const FeatureFlagLabel = ({ filter }: { filter: FeaturePropertyFilter }): JSX.Element => {
return <div>{filter.key}</div>
}
12 changes: 2 additions & 10 deletions frontend/src/lib/components/UniversalFilters/UniversalFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { LemonButton, LemonButtonProps, LemonDropdown, Popover } from '@posthog/
import { BindLogic, useActions, useValues } from 'kea'
import { useState } from 'react'

import { ActionFilter, AnyPropertyFilter, FilterLogicalOperator } from '~/types'
import { UniversalFiltersGroup, UniversalFilterValue } from '~/types'

import { TaxonomicPropertyFilter } from '../PropertyFilters/components/TaxonomicPropertyFilter'
import { PropertyFilters } from '../PropertyFilters/PropertyFilters'
Expand All @@ -14,14 +14,6 @@ import { UniversalFilterButton } from './UniversalFilterButton'
import { universalFiltersLogic } from './universalFiltersLogic'
import { isEditableFilter, isEventFilter } from './utils'

export interface UniversalFiltersGroup {
type: FilterLogicalOperator
values: UniversalFiltersGroupValue[]
}

export type UniversalFiltersGroupValue = UniversalFiltersGroup | UniversalFilterValue
export type UniversalFilterValue = AnyPropertyFilter | ActionFilter

type UniversalFiltersProps = {
rootKey: string
group: UniversalFiltersGroup | null
Expand Down Expand Up @@ -160,7 +152,7 @@ const AddFilterButton = (props: Omit<LemonButtonProps, 'onClick' | 'sideAction'
onClick={() => setDropdownOpen(!dropdownOpen)}
{...props}
>
Add filter
{props?.title || 'Add filter'}
</LemonButton>
</LemonDropdown>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { expectLogic } from 'kea-test-utils'

import { initKeaTests } from '~/test/init'
import { AnyPropertyFilter, FilterLogicalOperator, PropertyFilterType, PropertyOperator } from '~/types'
import {
AnyPropertyFilter,
FilterLogicalOperator,
PropertyFilterType,
PropertyOperator,
UniversalFiltersGroup,
} from '~/types'

import { TaxonomicFilterGroup, TaxonomicFilterGroupType } from '../TaxonomicFilter/types'
import { UniversalFiltersGroup } from './UniversalFilters'
import { universalFiltersLogic } from './universalFiltersLogic'

const propertyFilter: AnyPropertyFilter = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@ import {
import { taxonomicFilterGroupTypeToEntityType } from 'scenes/insights/filters/ActionFilter/ActionFilterRow/ActionFilterRow'

import { propertyDefinitionsModel } from '~/models/propertyDefinitionsModel'
import { ActionFilter, FilterLogicalOperator, PropertyFilterType } from '~/types'
import {
ActionFilter,
FeaturePropertyFilter,
FilterLogicalOperator,
PropertyFilterType,
PropertyOperator,
UniversalFiltersGroup,
UniversalFiltersGroupValue,
} from '~/types'

import { TaxonomicFilterGroup, TaxonomicFilterGroupType, TaxonomicFilterValue } from '../TaxonomicFilter/types'
import { UniversalFiltersGroup, UniversalFiltersGroupValue } from './UniversalFilters'
import type { universalFiltersLogicType } from './universalFiltersLogicType'

export const DEFAULT_UNIVERSAL_GROUP_FILTER: UniversalFiltersGroup = {
Expand Down Expand Up @@ -52,7 +59,7 @@ export const universalFiltersLogic = kea<universalFiltersLogicType>([
addGroupFilter: (
taxonomicGroup: TaxonomicFilterGroup,
propertyKey: TaxonomicFilterValue,
item: { propertyFilterType?: PropertyFilterType; name?: string }
item: { propertyFilterType?: PropertyFilterType; name?: string; key?: string }
) => ({
taxonomicGroup,
propertyKey,
Expand Down Expand Up @@ -98,6 +105,7 @@ export const universalFiltersLogic = kea<universalFiltersLogicType>([
TaxonomicFilterGroupType.Cohorts,
TaxonomicFilterGroupType.Elements,
TaxonomicFilterGroupType.HogQLExpression,
TaxonomicFilterGroupType.FeatureFlags,
].includes(t)
),
],
Expand All @@ -112,26 +120,39 @@ export const universalFiltersLogic = kea<universalFiltersLogicType>([
addGroupFilter: ({ taxonomicGroup, propertyKey, item }) => {
const newValues = [...values.filterGroup.values]

const propertyType = item.propertyFilterType ?? taxonomicFilterTypeToPropertyFilterType(taxonomicGroup.type)
if (propertyKey && propertyType) {
const newPropertyFilter = createDefaultPropertyFilter(
{},
propertyKey,
propertyType,
taxonomicGroup,
values.describeProperty
)
newValues.push(newPropertyFilter)
if (taxonomicGroup.type === TaxonomicFilterGroupType.FeatureFlags) {
if (!item.key) {
return
}
const newFeatureFlagFilter: FeaturePropertyFilter = {
type: PropertyFilterType.Feature,
key: item.key,
operator: PropertyOperator.Exact,
}
newValues.push(newFeatureFlagFilter)
} else {
const entityType = taxonomicFilterGroupTypeToEntityType(taxonomicGroup.type)
if (entityType) {
const newEntityFilter: ActionFilter = {
id: propertyKey,
name: item?.name ?? '',
type: entityType,
}
const propertyType =
item.propertyFilterType ?? taxonomicFilterTypeToPropertyFilterType(taxonomicGroup.type)
if (propertyKey && propertyType) {
const newPropertyFilter = createDefaultPropertyFilter(
{},
propertyKey,
propertyType,
taxonomicGroup,
values.describeProperty
)
newValues.push(newPropertyFilter)
} else {
const entityType = taxonomicFilterGroupTypeToEntityType(taxonomicGroup.type)
if (entityType) {
const newEntityFilter: ActionFilter = {
id: propertyKey,
name: item?.name ?? '',
type: entityType,
}

newValues.push(newEntityFilter)
newValues.push(newEntityFilter)
}
}
}
actions.setGroupValues(newValues)
Expand Down
15 changes: 13 additions & 2 deletions frontend/src/lib/components/UniversalFilters/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { ActionFilter, FilterLogicalOperator, LogEntryPropertyFilter, RecordingPropertyFilter } from '~/types'
import {
ActionFilter,
FeaturePropertyFilter,
FilterLogicalOperator,
LogEntryPropertyFilter,
RecordingPropertyFilter,
UniversalFiltersGroup,
UniversalFiltersGroupValue,
UniversalFilterValue,
} from '~/types'

import { isCohortPropertyFilter } from '../PropertyFilters/utils'
import { UniversalFiltersGroup, UniversalFiltersGroupValue, UniversalFilterValue } from './UniversalFilters'

export function isUniversalGroupFilterLike(filter?: UniversalFiltersGroupValue): filter is UniversalFiltersGroup {
return filter?.type === FilterLogicalOperator.And || filter?.type === FilterLogicalOperator.Or
Expand All @@ -15,6 +23,9 @@ export function isEventFilter(filter: UniversalFilterValue): filter is ActionFil
export function isActionFilter(filter: UniversalFilterValue): filter is ActionFilter {
return filter.type === 'actions'
}
export function isFeatureFlagFilter(filter: UniversalFilterValue): filter is FeaturePropertyFilter {
return filter.type === 'feature'
}
export function isRecordingPropertyFilter(filter: UniversalFilterValue): filter is RecordingPropertyFilter {
return filter.type === 'recording'
}
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ export const FEATURE_FLAGS = {
BILLING_PAYMENT_ENTRY_IN_APP: 'billing-payment-entry-in-app', // owner: @zach
LEGACY_ACTION_WEBHOOKS: 'legacy-action-webhooks', // owner: @mariusandra #team-cdp
SESSION_REPLAY_URL_TRIGGER: 'session-replay-url-trigger', // owner: @richard-better #team-replay
REPLAY_TEMPLATES: 'replay-templates', // owner: @raquelmsmith #team-replay
} as const
export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS]

Expand Down
23 changes: 22 additions & 1 deletion frontend/src/lib/lemon-ui/LemonCard/LemonCard.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import './LemonCard.scss'

import { IconX } from '@posthog/icons'

import { LemonButton } from '../LemonButton'

export interface LemonCardProps {
hoverEffect?: boolean
className?: string
children?: React.ReactNode
onClick?: () => void
focused?: boolean
'data-attr'?: string
closeable?: boolean
onClose?: () => void
}

export function LemonCard({
Expand All @@ -15,16 +21,31 @@ export function LemonCard({
children,
onClick,
focused,
closeable,
onClose,
...props
}: LemonCardProps): JSX.Element {
return (
<div
className={`LemonCard ${hoverEffect && 'LemonCard--hoverEffect'} border ${
focused ? 'border-2 border-primary' : 'border-border'
} rounded p-6 bg-bg-light ${className}`}
} rounded p-6 bg-bg-light ${onClick && !focused ? 'cursor-pointer' : ''} ${className}`}
onClick={onClick}
{...props}
>
{closeable ? (
<div className="absolute top-2 right-2">
<LemonButton
icon={<IconX />}
onClick={(e) => {
e.stopPropagation()
onClose?.()
}}
type="tertiary"
size="xsmall"
/>
</div>
) : null}
{children}
</div>
)
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/scenes/error-tracking/errorTrackingLogic.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type { LemonSegmentedButtonOption } from '@posthog/lemon-ui'
import { actions, connect, kea, listeners, path, reducers, selectors } from 'kea'
import { UniversalFiltersGroup } from 'lib/components/UniversalFilters/UniversalFilters'
import { FEATURE_FLAGS } from 'lib/constants'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'

import { DateRange } from '~/queries/schema'
import { FilterLogicalOperator } from '~/types'
import { FilterLogicalOperator, UniversalFiltersGroup } from '~/types'

import type { errorTrackingLogicType } from './errorTrackingLogicType'

Expand Down
3 changes: 1 addition & 2 deletions frontend/src/scenes/error-tracking/queries.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { UniversalFiltersGroup } from 'lib/components/UniversalFilters/UniversalFilters'
import { dayjs } from 'lib/dayjs'
import { range } from 'lib/utils'

Expand All @@ -11,7 +10,7 @@ import {
InsightVizNode,
NodeKind,
} from '~/queries/schema'
import { AnyPropertyFilter, BaseMathType, ChartDisplayType, PropertyGroupFilter } from '~/types'
import { AnyPropertyFilter, BaseMathType, ChartDisplayType, PropertyGroupFilter, UniversalFiltersGroup } from '~/types'

export type SparklineConfig = {
value: number
Expand Down
16 changes: 13 additions & 3 deletions frontend/src/scenes/session-recordings/SessionRecordings.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IconEllipsis, IconGear } from '@posthog/icons'
import { LemonButton, LemonMenu } from '@posthog/lemon-ui'
import { LemonBadge, LemonButton, LemonMenu } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { router } from 'kea-router'
import { authorizedUrlListLogic, AuthorizedUrlListType } from 'lib/components/AuthorizedUrlList/authorizedUrlListLogic'
Expand All @@ -26,6 +26,7 @@ import { SessionRecordingsPlaylist } from './playlist/SessionRecordingsPlaylist'
import { SavedSessionRecordingPlaylists } from './saved-playlists/SavedSessionRecordingPlaylists'
import { savedSessionRecordingPlaylistsLogic } from './saved-playlists/savedSessionRecordingPlaylistsLogic'
import { humanFriendlyTabName, sessionReplaySceneLogic } from './sessionReplaySceneLogic'
import SessionRecordingTemplates from './templates/SessionRecordingTemplates'

function Header(): JSX.Element {
const { guardAvailableFeature } = useValues(upgradeModalLogic)
Expand Down Expand Up @@ -192,21 +193,30 @@ function MainPanel(): JSX.Element {
<SavedSessionRecordingPlaylists tab={ReplayTabs.Playlists} />
) : tab === ReplayTabs.Errors ? (
<SessionRecordingErrors />
) : tab === ReplayTabs.Templates ? (
<SessionRecordingTemplates />
) : null}
</div>
)
}

function PageTabs(): JSX.Element {
const { tab, tabs } = useValues(sessionReplaySceneLogic)
const { tab, tabs, shouldShowNewBadge } = useValues(sessionReplaySceneLogic)

return (
<LemonTabs
activeKey={tab}
onChange={(t) => router.actions.push(urls.replay(t as ReplayTabs))}
tabs={tabs.map((replayTab) => {
return {
label: humanFriendlyTabName(replayTab),
label: (
<>
{humanFriendlyTabName(replayTab)}
{replayTab === ReplayTabs.Templates && shouldShowNewBadge && (
<LemonBadge className="ml-1" size="small" />
)}
</>
),
key: replayTab,
}
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import clsx from 'clsx'
import { useActions, useMountedLogic, useValues } from 'kea'
import { DateFilter } from 'lib/components/DateFilter/DateFilter'
import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types'
import UniversalFilters, { UniversalFiltersGroup } from 'lib/components/UniversalFilters/UniversalFilters'
import UniversalFilters from 'lib/components/UniversalFilters/UniversalFilters'
import { universalFiltersLogic } from 'lib/components/UniversalFilters/universalFiltersLogic'
import { isUniversalGroupFilterLike } from 'lib/components/UniversalFilters/utils'
import { useEffect, useState } from 'react'
Expand All @@ -11,7 +11,7 @@ import { TestAccountFilter } from 'scenes/insights/filters/TestAccountFilter'
import { actionsModel } from '~/models/actionsModel'
import { cohortsModel } from '~/models/cohortsModel'
import { AndOrFilterSelect } from '~/queries/nodes/InsightViz/PropertyGroupFilters/AndOrFilterSelect'
import { RecordingUniversalFilters } from '~/types'
import { RecordingUniversalFilters, UniversalFiltersGroup } from '~/types'

import { DurationFilter } from './DurationFilter'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { router } from 'kea-router'
import api from 'lib/api'
import { convertPropertyGroupToProperties, isValidPropertyFilter } from 'lib/components/PropertyFilters/utils'
import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types'
import { UniversalFilterValue } from 'lib/components/UniversalFilters/UniversalFilters'
import { isActionFilter, isEventFilter } from 'lib/components/UniversalFilters/utils'
import { lemonToast } from 'lib/lemon-ui/LemonToast/LemonToast'
import { getCoreFilterDefinition } from 'lib/taxonomy'
Expand All @@ -16,7 +15,7 @@ import { PLAYLIST_LIMIT_REACHED_MESSAGE } from 'scenes/session-recordings/sessio
import { urls } from 'scenes/urls'

import { cohortsModelType } from '~/models/cohortsModelType'
import { PropertyOperator, SessionRecordingPlaylistType } from '~/types'
import { PropertyOperator, SessionRecordingPlaylistType, UniversalFilterValue } from '~/types'

function getOperatorSymbol(operator: PropertyOperator | null): string {
if (!operator) {
Expand Down
Loading

0 comments on commit 45257c1

Please sign in to comment.