diff --git a/frontend/src/lib/components/DateFilter/DateFilter.tsx b/frontend/src/lib/components/DateFilter/DateFilter.tsx index f68f68867d1ed..f82453d829a02 100644 --- a/frontend/src/lib/components/DateFilter/DateFilter.tsx +++ b/frontend/src/lib/components/DateFilter/DateFilter.tsx @@ -16,15 +16,16 @@ import { Tooltip } from 'lib/lemon-ui/Tooltip' import { dateFilterToText, dateMapping, uuid } from 'lib/utils' import { useRef } from 'react' -import { DateMappingOption } from '~/types' +import { DateMappingOption, PropertyOperator } from '~/types' +import { PropertyFilterDatePicker } from '../PropertyFilters/components/PropertyFilterDatePicker' import { dateFilterLogic } from './dateFilterLogic' import { RollingDateRangeFilter } from './RollingDateRangeFilter' export interface DateFilterProps { showCustom?: boolean showRollingRangePicker?: boolean - makeLabel?: (key: React.ReactNode) => React.ReactNode + makeLabel?: (key: React.ReactNode, startOfRange?: React.ReactNode) => React.ReactNode className?: string onChange?: (fromDate: string | null, toDate: string | null) => void disabled?: boolean @@ -32,6 +33,8 @@ export interface DateFilterProps { isDateFormatted?: boolean size?: LemonButtonProps['size'] dropdownPlacement?: Placement + /* True when we're not dealing with ranges, but a single date / relative date */ + isFixedDateMode?: boolean } interface RawDateFilterProps extends DateFilterProps { dateFrom?: string | null | dayjs.Dayjs @@ -53,6 +56,7 @@ export function DateFilter({ size, dropdownPlacement = 'bottom-start', max, + isFixedDateMode = false, }: RawDateFilterProps): JSX.Element { const key = useRef(uuid()).current const logicProps: DateFilterLogicProps = { @@ -62,11 +66,30 @@ export function DateFilter({ onChange, dateOptions, isDateFormatted, + isFixedDateMode, } - const { open, openFixedRange, openDateToNow, close, setRangeDateFrom, setRangeDateTo, setDate, applyRange } = - useActions(dateFilterLogic(logicProps)) - const { isVisible, view, rangeDateFrom, rangeDateTo, label, isFixedRange, isDateToNow, isRollingDateRange } = - useValues(dateFilterLogic(logicProps)) + const { + open, + openFixedRange, + openDateToNow, + openFixedDate, + close, + setRangeDateFrom, + setRangeDateTo, + setDate, + applyRange, + } = useActions(dateFilterLogic(logicProps)) + const { + isVisible, + view, + rangeDateFrom, + rangeDateTo, + label, + isFixedRange, + isDateToNow, + isFixedDate, + isRollingDateRange, + } = useValues(dateFilterLogic(logicProps)) const optionsRef = useRef(null) const rollingDateRangeRef = useRef(null) @@ -93,6 +116,15 @@ export function DateFilter({ }} onClose={open} /> + ) : view === DateFilterView.FixedDate ? ( + { + setDate(String(date), '') + }} + /> ) : (
e.stopPropagation()}> {dateOptions.map(({ key, values, inactive }) => { @@ -113,9 +145,18 @@ export function DateFilter({ dateOptions, isDateFormatted ) + const startOfRangeDateValue = dateFilterToText( + values[0], + undefined, + '', + [], + false, + 'MMMM D, YYYY', + true + ) return ( - + setDate(values[0] || null, values[1] || null)} @@ -129,7 +170,9 @@ export function DateFilter({ })} {showRollingRangePicker && ( { setDate(fromDate, '') @@ -139,15 +182,25 @@ export function DateFilter({ ref: rollingDateRangeRef, }} max={max} + allowedDateOptions={isFixedDateMode ? ['hours', 'days', 'weeks', 'months', 'years'] : undefined} + fullWidth /> )} - - From custom date until now… - - - Custom fixed date range… - + {isFixedDateMode ? ( + + Custom date... + + ) : ( + <> + + From custom date until now… + + + Custom fixed date range… + + + )}
) diff --git a/frontend/src/lib/components/DateFilter/RollingDateRangeFilter.tsx b/frontend/src/lib/components/DateFilter/RollingDateRangeFilter.tsx index 99b1b12a31723..483d865c23f59 100644 --- a/frontend/src/lib/components/DateFilter/RollingDateRangeFilter.tsx +++ b/frontend/src/lib/components/DateFilter/RollingDateRangeFilter.tsx @@ -1,28 +1,34 @@ import './RollingDateRangeFilter.scss' -import { LemonButton, LemonInput, LemonSelect, LemonSelectOptions } from '@posthog/lemon-ui' +import { LemonButton, LemonButtonProps, LemonInput, LemonSelect, LemonSelectOptionLeaf } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' import { dayjs } from 'lib/dayjs' import { Tooltip } from 'lib/lemon-ui/Tooltip' import { DateOption, rollingDateRangeFilterLogic } from './rollingDateRangeFilterLogic' -const dateOptions: LemonSelectOptions = [ +const dateOptions: LemonSelectOptionLeaf[] = [ + { value: 'hours', label: 'hours' }, { value: 'days', label: 'days' }, { value: 'weeks', label: 'weeks' }, { value: 'months', label: 'months' }, { value: 'quarters', label: 'quarters' }, + { value: 'years', label: 'years' }, ] 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 } + dateRangeFilterLabel?: string + allowedDateOptions?: DateOption[] + fullWidth?: LemonButtonProps['fullWidth'] } export function RollingDateRangeFilter({ @@ -32,21 +38,26 @@ export function RollingDateRangeFilter({ dateFrom, selected, max, + dateRangeFilterLabel = 'In the last', + pageKey, + allowedDateOptions = ['days', 'weeks', 'months', 'years'], + fullWidth, }: RollingDateRangeFilterProps): JSX.Element { - const logicProps = { onChange, dateFrom, selected, max } + const logicProps = { onChange, dateFrom, selected, max, 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 ( - + -

In the last

+

{dateRangeFilterLabel}

e.stopPropagation()}> allowedDateOptions.includes(option.value))} menu={{ ...popover, className: 'RollingDateRangeFilter__popover', diff --git a/frontend/src/lib/components/DateFilter/dateFilterLogic.ts b/frontend/src/lib/components/DateFilter/dateFilterLogic.ts index 77faff7074618..d09067f80ce47 100644 --- a/frontend/src/lib/components/DateFilter/dateFilterLogic.ts +++ b/frontend/src/lib/components/DateFilter/dateFilterLogic.ts @@ -15,6 +15,7 @@ export const dateFilterLogic = kea([ open: true, openFixedRange: true, openDateToNow: true, + openFixedDate: true, close: true, applyRange: true, setDate: (dateFrom: string | null, dateTo: string | null) => ({ dateFrom, dateTo }), @@ -28,6 +29,7 @@ export const dateFilterLogic = kea([ open: () => DateFilterView.QuickList, openFixedRange: () => DateFilterView.FixedRange, openDateToNow: () => DateFilterView.DateToNow, + openFixedDate: () => DateFilterView.FixedDate, }, ], isVisible: [ @@ -36,6 +38,7 @@ export const dateFilterLogic = kea([ open: () => true, openFixedRange: () => true, openDateToNow: () => true, + openFixedDate: () => true, setDate: () => false, close: () => false, }, @@ -71,14 +74,20 @@ export const dateFilterLogic = kea([ (dateFrom, dateTo) => !!(dateFrom && dateTo && dayjs(dateFrom).isValid() && dayjs(dateTo).isValid()), ], isDateToNow: [ - (s) => [s.dateFrom, s.dateTo], - (dateFrom, dateTo) => !!dateFrom && !dateTo && dayjs(dateFrom).isValid(), + (s) => [s.dateFrom, s.dateTo, (_, p) => p.isFixedDateMode], + (dateFrom, dateTo, isFixedDateMode) => + !!dateFrom && !dateTo && dayjs(dateFrom).isValid() && !isFixedDateMode, + ], + isFixedDate: [ + (s) => [s.dateFrom, s.dateTo, (_, p) => p.isFixedDateMode], + (dateFrom, dateTo, isFixedDateMode) => dateFrom && dayjs(dateFrom).isValid() && !dateTo && isFixedDateMode, ], isRollingDateRange: [ - (s) => [s.isFixedRange, s.isDateToNow, s.dateOptions, s.dateFrom, s.dateTo], - (isFixedRange, isDateToNow, dateOptions, dateFrom, dateTo): boolean => + (s) => [s.isFixedRange, s.isDateToNow, s.isFixedDate, s.dateOptions, s.dateFrom, s.dateTo], + (isFixedRange, isDateToNow, isFixedDate, dateOptions, dateFrom, dateTo): boolean => !isFixedRange && !isDateToNow && + !isFixedDate && !dateOptions?.find( (option) => (option.values[0] ?? null) === (dateFrom ?? null) && @@ -86,12 +95,14 @@ export const dateFilterLogic = kea([ ), ], label: [ - (s) => [s.dateFrom, s.dateTo, s.isFixedRange, s.isDateToNow, s.dateOptions], - (dateFrom, dateTo, isFixedRange, isDateToNow, dateOptions) => + (s) => [s.dateFrom, s.dateTo, s.isFixedRange, s.isDateToNow, s.isFixedDate, s.dateOptions], + (dateFrom, dateTo, isFixedRange, isDateToNow, isFixedDate, dateOptions) => isFixedRange ? formatDateRange(dayjs(dateFrom), dayjs(dateTo)) : isDateToNow ? `${formatDate(dayjs(dateFrom))} to now` + : isFixedDate + ? formatDate(dateStringToDayJs(dateFrom) ?? dayjs(dateFrom)) : dateFilterToText(dateFrom, dateTo, CUSTOM_OPTION_VALUE, dateOptions, false), ], }), diff --git a/frontend/src/lib/components/DateFilter/rollingDateRangeFilterLogic.ts b/frontend/src/lib/components/DateFilter/rollingDateRangeFilterLogic.ts index 29bed83a7566f..821a1095b716e 100644 --- a/frontend/src/lib/components/DateFilter/rollingDateRangeFilterLogic.ts +++ b/frontend/src/lib/components/DateFilter/rollingDateRangeFilterLogic.ts @@ -1,16 +1,18 @@ import './RollingDateRangeFilter.scss' -import { actions, kea, listeners, path, props, reducers, selectors } from 'kea' +import { actions, kea, key, listeners, path, props, reducers, selectors } from 'kea' import { Dayjs } from 'lib/dayjs' import { dateFilterToText } from 'lib/utils' import type { rollingDateRangeFilterLogicType } from './rollingDateRangeFilterLogicType' const dateOptionsMap = { + y: 'years', q: 'quarters', m: 'months', w: 'weeks', d: 'days', + h: 'hours', } as const export type DateOption = (typeof dateOptionsMap)[keyof typeof dateOptionsMap] @@ -20,6 +22,7 @@ export type RollingDateFilterLogicPropsType = { onChange?: (fromDate: string) => void dateFrom?: Dayjs | string | null max?: number | null + pageKey?: string } const counterDefault = (selected: boolean | undefined, dateFrom: Dayjs | string | null | undefined): number => { @@ -32,7 +35,7 @@ const counterDefault = (selected: boolean | undefined, dateFrom: Dayjs | string return 3 } -const dateOptionDefault = (selected: boolean | undefined, dateFrom: Dayjs | string | null | undefined): string => { +const dateOptionDefault = (selected: boolean | undefined, dateFrom: Dayjs | string | null | undefined): DateOption => { if (selected && dateFrom && typeof dateFrom === 'string') { const dateOption = dateOptionsMap[dateFrom.slice(-1)] if (dateOption) { @@ -44,15 +47,16 @@ const dateOptionDefault = (selected: boolean | undefined, dateFrom: Dayjs | stri export const rollingDateRangeFilterLogic = kea([ path(['lib', 'components', 'DateFilter', 'RollingDateRangeFilterLogic']), + props({} as RollingDateFilterLogicPropsType), + key(({ pageKey }) => pageKey ?? 'unknown'), actions({ increaseCounter: true, decreaseCounter: true, setCounter: (counter: number | null | undefined) => ({ counter }), - setDateOption: (option: string) => ({ option }), + setDateOption: (option: DateOption) => ({ option }), toggleDateOptionsSelector: true, select: true, }), - props({} as RollingDateFilterLogicPropsType), reducers(({ props }) => ({ counter: [ counterDefault(props.selected, props.dateFrom) as number | null, @@ -84,17 +88,23 @@ export const rollingDateRangeFilterLogic = kea( selectors(() => ({ value: [ (s) => [s.counter, s.dateOption], - (counter: number | null, dateOption: string) => { + (counter, dateOption) => { if (!counter) { return '' } switch (dateOption) { + case 'years': + return `-${counter}y` case 'quarters': return `-${counter}q` case 'months': return `-${counter}m` case 'weeks': return `-${counter}w` + case 'days': + return `-${counter}d` + case 'hours': + return `-${counter}h` default: return `-${counter}d` } @@ -106,6 +116,20 @@ export const rollingDateRangeFilterLogic = kea( return dateFilterToText(value, undefined, 'Custom rolling range', [], true) }, ], + startOfDateRange: [ + (s) => [s.value], + (value: string) => { + return dateFilterToText( + value, + undefined, + 'N/A', + [], + false, + value.slice(-1) === 'h' ? 'MMMM D, YYYY HH:mm:ss' : 'MMMM D, YYYY', + true + ) + }, + ], })), listeners(({ props, values }) => ({ select: () => { diff --git a/frontend/src/lib/components/DateFilter/types.ts b/frontend/src/lib/components/DateFilter/types.ts index d3563cbbad0bf..506eb7bab36ae 100644 --- a/frontend/src/lib/components/DateFilter/types.ts +++ b/frontend/src/lib/components/DateFilter/types.ts @@ -6,6 +6,7 @@ export enum DateFilterView { QuickList = 'QuickList', DateToNow = 'DateToNow', FixedRange = 'FixedRange', + FixedDate = 'FixedDate', } export type DateFilterLogicProps = { @@ -15,6 +16,7 @@ export type DateFilterLogicProps = { dateTo?: Dayjs | string | null dateOptions?: DateMappingOption[] isDateFormatted?: boolean + isFixedDateMode?: boolean } export const CUSTOM_OPTION_KEY = 'Custom' diff --git a/frontend/src/lib/components/PropertyFilters/PropertyFilters.tsx b/frontend/src/lib/components/PropertyFilters/PropertyFilters.tsx index 14318daf54402..ca2157fd321b4 100644 --- a/frontend/src/lib/components/PropertyFilters/PropertyFilters.tsx +++ b/frontend/src/lib/components/PropertyFilters/PropertyFilters.tsx @@ -33,6 +33,7 @@ interface PropertyFiltersProps { allowNew?: boolean errorMessages?: JSX.Element[] | null propertyAllowList?: { [key in TaxonomicFilterGroupType]?: string[] } + allowRelativeDateOptions?: boolean } export function PropertyFilters({ @@ -55,6 +56,7 @@ export function PropertyFilters({ allowNew = true, errorMessages = null, propertyAllowList, + allowRelativeDateOptions, }: PropertyFiltersProps): JSX.Element { const logicProps = { propertyFilters, onChange, pageKey, sendAllKeyUpdates } const { filters, filtersWithNew } = useValues(propertyFilterLogic(logicProps)) @@ -112,6 +114,7 @@ export function PropertyFilters({ }} propertyAllowList={propertyAllowList} taxonomicFilterOptionsFromProp={taxonomicFilterOptionsFromProp} + allowRelativeDateOptions={allowRelativeDateOptions} /> )} errorMessage={errorMessages && errorMessages[index]} diff --git a/frontend/src/lib/components/PropertyFilters/components/OperatorValueSelect.tsx b/frontend/src/lib/components/PropertyFilters/components/OperatorValueSelect.tsx index d1d192368181a..289e3fa3ec30b 100644 --- a/frontend/src/lib/components/PropertyFilters/components/OperatorValueSelect.tsx +++ b/frontend/src/lib/components/PropertyFilters/components/OperatorValueSelect.tsx @@ -27,6 +27,7 @@ export interface OperatorValueSelectProps { eventNames?: string[] propertyDefinitions: PropertyDefinition[] defaultOpen?: boolean + addRelativeDateTimeOptions?: boolean } interface OperatorSelectProps extends Omit, 'options'> { @@ -67,6 +68,7 @@ export function OperatorValueSelect({ propertyDefinitions = [], eventNames = [], defaultOpen, + addRelativeDateTimeOptions, }: OperatorValueSelectProps): JSX.Element { const propertyDefinition = propertyDefinitions.find((pd) => pd.name === propkey) @@ -153,6 +155,7 @@ export function OperatorValueSelect({ }} // open automatically only if new filter autoFocus={!isMobile() && value === null} + addRelativeDateTimeOptions={addRelativeDateTimeOptions} />
)} diff --git a/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx b/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx index 033e0b2680b1e..c993676fabca4 100644 --- a/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx +++ b/frontend/src/lib/components/PropertyFilters/components/PropertyValue.tsx @@ -3,11 +3,13 @@ import './PropertyValue.scss' import { AutoComplete } from 'antd' import clsx from 'clsx' import { useActions, useValues } from 'kea' +import { DateFilter } from 'lib/components/DateFilter/DateFilter' import { DurationPicker } from 'lib/components/DurationPicker/DurationPicker' import { PropertyFilterDatePicker } from 'lib/components/PropertyFilters/components/PropertyFilterDatePicker' import { propertyFilterTypeToPropertyDefinitionType } from 'lib/components/PropertyFilters/utils' +import { dayjs } from 'lib/dayjs' import { LemonSelectMultiple } from 'lib/lemon-ui/LemonSelectMultiple/LemonSelectMultiple' -import { isOperatorDate, isOperatorFlag, isOperatorMulti, toString } from 'lib/utils' +import { formatDate, isOperatorDate, isOperatorFlag, isOperatorMulti, toString } from 'lib/utils' import { useEffect, useRef, useState } from 'react' import { propertyDefinitionsModel } from '~/models/propertyDefinitionsModel' @@ -25,6 +27,7 @@ export interface PropertyValueProps { autoFocus?: boolean allowCustom?: boolean eventNames?: string[] + addRelativeDateTimeOptions?: boolean } function matchesLowerCase(needle?: string, haystack?: string): boolean { @@ -46,6 +49,7 @@ export function PropertyValue({ autoFocus = false, allowCustom = true, eventNames = [], + addRelativeDateTimeOptions = false, }: PropertyValueProps): JSX.Element { // what the human has typed into the box const [input, setInput] = useState(Array.isArray(value) ? '' : toString(value) ?? '') @@ -193,6 +197,50 @@ export function PropertyValue({ ) } + if (isDateTimeProperty && addRelativeDateTimeOptions) { + if (operator === PropertyOperator.IsDateExact) { + return ( + + ) + } + + return ( + formatDate(date.subtract(24, 'h')), + defaultInterval: 'hour', + }, + { + key: 'Last 7 days', + values: ['-7d'], + getFormattedDate: (date: dayjs.Dayjs): string => formatDate(date.subtract(7, 'd')), + defaultInterval: 'day', + }, + { + key: 'Last 14 days', + values: ['-14d'], + getFormattedDate: (date: dayjs.Dayjs): string => formatDate(date.subtract(14, 'd')), + defaultInterval: 'day', + }, + ]} + size="medium" + makeLabel={(_, startOfRange) => ( + + Matches all values {operator === PropertyOperator.IsDateBefore ? 'before' : 'after'}{' '} + {startOfRange} + + )} + /> + ) + } + return isDateTimeProperty ? ( ) : isDurationProperty ? ( diff --git a/frontend/src/lib/components/PropertyFilters/components/TaxonomicPropertyFilter.tsx b/frontend/src/lib/components/PropertyFilters/components/TaxonomicPropertyFilter.tsx index 2875a289f97b7..533f565397751 100644 --- a/frontend/src/lib/components/PropertyFilters/components/TaxonomicPropertyFilter.tsx +++ b/frontend/src/lib/components/PropertyFilters/components/TaxonomicPropertyFilter.tsx @@ -45,6 +45,7 @@ export function TaxonomicPropertyFilter({ metadataSource, propertyAllowList, taxonomicFilterOptionsFromProp, + allowRelativeDateOptions, }: PropertyFilterInternalProps): JSX.Element { const pageKey = useMemo(() => pageKeyInput || `filter-${uniqueMemoizedIndex++}`, [pageKeyInput]) const groupTypes = taxonomicGroupTypes || [ @@ -197,6 +198,7 @@ export function TaxonomicPropertyFilter({ placeholder="Enter value..." endpoint={filter?.key && activeTaxonomicGroup?.valuesEndpoint?.(filter.key)} eventNames={eventNames} + addRelativeDateTimeOptions={allowRelativeDateOptions} onChange={(newOperator, newValue) => { if (filter?.key && filter?.type) { setFilter(index, { diff --git a/frontend/src/lib/components/PropertyFilters/types.ts b/frontend/src/lib/components/PropertyFilters/types.ts index 4cb168efc0e4f..0ba5d20825894 100644 --- a/frontend/src/lib/components/PropertyFilters/types.ts +++ b/frontend/src/lib/components/PropertyFilters/types.ts @@ -48,4 +48,5 @@ export interface PropertyFilterInternalProps { hasRowOperator?: boolean metadataSource?: AnyDataNode propertyAllowList?: { [key in TaxonomicFilterGroupType]?: string[] } + allowRelativeDateOptions?: boolean } diff --git a/frontend/src/lib/utils.tsx b/frontend/src/lib/utils.tsx index 52ecc1aef1839..de19b89459432 100644 --- a/frontend/src/lib/utils.tsx +++ b/frontend/src/lib/utils.tsx @@ -882,7 +882,8 @@ export function dateFilterToText( defaultValue: string | null, dateOptions: DateMappingOption[] = dateMapping, isDateFormatted: boolean = false, - dateFormat: string = DATE_FORMAT + dateFormat: string = DATE_FORMAT, + startOfRange: boolean = false ): string | null { if (dayjs.isDayjs(dateFrom) && dayjs.isDayjs(dateTo)) { return formatDateRange(dateFrom, dateTo, dateFormat) @@ -922,6 +923,12 @@ export function dateFilterToText( if (dateOption && counter) { let date = null switch (dateOption) { + case 'year': + date = dayjs().subtract(counter, 'y') + break + case 'hour': + date = dayjs().subtract(counter, 'h') + break case 'quarter': date = dayjs().subtract(counter * 3, 'M') break @@ -937,6 +944,8 @@ export function dateFilterToText( } if (isDateFormatted) { return formatDateRange(date, dayjs().endOf('d')) + } else if (startOfRange) { + return formatDate(date, dateFormat) } else { return `Last ${counter} ${dateOption}${counter > 1 ? 's' : ''}` } diff --git a/frontend/src/scenes/feature-flags/FeatureFlagReleaseConditions.tsx b/frontend/src/scenes/feature-flags/FeatureFlagReleaseConditions.tsx index cc054dd7de160..e681c2b425536 100644 --- a/frontend/src/scenes/feature-flags/FeatureFlagReleaseConditions.tsx +++ b/frontend/src/scenes/feature-flags/FeatureFlagReleaseConditions.tsx @@ -6,7 +6,7 @@ import { router } from 'kea-router' import { allOperatorsToHumanName } from 'lib/components/DefinitionPopover/utils' import { PropertyFilters } from 'lib/components/PropertyFilters/PropertyFilters' import { isPropertyFilterWithOperator } from 'lib/components/PropertyFilters/utils' -import { INSTANTLY_AVAILABLE_PROPERTIES } from 'lib/constants' +import { FEATURE_FLAGS, INSTANTLY_AVAILABLE_PROPERTIES } from 'lib/constants' import { groupsAccessLogic, GroupsAccessStatus } from 'lib/introductions/groupsAccessLogic' import { GroupsIntroductionOption } from 'lib/introductions/GroupsIntroductionOption' import { IconCopy, IconDelete, IconErrorOutline, IconOpenInNew, IconPlus, IconSubArrowRight } from 'lib/lemon-ui/icons' @@ -15,7 +15,7 @@ import { LemonButton } from 'lib/lemon-ui/LemonButton' import { LemonDivider } from 'lib/lemon-ui/LemonDivider' import { LemonTag } from 'lib/lemon-ui/LemonTag/LemonTag' import { Spinner } from 'lib/lemon-ui/Spinner/Spinner' -import { capitalizeFirstLetter, humanFriendlyNumber } from 'lib/utils' +import { capitalizeFirstLetter, dateFilterToText, dateStringToComponents, humanFriendlyNumber } from 'lib/utils' import { urls } from 'scenes/urls' import { cohortsModel } from '~/models/cohortsModel' @@ -51,6 +51,7 @@ export function FeatureFlagReleaseConditions({ affectedUsers, totalUsers, featureFlagTaxonomicOptions, + enabledFeatures, } = useValues(logic) const { setAggregationGroupTypeIndex, @@ -147,48 +148,61 @@ export function FeatureFlagReleaseConditions({ {readOnly ? ( <> {group.properties?.map((property, idx) => ( - <> -
- {idx === 0 ? ( - } - size="small" - /> - ) : ( - &} size="small" /> - )} - - {property.type === 'cohort' ? 'Cohort' : property.key}{' '} - - {isPropertyFilterWithOperator(property) ? ( - {allOperatorsToHumanName(property.operator)} - ) : null} +
+ {idx === 0 ? ( + } + size="small" + /> + ) : ( + &} size="small" /> + )} + + {property.type === 'cohort' ? 'Cohort' : property.key}{' '} + + {isPropertyFilterWithOperator(property) ? ( + {allOperatorsToHumanName(property.operator)} + ) : null} - {property.type === 'cohort' ? ( - } - targetBlank - > - {(property.value && cohortsById[property.value]?.name) || - `ID ${property.value}`} - - ) : ( - [ - ...(Array.isArray(property.value) ? property.value : [property.value]), - ].map((val, idx) => ( + {property.type === 'cohort' ? ( + } + targetBlank + > + {(property.value && cohortsById[property.value]?.name) || + `ID ${property.value}`} + + ) : ( + [...(Array.isArray(property.value) ? property.value : [property.value])].map( + (val, idx) => ( {val} + {isPropertyFilterWithOperator(property) && + ['is_date_before', 'is_date_after'].includes(property.operator) && + dateStringToComponents(String(val)) // check it's a relative date + ? ` ( ${dateFilterToText( + String(val), + undefined, + '', + [], + false, + String(val).slice(-1) === 'h' + ? 'MMMM D, YYYY HH:mm:ss' + : 'MMMM D, YYYY', + true + )} )` + : ''} - )) - )} -
- + ) + ) + )} +
))} ) : ( @@ -206,6 +220,7 @@ export function FeatureFlagReleaseConditions({ taxonomicFilterOptionsFromProp={featureFlagTaxonomicOptions} hasRowOperator={false} sendAllKeyUpdates + allowRelativeDateOptions={!!enabledFeatures[FEATURE_FLAGS.NEW_FEATURE_FLAG_OPERATORS]} errorMessages={ propertySelectErrors?.[index]?.properties?.some((message) => !!message.value) ? propertySelectErrors[index].properties?.map((message, index) => {