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

feat(flags): Add relative date operators #19792

Merged
merged 26 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
height: 1.6875rem;
}

.TaxonomicPropertyFilter & {
height: 2.2rem;
}

&:hover {
background-color: var(--mid);
}
Expand Down
20 changes: 15 additions & 5 deletions frontend/src/lib/components/DateFilter/RollingDateRangeFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,21 @@ const dateOptions: LemonSelectOptions<DateOption> = [
]

type RollingDateRangeFilterProps = {
pageKey?: string
selected?: boolean
dateFrom?: string | null | dayjs.Dayjs
max?: number | null
onChange?: (fromDate: string) => void
makeLabel?: (key: React.ReactNode) => React.ReactNode
makeLabel?: (key: React.ReactNode, startOfRange?: React.ReactNode) => React.ReactNode
popover?: {
ref?: React.MutableRefObject<HTMLDivElement | null>
}
dateRangeFilterLabel?: string
/* By default, we only update default counter values when selected, because that's the preferred way in insights.
* However, in some cases, we want to update the default counter values even when not selected, like
* when inside a TaxonomicFilter component.
*/
forceUpdateDefaults?: boolean
}

export function RollingDateRangeFilter({
Expand All @@ -32,21 +39,24 @@ export function RollingDateRangeFilter({
dateFrom,
selected,
max,
dateRangeFilterLabel = 'In the last',
forceUpdateDefaults,
pageKey,
}: RollingDateRangeFilterProps): JSX.Element {
const logicProps = { onChange, dateFrom, selected, max }
const logicProps = { onChange, dateFrom, selected, max, forceUpdateDefaults, pageKey }
const { increaseCounter, decreaseCounter, setCounter, setDateOption, toggleDateOptionsSelector, select } =
useActions(rollingDateRangeFilterLogic(logicProps))
const { counter, dateOption, formattedDate } = useValues(rollingDateRangeFilterLogic(logicProps))
const { counter, dateOption, formattedDate, startOfDateRange } = useValues(rollingDateRangeFilterLogic(logicProps))

return (
<Tooltip title={makeLabel ? makeLabel(formattedDate) : undefined}>
<Tooltip title={makeLabel ? makeLabel(formattedDate, startOfDateRange) : undefined}>
<LemonButton
className="RollingDateRangeFilter"
data-attr="rolling-date-range-filter"
onClick={select}
active={selected}
>
<p className="RollingDateRangeFilter__label">In the last</p>
<p className="RollingDateRangeFilter__label">{dateRangeFilterLabel}</p>
<div className="RollingDateRangeFilter__counter" onClick={(e): void => e.stopPropagation()}>
<span
className="RollingDateRangeFilter__counter__step"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import './RollingDateRangeFilter.scss'

import { actions, kea, listeners, path, props, reducers, selectors } from 'kea'
import { actions, kea, key, listeners, path, props, propsChanged, reducers, selectors } from 'kea'
import { Dayjs } from 'lib/dayjs'
import { dateFilterToText } from 'lib/utils'

Expand All @@ -20,10 +20,17 @@ export type RollingDateFilterLogicPropsType = {
onChange?: (fromDate: string) => void
dateFrom?: Dayjs | string | null
max?: number | null
forceUpdateDefaults?: boolean
pageKey?: string
}

const counterDefault = (selected: boolean | undefined, dateFrom: Dayjs | string | null | undefined): number => {
if (selected && dateFrom && typeof dateFrom === 'string') {
const counterDefault = (
selected: boolean | undefined,
shouldUpdate: boolean | undefined,
dateFrom: Dayjs | string | null | undefined
): number => {
const shouldUpdateDefaults = shouldUpdate ?? selected
if (shouldUpdateDefaults && dateFrom && typeof dateFrom === 'string') {
const counter = parseInt(dateFrom.slice(1, -1))
if (counter) {
return counter
Expand All @@ -32,8 +39,14 @@ const counterDefault = (selected: boolean | undefined, dateFrom: Dayjs | string
return 3
}

const dateOptionDefault = (selected: boolean | undefined, dateFrom: Dayjs | string | null | undefined): string => {
if (selected && dateFrom && typeof dateFrom === 'string') {
const dateOptionDefault = (
selected: boolean | undefined,
shouldUpdate: boolean | undefined,
dateFrom: Dayjs | string | null | undefined
): string => {
const shouldUpdateDefaults = shouldUpdate ?? selected

if (shouldUpdateDefaults && dateFrom && typeof dateFrom === 'string') {
const dateOption = dateOptionsMap[dateFrom.slice(-1)]
if (dateOption) {
return dateOption
Expand All @@ -44,6 +57,8 @@ const dateOptionDefault = (selected: boolean | undefined, dateFrom: Dayjs | stri

export const rollingDateRangeFilterLogic = kea<rollingDateRangeFilterLogicType>([
path(['lib', 'components', 'DateFilter', 'RollingDateRangeFilterLogic']),
props({} as RollingDateFilterLogicPropsType),
key(({ pageKey }) => pageKey ?? 'unknown'),
actions({
increaseCounter: true,
decreaseCounter: true,
Expand All @@ -52,10 +67,9 @@ export const rollingDateRangeFilterLogic = kea<rollingDateRangeFilterLogicType>(
toggleDateOptionsSelector: true,
select: true,
}),
props({} as RollingDateFilterLogicPropsType),
reducers(({ props }) => ({
counter: [
counterDefault(props.selected, props.dateFrom) as number | null,
counterDefault(props.selected, props.forceUpdateDefaults, props.dateFrom) as number | null,
{
increaseCounter: (state) => (state ? (!props.max || state < props.max ? state + 1 : state) : 1),
decreaseCounter: (state) => {
Expand All @@ -69,7 +83,7 @@ export const rollingDateRangeFilterLogic = kea<rollingDateRangeFilterLogicType>(
},
],
dateOption: [
dateOptionDefault(props.selected, props.dateFrom),
dateOptionDefault(props.selected, props.forceUpdateDefaults, props.dateFrom),
{
setDateOption: (_, { option }) => option,
},
Expand Down Expand Up @@ -106,7 +120,21 @@ export const rollingDateRangeFilterLogic = kea<rollingDateRangeFilterLogicType>(
return dateFilterToText(value, undefined, 'Custom rolling range', [], true)
},
],
startOfDateRange: [
(s) => [s.value],
(value: string) => {
return dateFilterToText(value, undefined, 'Custom rolling range', [], false, 'MMMM D, YYYY', true)
},
],
})),
propsChanged(({ actions, props }, oldProps) => {
// TRICKY: This forces prop updates to update the counter as well, so we aren't stuck with old values
// in the counter
if (props.dateFrom !== oldProps.dateFrom && props.forceUpdateDefaults) {
actions.setCounter(counterDefault(props.selected, props.forceUpdateDefaults, props.dateFrom))
actions.setDateOption(dateOptionDefault(props.selected, props.forceUpdateDefaults, props.dateFrom))
}
}),
listeners(({ props, values }) => ({
select: () => {
props.onChange?.(values.value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import './PropertyFilters.scss'

import { BindLogic, useActions, useValues } from 'kea'
import { TaxonomicPropertyFilter } from 'lib/components/PropertyFilters/components/TaxonomicPropertyFilter'
import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types'
import { TaxonomicFilterGroupType, TaxonomicFilterProps } from 'lib/components/TaxonomicFilter/types'
import React, { useEffect } from 'react'
import { LogicalRowDivider } from 'scenes/cohorts/CohortFilters/CohortCriteriaRowBuilder'

Expand All @@ -20,6 +20,7 @@ interface PropertyFiltersProps {
showConditionBadge?: boolean
disablePopover?: boolean
taxonomicGroupTypes?: TaxonomicFilterGroupType[]
taxonomicFilterOptionsFromProp?: TaxonomicFilterProps['optionsFromProp']
metadataSource?: AnyDataNode
showNestedArrow?: boolean
eventNames?: string[]
Expand All @@ -32,6 +33,7 @@ interface PropertyFiltersProps {
allowNew?: boolean
errorMessages?: JSX.Element[] | null
propertyAllowList?: { [key in TaxonomicFilterGroupType]?: string[] }
allowRelativeDateOperators?: boolean
}

export function PropertyFilters({
Expand All @@ -41,6 +43,7 @@ export function PropertyFilters({
showConditionBadge = false,
disablePopover = false, // use bare PropertyFilter without popover
taxonomicGroupTypes,
taxonomicFilterOptionsFromProp,
metadataSource,
showNestedArrow = false,
eventNames = [],
Expand All @@ -53,6 +56,7 @@ export function PropertyFilters({
allowNew = true,
errorMessages = null,
propertyAllowList,
allowRelativeDateOperators,
}: PropertyFiltersProps): JSX.Element {
const logicProps = { propertyFilters, onChange, pageKey, sendAllKeyUpdates }
const { filters, filtersWithNew } = useValues(propertyFilterLogic(logicProps))
Expand Down Expand Up @@ -109,6 +113,8 @@ export function PropertyFilters({
placement: pageKey === 'insight-filters' ? 'bottomLeft' : undefined,
}}
propertyAllowList={propertyAllowList}
taxonomicFilterOptionsFromProp={taxonomicFilterOptionsFromProp}
allowRelativeDateOperators={allowRelativeDateOperators}
/>
)}
errorMessage={errorMessages && errorMessages[index]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ export function OperatorValueWithDateTimeProperty(): JSX.Element {
)
}

export function OperatorValueWithRelativeDateTimeProperty(): JSX.Element {
return (
<>
<h1>Relative Date Time Property</h1>
<OperatorValueSelect {...props(PropertyType.DateTime)} addRelativeDateTimeOperators />
</>
)
}

export function OperatorValueWithNumericProperty(): JSX.Element {
return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface OperatorValueSelectProps {
eventNames?: string[]
propertyDefinitions: PropertyDefinition[]
defaultOpen?: boolean
addRelativeDateTimeOperators?: boolean
}

interface OperatorSelectProps extends Omit<LemonSelectProps<any>, 'options'> {
Expand Down Expand Up @@ -67,6 +68,7 @@ export function OperatorValueSelect({
propertyDefinitions = [],
eventNames = [],
defaultOpen,
addRelativeDateTimeOperators,
}: OperatorValueSelectProps): JSX.Element {
const propertyDefinition = propertyDefinitions.find((pd) => pd.name === propkey)

Expand All @@ -82,7 +84,8 @@ export function OperatorValueSelect({
useEffect(() => {
const limitedElementProperty = propkey === 'selector' || propkey === 'tag_name'
const operatorMapping: Record<string, string> = chooseOperatorMap(
limitedElementProperty ? PropertyType.Selector : propertyDefinition?.property_type
limitedElementProperty ? PropertyType.Selector : propertyDefinition?.property_type,
addRelativeDateTimeOperators
)
const operators = Object.keys(operatorMapping) as Array<PropertyOperator>
setOperators(operators)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ import './PropertyValue.scss'

import { AutoComplete } from 'antd'
import clsx from 'clsx'
import { useActions, useValues } from 'kea'
import { useActions, useMountedLogic, useValues } from 'kea'
import { RollingDateRangeFilter } from 'lib/components/DateFilter/RollingDateRangeFilter'
import { DurationPicker } from 'lib/components/DurationPicker/DurationPicker'
import { PropertyFilterDatePicker } from 'lib/components/PropertyFilters/components/PropertyFilterDatePicker'
import { propertyFilterTypeToPropertyDefinitionType } from 'lib/components/PropertyFilters/utils'
import { LemonSelectMultiple } from 'lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple'
import { isOperatorDate, isOperatorFlag, isOperatorMulti, toString } from 'lib/utils'
import { isOperatorDate, isOperatorFlag, isOperatorMulti, isOperatorRelativeDate, toString } from 'lib/utils'
import { useEffect, useRef, useState } from 'react'

import { propertyDefinitionsModel } from '~/models/propertyDefinitionsModel'
import { PropertyFilterType, PropertyOperator, PropertyType } from '~/types'

import { propertyFilterLogic } from '../propertyFilterLogic'

export interface PropertyValueProps {
propertyKey: string
type: PropertyFilterType
Expand Down Expand Up @@ -56,8 +59,12 @@ export function PropertyValue({
const { formatPropertyValueForDisplay, describeProperty, options } = useValues(propertyDefinitionsModel)
const { loadPropertyValues } = useActions(propertyDefinitionsModel)

// Get key from bound logic props if present
const pageKey = useMountedLogic(propertyFilterLogic)?.props?.pageKey

const isMultiSelect = operator && isOperatorMulti(operator)
const isDateTimeProperty = operator && isOperatorDate(operator)
const isRelativeDateTimeProperty = operator && isOperatorRelativeDate(operator)
const propertyDefinitionType = propertyFilterTypeToPropertyDefinitionType(type)

const isDurationProperty =
Expand Down Expand Up @@ -195,6 +202,22 @@ export function PropertyValue({

return isDateTimeProperty ? (
<PropertyFilterDatePicker autoFocus={autoFocus} operator={operator} value={value} setValue={setValue} />
) : isRelativeDateTimeProperty ? (
<RollingDateRangeFilter
pageKey={pageKey}
dateRangeFilterLabel="the last"
// :TRICKY: This filter adds a default negative sign to the value, which we don't need
dateFrom={`-${String(value)}`}
onChange={(newValue) => setValue(newValue.slice(1))}
max={10000}
makeLabel={(_, startOfRange) => (
<span className="hide-when-small">
Matches all values {operator === PropertyOperator.IsRelativeDateBefore ? 'before' : 'after'}{' '}
{startOfRange}
</span>
)}
forceUpdateDefaults
/>
) : isDurationProperty ? (
<DurationPicker autoFocus={autoFocus} value={value as number} onChange={setValue} />
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export function TaxonomicPropertyFilter({
hasRowOperator,
metadataSource,
propertyAllowList,
taxonomicFilterOptionsFromProp,
allowRelativeDateOperators,
}: PropertyFilterInternalProps): JSX.Element {
const pageKey = useMemo(() => pageKeyInput || `filter-${uniqueMemoizedIndex++}`, [pageKeyInput])
const groupTypes = taxonomicGroupTypes || [
Expand Down Expand Up @@ -108,6 +110,7 @@ export function TaxonomicPropertyFilter({
metadataSource={metadataSource}
eventNames={eventNames}
propertyAllowList={propertyAllowList}
optionsFromProp={taxonomicFilterOptionsFromProp}
/>
)

Expand Down Expand Up @@ -194,6 +197,7 @@ export function TaxonomicPropertyFilter({
placeholder="Enter value..."
endpoint={filter?.key && activeTaxonomicGroup?.valuesEndpoint?.(filter.key)}
eventNames={eventNames}
addRelativeDateTimeOperators={allowRelativeDateOperators}
onChange={(newOperator, newValue) => {
if (filter?.key && filter?.type) {
setFilter(index, {
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lib/components/PropertyFilters/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SelectGradientOverflowProps } from 'lib/components/SelectGradientOverfl
import {
TaxonomicFilterGroup,
TaxonomicFilterGroupType,
TaxonomicFilterProps,
TaxonomicFilterValue,
} from 'lib/components/TaxonomicFilter/types'

Expand Down Expand Up @@ -39,11 +40,13 @@ export interface PropertyFilterInternalProps {
onComplete: () => void
disablePopover: boolean
taxonomicGroupTypes?: TaxonomicFilterGroupType[]
taxonomicFilterOptionsFromProp?: TaxonomicFilterProps['optionsFromProp']
eventNames?: string[]
propertyGroupType?: FilterLogicalOperator | null
orFiltering?: boolean
addText?: string | null
hasRowOperator?: boolean
metadataSource?: AnyDataNode
propertyAllowList?: { [key in TaxonomicFilterGroupType]?: string[] }
allowRelativeDateOperators?: boolean
}
2 changes: 1 addition & 1 deletion frontend/src/lib/components/TaxonomicFilter/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface TaxonomicFilterProps {
selectFirstItem?: boolean
/** use to filter results in a group by name, currently only working for EventProperties */
excludedProperties?: { [key in TaxonomicFilterGroupType]?: TaxonomicFilterValue[] }
propertyAllowList?: { [key in TaxonomicFilterGroupType]?: string[] } // only return properties in this list, currently only working for EventProperties
propertyAllowList?: { [key in TaxonomicFilterGroupType]?: string[] } // only return properties in this list, currently only working for EventProperties and PersonProperties
metadataSource?: AnyDataNode
}

Expand Down
5 changes: 5 additions & 0 deletions frontend/src/lib/taxonomy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,11 @@ export const KEY_MAPPING: KeyMappingInterface = {
examples: ['16ff262c4301e5-0aa346c03894bc-39667c0e-1aeaa0-16ff262c431767'],
system: true,
},
$current_distinct_id: {
neilkakkar marked this conversation as resolved.
Show resolved Hide resolved
label: 'Current Distinct ID',
description: 'The current distinct ID of the user',
examples: ['16ff262c4301e5-0aa346c03894bc-39667c0e-1aeaa0-16ff262c431767'],
},
$event_type: {
label: 'Event Type',
description:
Expand Down
Loading
Loading