diff --git a/frontend/src/lib/components/DefinitionPopover/DefinitionPopover.tsx b/frontend/src/lib/components/DefinitionPopover/DefinitionPopover.tsx index bd250bfe90adb..3a31f5e704ee1 100644 --- a/frontend/src/lib/components/DefinitionPopover/DefinitionPopover.tsx +++ b/frontend/src/lib/components/DefinitionPopover/DefinitionPopover.tsx @@ -124,7 +124,8 @@ function Example({ value }: { value?: string }): JSX.Element { type === TaxonomicFilterGroupType.EventProperties || type === TaxonomicFilterGroupType.EventFeatureFlags || type === TaxonomicFilterGroupType.PersonProperties || - type === TaxonomicFilterGroupType.GroupsPrefix + type === TaxonomicFilterGroupType.GroupsPrefix || + type === TaxonomicFilterGroupType.Metadata ) { data = getKeyMapping(value, 'event') } else if (type === TaxonomicFilterGroupType.Elements) { diff --git a/frontend/src/lib/components/DefinitionPopover/definitionPopoverLogic.ts b/frontend/src/lib/components/DefinitionPopover/definitionPopoverLogic.ts index 013ecdc159c3d..d6ea4c4c751e0 100644 --- a/frontend/src/lib/components/DefinitionPopover/definitionPopoverLogic.ts +++ b/frontend/src/lib/components/DefinitionPopover/definitionPopoverLogic.ts @@ -178,6 +178,7 @@ export const definitionPopoverLogic = kea([ TaxonomicFilterGroupType.EventProperties, TaxonomicFilterGroupType.EventFeatureFlags, TaxonomicFilterGroupType.NumericalEventProperties, + TaxonomicFilterGroupType.Metadata, ].includes(type) || type.startsWith(TaxonomicFilterGroupType.GroupsPrefix), ], isCohort: [(s) => [s.type], (type) => type === TaxonomicFilterGroupType.Cohorts], diff --git a/frontend/src/lib/components/PropertyFilters/PropertyFilters.tsx b/frontend/src/lib/components/PropertyFilters/PropertyFilters.tsx index 3f6b29b29bfa4..14318daf54402 100644 --- a/frontend/src/lib/components/PropertyFilters/PropertyFilters.tsx +++ b/frontend/src/lib/components/PropertyFilters/PropertyFilters.tsx @@ -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' @@ -20,6 +20,7 @@ interface PropertyFiltersProps { showConditionBadge?: boolean disablePopover?: boolean taxonomicGroupTypes?: TaxonomicFilterGroupType[] + taxonomicFilterOptionsFromProp?: TaxonomicFilterProps['optionsFromProp'] metadataSource?: AnyDataNode showNestedArrow?: boolean eventNames?: string[] @@ -41,6 +42,7 @@ export function PropertyFilters({ showConditionBadge = false, disablePopover = false, // use bare PropertyFilter without popover taxonomicGroupTypes, + taxonomicFilterOptionsFromProp, metadataSource, showNestedArrow = false, eventNames = [], @@ -109,6 +111,7 @@ export function PropertyFilters({ placement: pageKey === 'insight-filters' ? 'bottomLeft' : undefined, }} propertyAllowList={propertyAllowList} + taxonomicFilterOptionsFromProp={taxonomicFilterOptionsFromProp} /> )} errorMessage={errorMessages && errorMessages[index]} diff --git a/frontend/src/lib/components/PropertyFilters/components/TaxonomicPropertyFilter.tsx b/frontend/src/lib/components/PropertyFilters/components/TaxonomicPropertyFilter.tsx index 24d9cc3f52bb4..2875a289f97b7 100644 --- a/frontend/src/lib/components/PropertyFilters/components/TaxonomicPropertyFilter.tsx +++ b/frontend/src/lib/components/PropertyFilters/components/TaxonomicPropertyFilter.tsx @@ -44,6 +44,7 @@ export function TaxonomicPropertyFilter({ hasRowOperator, metadataSource, propertyAllowList, + taxonomicFilterOptionsFromProp, }: PropertyFilterInternalProps): JSX.Element { const pageKey = useMemo(() => pageKeyInput || `filter-${uniqueMemoizedIndex++}`, [pageKeyInput]) const groupTypes = taxonomicGroupTypes || [ @@ -56,9 +57,10 @@ export function TaxonomicPropertyFilter({ ] const taxonomicOnChange: (group: TaxonomicFilterGroup, value: TaxonomicFilterValue, item: any) => void = ( taxonomicGroup, - value + value, + item ) => { - selectItem(taxonomicGroup, value) + selectItem(taxonomicGroup, value, item?.propertyFilterType) if ( taxonomicGroup.type === TaxonomicFilterGroupType.Cohorts || taxonomicGroup.type === TaxonomicFilterGroupType.HogQLExpression @@ -108,6 +110,7 @@ export function TaxonomicPropertyFilter({ metadataSource={metadataSource} eventNames={eventNames} propertyAllowList={propertyAllowList} + optionsFromProp={taxonomicFilterOptionsFromProp} /> ) diff --git a/frontend/src/lib/components/PropertyFilters/components/taxonomicPropertyFilterLogic.ts b/frontend/src/lib/components/PropertyFilters/components/taxonomicPropertyFilterLogic.ts index 3490ae4608d72..aa1a1ca685cc7 100644 --- a/frontend/src/lib/components/PropertyFilters/components/taxonomicPropertyFilterLogic.ts +++ b/frontend/src/lib/components/PropertyFilters/components/taxonomicPropertyFilterLogic.ts @@ -11,6 +11,7 @@ import { import { taxonomicFilterLogic } from 'lib/components/TaxonomicFilter/taxonomicFilterLogic' import { TaxonomicFilterGroup, + TaxonomicFilterGroupType, TaxonomicFilterLogicProps, TaxonomicFilterValue, } from 'lib/components/TaxonomicFilter/types' @@ -50,9 +51,14 @@ export const taxonomicPropertyFilterLogic = kea ({ + selectItem: ( + taxonomicGroup: TaxonomicFilterGroup, + propertyKey?: TaxonomicFilterValue, + itemPropertyFilterType?: PropertyFilterType + ) => ({ taxonomicGroup, propertyKey, + itemPropertyFilterType, }), openDropdown: true, closeDropdown: true, @@ -87,8 +93,8 @@ export const taxonomicPropertyFilterLogic = kea ({ - selectItem: ({ taxonomicGroup, propertyKey }) => { - const propertyType = taxonomicFilterTypeToPropertyFilterType(taxonomicGroup.type) + selectItem: ({ taxonomicGroup, propertyKey, itemPropertyFilterType }) => { + const propertyType = itemPropertyFilterType ?? taxonomicFilterTypeToPropertyFilterType(taxonomicGroup.type) if (propertyKey && propertyType) { if (propertyType === PropertyFilterType.Cohort) { const cohortProperty: CohortPropertyFilter = { @@ -106,9 +112,7 @@ export const taxonomicPropertyFilterLogic = kea void disablePopover: boolean taxonomicGroupTypes?: TaxonomicFilterGroupType[] + taxonomicFilterOptionsFromProp?: TaxonomicFilterProps['optionsFromProp'] eventNames?: string[] propertyGroupType?: FilterLogicalOperator | null orFiltering?: boolean diff --git a/frontend/src/lib/components/PropertyFilters/utils.ts b/frontend/src/lib/components/PropertyFilters/utils.ts index e4bb8cbba1c7f..720b4a1abd50d 100644 --- a/frontend/src/lib/components/PropertyFilters/utils.ts +++ b/frontend/src/lib/components/PropertyFilters/utils.ts @@ -292,7 +292,10 @@ export function taxonomicFilterTypeToPropertyFilterType( if (filterType === TaxonomicFilterGroupType.CohortsWithAllUsers) { return PropertyFilterType.Cohort } - if (filterType?.startsWith(TaxonomicFilterGroupType.GroupsPrefix)) { + if ( + filterType?.startsWith(TaxonomicFilterGroupType.GroupsPrefix) || + filterType?.startsWith(TaxonomicFilterGroupType.GroupNamesPrefix) + ) { return PropertyFilterType.Group } diff --git a/frontend/src/lib/components/TaxonomicFilter/InfiniteList.tsx b/frontend/src/lib/components/TaxonomicFilter/InfiniteList.tsx index 643009749e856..0591c4537be7a 100644 --- a/frontend/src/lib/components/TaxonomicFilter/InfiniteList.tsx +++ b/frontend/src/lib/components/TaxonomicFilter/InfiniteList.tsx @@ -105,6 +105,7 @@ const renderItemContents = ({ listGroupType === TaxonomicFilterGroupType.PersonProperties || listGroupType === TaxonomicFilterGroupType.Events || listGroupType === TaxonomicFilterGroupType.CustomEvents || + listGroupType === TaxonomicFilterGroupType.Metadata || listGroupType.startsWith(TaxonomicFilterGroupType.GroupsPrefix) ? ( <>
@@ -136,7 +137,7 @@ const selectedItemHasPopover = ( group?: TaxonomicFilterGroup ): boolean => { return ( - // NB: also update "renderItemPopover" above + // NB: also update "renderItemContents" above !!item && !!group?.getValue?.(item) && !!listGroupType && @@ -151,6 +152,7 @@ const selectedItemHasPopover = ( TaxonomicFilterGroupType.PersonProperties, TaxonomicFilterGroupType.Cohorts, TaxonomicFilterGroupType.CohortsWithAllUsers, + TaxonomicFilterGroupType.Metadata, ].includes(listGroupType) || listGroupType.startsWith(TaxonomicFilterGroupType.GroupsPrefix)) ) diff --git a/frontend/src/lib/components/TaxonomicFilter/infiniteListLogic.ts b/frontend/src/lib/components/TaxonomicFilter/infiniteListLogic.ts index 4d0857b0878fc..034f3c2601fca 100644 --- a/frontend/src/lib/components/TaxonomicFilter/infiniteListLogic.ts +++ b/frontend/src/lib/components/TaxonomicFilter/infiniteListLogic.ts @@ -269,6 +269,7 @@ export const infiniteListLogic = kea([ [TaxonomicFilterGroupType.Events]: 'event', [TaxonomicFilterGroupType.EventProperties]: 'event', [TaxonomicFilterGroupType.PersonProperties]: 'event', + [TaxonomicFilterGroupType.Metadata]: 'event', [TaxonomicFilterGroupType.Elements]: 'element', } diff --git a/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx b/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx index b285af7d15ef3..a404a7a8bc9c3 100644 --- a/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx +++ b/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx @@ -206,6 +206,16 @@ export const taxonomicFilterLogic = kea([ getValue: (option: SimpleOption) => option.name, getPopoverHeader: () => 'Autocapture Element', }, + { + name: 'Metadata', + searchPlaceholder: 'metadata', + type: TaxonomicFilterGroupType.Metadata, + // populate options using `optionsFromProp` depending on context in which + // this taxonomic group type is used + getName: (option: SimpleOption) => option.name, + getValue: (option: SimpleOption) => option.name, + ...propertyTaxonomicGroupProps(true), + }, { name: 'Event properties', searchPlaceholder: 'event properties', diff --git a/frontend/src/lib/components/TaxonomicFilter/types.ts b/frontend/src/lib/components/TaxonomicFilter/types.ts index 132d51862d354..cfc4ce64c00a8 100644 --- a/frontend/src/lib/components/TaxonomicFilter/types.ts +++ b/frontend/src/lib/components/TaxonomicFilter/types.ts @@ -2,10 +2,18 @@ import Fuse from 'fuse.js' import { LogicWrapper } from 'kea' import { AnyDataNode } from '~/queries/schema' -import { ActionType, CohortType, EventDefinition, PersonProperty, PropertyDefinition } from '~/types' +import { + ActionType, + CohortType, + EventDefinition, + PersonProperty, + PropertyDefinition, + PropertyFilterType, +} from '~/types' export interface SimpleOption { name: string + propertyFilterType?: PropertyFilterType } export interface TaxonomicFilterProps { @@ -23,7 +31,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 } @@ -66,6 +74,8 @@ export interface TaxonomicFilterGroup { } export enum TaxonomicFilterGroupType { + // Person and event metadata that isn't present in properties + Metadata = 'metadata', Actions = 'actions', Cohorts = 'cohorts', CohortsWithAllUsers = 'cohorts_with_all', diff --git a/frontend/src/lib/constants.tsx b/frontend/src/lib/constants.tsx index 9c499861b1854..4ad0fc5063086 100644 --- a/frontend/src/lib/constants.tsx +++ b/frontend/src/lib/constants.tsx @@ -95,6 +95,9 @@ export const INSTANTLY_AVAILABLE_PROPERTIES = [ '$geoip_continent_code', '$geoip_postal_code', '$geoip_time_zone', + // Person and group identifiers + '$group_key', + 'distinct_id', ] // Event constants @@ -196,6 +199,7 @@ export const FEATURE_FLAGS = { REDIRECT_WEB_PRODUCT_ANALYTICS_ONBOARDING: 'redirect-web-product-analytics-onboarding', // owner: @biancayang RECRUIT_ANDROID_MOBILE_BETA_TESTERS: 'recruit-android-mobile-beta-testers', // owner: #team-replay SIDEPANEL_STATUS: 'sidepanel-status', // owner: @benjackwhite + NEW_FEATURE_FLAG_OPERATORS: 'new-feature-flag-operators', // owner: @neilkakkar AI_SESSION_SUMMARY: 'ai-session-summary', // owner: #team-replay } as const export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS] diff --git a/frontend/src/lib/taxonomy.tsx b/frontend/src/lib/taxonomy.tsx index a0219ddd5f1ab..b5c6cf2f7767f 100644 --- a/frontend/src/lib/taxonomy.tsx +++ b/frontend/src/lib/taxonomy.tsx @@ -592,6 +592,11 @@ export const KEY_MAPPING: KeyMappingInterface = { examples: ['16ff262c4301e5-0aa346c03894bc-39667c0e-1aeaa0-16ff262c431767'], system: true, }, + distinct_id: { + label: 'Distinct ID', + description: 'The current distinct ID of the user', + examples: ['16ff262c4301e5-0aa346c03894bc-39667c0e-1aeaa0-16ff262c431767'], + }, $event_type: { label: 'Event Type', description: diff --git a/frontend/src/scenes/feature-flags/FeatureFlagReleaseConditions.tsx b/frontend/src/scenes/feature-flags/FeatureFlagReleaseConditions.tsx index 8888b32dc2199..cc054dd7de160 100644 --- a/frontend/src/scenes/feature-flags/FeatureFlagReleaseConditions.tsx +++ b/frontend/src/scenes/feature-flags/FeatureFlagReleaseConditions.tsx @@ -50,6 +50,7 @@ export function FeatureFlagReleaseConditions({ computeBlastRadiusPercentage, affectedUsers, totalUsers, + featureFlagTaxonomicOptions, } = useValues(logic) const { setAggregationGroupTypeIndex, @@ -202,6 +203,7 @@ export function FeatureFlagReleaseConditions({ addText="Add condition" onChange={(properties) => updateConditionSet(index, undefined, properties)} taxonomicGroupTypes={taxonomicGroupTypes} + taxonomicFilterOptionsFromProp={featureFlagTaxonomicOptions} hasRowOperator={false} sendAllKeyUpdates errorMessages={ diff --git a/frontend/src/scenes/feature-flags/featureFlagLogic.ts b/frontend/src/scenes/feature-flags/featureFlagLogic.ts index c40938c5ea6da..829084a53898b 100644 --- a/frontend/src/scenes/feature-flags/featureFlagLogic.ts +++ b/frontend/src/scenes/feature-flags/featureFlagLogic.ts @@ -4,9 +4,11 @@ import { loaders } from 'kea-loaders' import { router, urlToAction } from 'kea-router' import api from 'lib/api' import { convertPropertyGroupToProperties } from 'lib/components/PropertyFilters/utils' -import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types' +import { TaxonomicFilterGroupType, TaxonomicFilterProps } from 'lib/components/TaxonomicFilter/types' +import { FEATURE_FLAGS } from 'lib/constants' import { dayjs } from 'lib/dayjs' import { lemonToast } from 'lib/lemon-ui/LemonToast/LemonToast' +import { featureFlagLogic as enabledFeaturesLogic } from 'lib/logic/featureFlagLogic' import { sum, toParams } from 'lib/utils' import { deleteWithUndo } from 'lib/utils/deleteWithUndo' import { eventUsageLogic } from 'lib/utils/eventUsageLogic' @@ -189,6 +191,8 @@ export const featureFlagLogic = kea([ ['dashboards'], organizationLogic, ['currentOrganization'], + enabledFeaturesLogic, + ['featureFlags as enabledFeatures'], ], actions: [ newDashboardLogic({ featureFlagId: typeof props.id === 'number' ? props.id : undefined }), @@ -927,17 +931,33 @@ export const featureFlagLogic = kea([ }, ], taxonomicGroupTypes: [ - (s) => [s.featureFlag, s.groupsTaxonomicTypes], - (featureFlag, groupsTaxonomicTypes): TaxonomicFilterGroupType[] => { + (s) => [s.featureFlag, s.groupsTaxonomicTypes, s.enabledFeatures], + (featureFlag, groupsTaxonomicTypes, enabledFeatures): TaxonomicFilterGroupType[] => { + const baseGroupTypes = [] + const additionalGroupTypes = [] + const newFlagOperatorsEnabled = enabledFeatures[FEATURE_FLAGS.NEW_FEATURE_FLAG_OPERATORS] if ( featureFlag && featureFlag.filters.aggregation_group_type_index != null && groupsTaxonomicTypes.length > 0 ) { - return [groupsTaxonomicTypes[featureFlag.filters.aggregation_group_type_index]] + baseGroupTypes.push(groupsTaxonomicTypes[featureFlag.filters.aggregation_group_type_index]) + + if (newFlagOperatorsEnabled) { + additionalGroupTypes.push( + `${TaxonomicFilterGroupType.GroupNamesPrefix}_${featureFlag.filters.aggregation_group_type_index}` as unknown as TaxonomicFilterGroupType + ) + } + } else { + baseGroupTypes.push(TaxonomicFilterGroupType.PersonProperties) + baseGroupTypes.push(TaxonomicFilterGroupType.Cohorts) + + if (newFlagOperatorsEnabled) { + additionalGroupTypes.push(TaxonomicFilterGroupType.Metadata) + } } - return [TaxonomicFilterGroupType.PersonProperties, TaxonomicFilterGroupType.Cohorts] + return [...baseGroupTypes, ...additionalGroupTypes] }, ], breadcrumbs: [ @@ -951,6 +971,21 @@ export const featureFlagLogic = kea([ { key: [Scene.FeatureFlag, featureFlag.id || 'unknown'], name: featureFlag.key || 'Unnamed' }, ], ], + featureFlagTaxonomicOptions: [ + (s) => [s.featureFlag], + (featureFlag) => { + if (featureFlag && featureFlag.filters.aggregation_group_type_index != null) { + return {} + } + + const taxonomicOptions: TaxonomicFilterProps['optionsFromProp'] = { + [TaxonomicFilterGroupType.Metadata]: [ + { name: 'distinct_id', propertyFilterType: PropertyFilterType.Person }, + ], + } + return taxonomicOptions + }, + ], propertySelectErrors: [ (s) => [s.featureFlag], (featureFlag) => {