mirror of
https://github.com/PostHog/posthog.git
synced 2024-11-24 00:47:50 +01:00
Nearly there, just needs a tiny refactor
This commit is contained in:
parent
6e64697b08
commit
9b68150337
@ -50,6 +50,9 @@ export interface VerticalNestedDNDProps<ChildItem extends VDNDChildItem, Item ex
|
||||
initialItems: Item[]
|
||||
renderContainerItem: (item: Item, callbacks: { updateContainerItem: (item: Item) => void }) => JSX.Element | null
|
||||
renderChildItem: (item: ChildItem, callbacks: { updateChildItem: (item: ChildItem) => void }) => JSX.Element | null
|
||||
renderAddChildItem?: (item: Item, callbacks: { onAddChild: (id: UniqueIdentifier) => void }) => JSX.Element | null
|
||||
renderAddContainerItem?: (callbacks: { onAddContainer: () => void }) => JSX.Element | null
|
||||
renderAdditionalControls?: () => JSX.Element | null
|
||||
createNewContainerItem(): Item
|
||||
createNewChildItem(): ChildItem
|
||||
onChange?(items: Item[]): void
|
||||
@ -61,6 +64,9 @@ export function VerticalNestedDND<ChildItem extends VDNDChildItem, Item extends
|
||||
renderChildItem,
|
||||
createNewChildItem,
|
||||
createNewContainerItem,
|
||||
renderAddChildItem,
|
||||
renderAddContainerItem,
|
||||
renderAdditionalControls,
|
||||
onChange,
|
||||
}: VerticalNestedDNDProps<ChildItem, Item>): JSX.Element {
|
||||
const [items, setItems] = useState(() => {
|
||||
@ -365,12 +371,13 @@ export function VerticalNestedDND<ChildItem extends VDNDChildItem, Item extends
|
||||
<DroppableContainer
|
||||
key={containerId}
|
||||
items={items[containerId].items || []}
|
||||
onRemove={() => handleRemove(containerId)}
|
||||
onRemove={() => handleRemoveContainer(containerId)}
|
||||
renderContainerItem={renderContainerItem}
|
||||
containerItemId={containerId}
|
||||
item={items[containerId]}
|
||||
onAddChild={handleAddChild}
|
||||
updateContainerItem={updateContainerItem}
|
||||
renderAddChildItem={renderAddChildItem}
|
||||
>
|
||||
<SortableContext
|
||||
items={items[containerId].items || []}
|
||||
@ -388,6 +395,7 @@ export function VerticalNestedDND<ChildItem extends VDNDChildItem, Item extends
|
||||
getIndex={getIndex}
|
||||
renderChildItem={renderChildItem}
|
||||
updateChildItem={updateChildItem}
|
||||
onRemove={handleRemoveChild}
|
||||
item={value}
|
||||
/>
|
||||
)
|
||||
@ -396,10 +404,15 @@ export function VerticalNestedDND<ChildItem extends VDNDChildItem, Item extends
|
||||
</DroppableContainer>
|
||||
))}
|
||||
</SortableContext>
|
||||
<div className="px-[calc(1.5rem+1px)]">
|
||||
<LemonButton onClick={handleAddColumn} fullWidth={false} type="secondary">
|
||||
Add container
|
||||
</LemonButton>
|
||||
<div className="px-[calc(1.5rem+1px)] flex flex-row justify-end space-x-2">
|
||||
{renderAddContainerItem ? (
|
||||
renderAddContainerItem({ onAddContainer: handleAddColumn })
|
||||
) : (
|
||||
<LemonButton onClick={handleAddColumn} fullWidth={false} type="primary">
|
||||
Add container
|
||||
</LemonButton>
|
||||
)}
|
||||
{renderAdditionalControls ? renderAdditionalControls() : null}
|
||||
</div>
|
||||
</div>
|
||||
{createPortal(
|
||||
@ -427,6 +440,7 @@ export function VerticalNestedDND<ChildItem extends VDNDChildItem, Item extends
|
||||
renderChildItem={renderChildItem}
|
||||
item={item}
|
||||
updateChildItem={NOOP}
|
||||
onRemove={NOOP}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -452,16 +466,34 @@ export function VerticalNestedDND<ChildItem extends VDNDChildItem, Item extends
|
||||
renderChildItem={renderChildItem}
|
||||
item={item}
|
||||
updateChildItem={NOOP}
|
||||
onRemove={NOOP}
|
||||
/>
|
||||
))}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
function handleRemove(containerID: UniqueIdentifier): void {
|
||||
function handleRemoveContainer(containerID: UniqueIdentifier): void {
|
||||
setContainers((containers) => containers.filter((id) => id !== containerID))
|
||||
}
|
||||
|
||||
function handleRemoveChild(childId: UniqueIdentifier): void {
|
||||
setItems((items) => {
|
||||
const containerId = findContainer(childId)
|
||||
if (!containerId) {
|
||||
return items
|
||||
}
|
||||
const container = items[containerId]
|
||||
return {
|
||||
...items,
|
||||
[containerId]: {
|
||||
...container,
|
||||
items: container.items?.filter((item) => item.id !== childId),
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleAddColumn(): void {
|
||||
const newItem: Item = createNewContainerItem()
|
||||
|
||||
@ -540,6 +572,7 @@ interface SortableItemProps<Item extends VDNDChildItem> {
|
||||
getIndex(id: UniqueIdentifier): number
|
||||
renderChildItem(item: Item, callbacks: { updateChildItem: (item: Item) => void }): JSX.Element | null
|
||||
updateChildItem(item: Item): void
|
||||
onRemove(id: UniqueIdentifier): void
|
||||
item: Item
|
||||
}
|
||||
|
||||
@ -550,6 +583,7 @@ function SortableItem<Item extends VDNDChildItem>({
|
||||
handle,
|
||||
renderChildItem,
|
||||
updateChildItem,
|
||||
onRemove,
|
||||
item,
|
||||
}: SortableItemProps<Item>): JSX.Element {
|
||||
const { setNodeRef, setActivatorNodeRef, listeners, isDragging, isSorting, transform, transition } = useSortable({
|
||||
@ -572,6 +606,7 @@ function SortableItem<Item extends VDNDChildItem>({
|
||||
listeners={listeners}
|
||||
renderChildItem={renderChildItem}
|
||||
updateChildItem={updateChildItem}
|
||||
onRemove={onRemove}
|
||||
item={item}
|
||||
/>
|
||||
)
|
||||
@ -655,6 +690,10 @@ export interface ContainerProps<Item extends VNDNDContainerItem<any>> {
|
||||
transform?: string
|
||||
renderContainerItem(item: Item, callbacks: { updateContainerItem: (item: Item) => void }): JSX.Element | null
|
||||
updateContainerItem(item: Item): void
|
||||
renderAddChildItem?(
|
||||
item: Item,
|
||||
callbacks: { onAddChild: (containerId: UniqueIdentifier) => void }
|
||||
): JSX.Element | null
|
||||
item: Item
|
||||
}
|
||||
|
||||
@ -679,6 +718,7 @@ export const Container = forwardRef(function Container_<Item extends VNDNDContai
|
||||
renderContainerItem,
|
||||
updateContainerItem,
|
||||
item,
|
||||
renderAddChildItem,
|
||||
...props
|
||||
}: ContainerProps<Item>,
|
||||
ref: React.ForwardedRef<HTMLDivElement>
|
||||
@ -700,7 +740,7 @@ export const Container = forwardRef(function Container_<Item extends VNDNDContai
|
||||
onClick={onClick}
|
||||
tabIndex={onClick ? 0 : undefined}
|
||||
>
|
||||
<div className="flex flex-row justify-between px-2 space-x-2">
|
||||
<div className="flex flex-row justify-between px-2 space-x-2 items-start">
|
||||
<Handle {...handleProps} />
|
||||
<div className="flex-1">
|
||||
{renderContainerItem ? (
|
||||
@ -712,14 +752,18 @@ export const Container = forwardRef(function Container_<Item extends VNDNDContai
|
||||
<Remove onClick={onRemove} />
|
||||
</div>
|
||||
{placeholder ? children : <ul className="space-y-2">{children}</ul>}
|
||||
<div className="flex flex-row justify-between px-2 mb-2 space-x-2">
|
||||
<LemonButton
|
||||
onClick={onRemove ? () => onAddChild(item.id) : undefined}
|
||||
fullWidth={false}
|
||||
type="secondary"
|
||||
>
|
||||
Add child
|
||||
</LemonButton>
|
||||
<div className="flex flex-row justify-end px-2 mb-2 space-x-2">
|
||||
{renderAddChildItem ? (
|
||||
renderAddChildItem(item, { onAddChild })
|
||||
) : (
|
||||
<LemonButton
|
||||
onClick={onRemove ? () => onAddChild(item.id) : undefined}
|
||||
fullWidth={false}
|
||||
type="secondary"
|
||||
>
|
||||
Add child
|
||||
</LemonButton>
|
||||
)}
|
||||
</div>
|
||||
</Component>
|
||||
)
|
||||
@ -742,7 +786,7 @@ export interface ChildItemProps<Item extends VDNDChildItem> {
|
||||
wrapperStyle?: React.CSSProperties
|
||||
childItemId: UniqueIdentifier
|
||||
item: Item
|
||||
onRemove?(): void
|
||||
onRemove(id: UniqueIdentifier): void
|
||||
renderChildItem(item: Item, callbacks: { updateChildItem: (item: Item) => void }): JSX.Element | null
|
||||
updateChildItem(item: Item): void
|
||||
}
|
||||
@ -798,13 +842,13 @@ export const ChildItem = React.memo(
|
||||
{...(!handle ? listeners : undefined)}
|
||||
{...props}
|
||||
tabIndex={!handle ? 0 : undefined}
|
||||
className="flex flex-row justify-between w-full space-x-2"
|
||||
className="flex flex-row justify-between w-full space-x-2 items-start"
|
||||
>
|
||||
<Handle {...handleProps} {...listeners} />
|
||||
<div className="flex-1">
|
||||
{renderChildItem ? renderChildItem(item, { updateChildItem }) : <span>Item {childItemId}</span>}
|
||||
</div>
|
||||
<Remove onClick={onRemove} />
|
||||
<Remove onClick={() => onRemove(item.id)} />
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { BounceRatePageViewModeSetting } from 'scenes/settings/environment/BounceRatePageViewMode'
|
||||
import { ChannelType } from 'scenes/settings/environment/ChannelType'
|
||||
import { CustomChannelTypes } from 'scenes/settings/environment/CustomChannelTypes'
|
||||
import { DeadClicksAutocaptureSettings } from 'scenes/settings/environment/DeadClicksAutocaptureSettings'
|
||||
import { PersonsJoinMode } from 'scenes/settings/environment/PersonsJoinMode'
|
||||
import { PersonsOnEvents } from 'scenes/settings/environment/PersonsOnEvents'
|
||||
@ -215,8 +215,8 @@ export const SETTINGS_MAP: SettingSection[] = [
|
||||
settings: [
|
||||
{
|
||||
id: 'channel-type',
|
||||
title: 'Channel type',
|
||||
component: <ChannelType />,
|
||||
title: 'Custom channel type',
|
||||
component: <CustomChannelTypes />,
|
||||
},
|
||||
],
|
||||
flag: 'CUSTOM_CHANNEL_TYPE_RULES',
|
||||
|
@ -1,16 +1,24 @@
|
||||
import { VerticalNestedDND } from 'lib/lemon-ui/VerticalNestedDND/VerticalNestedDND'
|
||||
import { CustomChannelCondition, CustomChannelField, CustomChannelOperator, CustomChannelRule } from '~/queries/schema'
|
||||
import { IconPlus } from '@posthog/icons'
|
||||
import { useActions, useValues } from 'kea'
|
||||
import { teamLogic } from 'scenes/teamLogic'
|
||||
import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
|
||||
import { useMemo, useRef, useState } from 'react'
|
||||
import { genericOperatorMap, UnexpectedNeverError, uuid } from 'lib/utils'
|
||||
import { FilterLogicalOperator, PropertyFilterType, PropertyOperator } from '~/types'
|
||||
import isEqual from 'lodash.isequal'
|
||||
import debounce from 'lodash.debounce'
|
||||
import { LemonSelect } from 'lib/lemon-ui/LemonSelect'
|
||||
import { PropertyValue } from 'lib/components/PropertyFilters/components/PropertyValue'
|
||||
import { LemonInputSelect } from 'lib/lemon-ui/LemonInputSelect'
|
||||
import { LemonButton } from 'lib/lemon-ui/LemonButton'
|
||||
import { LemonInputSelect, LemonInputSelectOption } from 'lib/lemon-ui/LemonInputSelect'
|
||||
import { LemonSelect } from 'lib/lemon-ui/LemonSelect'
|
||||
import { VerticalNestedDND } from 'lib/lemon-ui/VerticalNestedDND/VerticalNestedDND'
|
||||
import { genericOperatorMap, UnexpectedNeverError, uuid } from 'lib/utils'
|
||||
import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
|
||||
import isEqual from 'lodash.isequal'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { teamLogic } from 'scenes/teamLogic'
|
||||
|
||||
import {
|
||||
CustomChannelCondition,
|
||||
CustomChannelField,
|
||||
CustomChannelOperator,
|
||||
CustomChannelRule,
|
||||
DefaultChannelTypes,
|
||||
} from '~/queries/schema'
|
||||
import { FilterLogicalOperator, PropertyFilterType, PropertyOperator } from '~/types'
|
||||
|
||||
const combinerOptions = [
|
||||
{ label: 'All', value: FilterLogicalOperator.And },
|
||||
@ -70,7 +78,7 @@ function opToPropertyOperator(op: CustomChannelOperator): PropertyOperator {
|
||||
}
|
||||
}
|
||||
|
||||
function keyToSessionproperty(key: CustomChannelField): string {
|
||||
function keyToSessionProperty(key: CustomChannelField): string {
|
||||
switch (key) {
|
||||
case CustomChannelField.ReferringDomain:
|
||||
return '$entry_referring_domain'
|
||||
@ -85,86 +93,96 @@ function keyToSessionproperty(key: CustomChannelField): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function ChannelType(): JSX.Element {
|
||||
const sanitizeCustomChannelTypeRules = (rules: CustomChannelRule[]): CustomChannelRule[] => {
|
||||
return (rules || [])
|
||||
.map((rule) => {
|
||||
return {
|
||||
id: rule.id || uuid(),
|
||||
channel_type: rule.channel_type,
|
||||
combiner: rule.combiner,
|
||||
items: (rule.items || [])
|
||||
.map((condition) => ({
|
||||
id: condition.id || uuid(),
|
||||
key: condition.key,
|
||||
op: condition.op,
|
||||
value: condition.value,
|
||||
}))
|
||||
.filter((item) => item.key && item.op && item.value),
|
||||
}
|
||||
})
|
||||
.filter((rule) => rule.channel_type && rule.items.length > 0)
|
||||
}
|
||||
|
||||
export function CustomChannelTypes(): JSX.Element {
|
||||
const { updateCurrentTeam } = useActions(teamLogic)
|
||||
const { currentTeam } = useValues(teamLogic)
|
||||
const { reportCustomChannelTypeRulesUpdated } = useActions(eventUsageLogic)
|
||||
|
||||
const savedCustomChannelTypeRules =
|
||||
currentTeam?.modifiers?.customChannelTypeRules ?? currentTeam?.default_modifiers?.customChannelTypeRules ?? null
|
||||
const [customChannelTypeRules] = useState(() =>
|
||||
(savedCustomChannelTypeRules || []).map((rule) => {
|
||||
return {
|
||||
...rule,
|
||||
id: rule.id || uuid(),
|
||||
items: (rule.items || []).map((item) => {
|
||||
return {
|
||||
...item,
|
||||
id: item.id || uuid(),
|
||||
}
|
||||
}),
|
||||
}
|
||||
const [savedCustomChannelTypeRules, setSavedCustomChannelTypeRules] = useState(() =>
|
||||
sanitizeCustomChannelTypeRules(
|
||||
currentTeam?.modifiers?.customChannelTypeRules ??
|
||||
currentTeam?.default_modifiers?.customChannelTypeRules ??
|
||||
[]
|
||||
)
|
||||
)
|
||||
|
||||
const [customChannelTypeRules, setCustomChannelTypeRules] = useState(savedCustomChannelTypeRules)
|
||||
|
||||
const channelTypeOptions = useMemo((): LemonInputSelectOption[] => {
|
||||
const optionsSet = new Set<string>()
|
||||
customChannelTypeRules.forEach((rule) => {
|
||||
optionsSet.add(rule.channel_type)
|
||||
})
|
||||
)
|
||||
|
||||
const lastSavedRef = useRef<CustomChannelRule[]>(customChannelTypeRules)
|
||||
|
||||
const debouncedHandleChange = useMemo(
|
||||
() =>
|
||||
debounce(
|
||||
function handleChange(rules: CustomChannelRule[]): void {
|
||||
// strip conditions where the value is empty, and strip empty rules
|
||||
rules = rules
|
||||
.map((rule) => {
|
||||
return {
|
||||
...rule,
|
||||
conditions: rule.items.filter((condition) => condition.value !== ''),
|
||||
}
|
||||
})
|
||||
.filter((rule) => {
|
||||
return rule.conditions.length > 0 && rule.channel_type !== ''
|
||||
})
|
||||
|
||||
// don't update if the rules are the same as the last saved rules
|
||||
if (isEqual(rules, lastSavedRef.current)) {
|
||||
return
|
||||
}
|
||||
|
||||
updateCurrentTeam({ modifiers: { ...currentTeam?.modifiers, customChannelTypeRules: rules } })
|
||||
reportCustomChannelTypeRulesUpdated(rules.length)
|
||||
},
|
||||
500,
|
||||
{ trailing: true, maxWait: 2000 }
|
||||
),
|
||||
[updateCurrentTeam, reportCustomChannelTypeRulesUpdated, currentTeam?.modifiers]
|
||||
)
|
||||
Object.values(DefaultChannelTypes).forEach((channelType) => {
|
||||
optionsSet.add(channelType)
|
||||
})
|
||||
return Array.from(optionsSet).map((channelType) => ({ label: channelType, key: channelType }))
|
||||
}, [customChannelTypeRules])
|
||||
|
||||
return (
|
||||
<ChannelTypeEditor
|
||||
handleChange={debouncedHandleChange}
|
||||
initialCustomChannelTypeRules={customChannelTypeRules}
|
||||
/>
|
||||
<div>
|
||||
<ChannelTypeEditor
|
||||
handleChange={setCustomChannelTypeRules}
|
||||
initialCustomChannelTypeRules={customChannelTypeRules}
|
||||
channelTypeOptions={channelTypeOptions}
|
||||
onSave={() => {
|
||||
updateCurrentTeam({
|
||||
modifiers: { customChannelTypeRules: sanitizeCustomChannelTypeRules(customChannelTypeRules) },
|
||||
})
|
||||
reportCustomChannelTypeRulesUpdated(customChannelTypeRules.length)
|
||||
setSavedCustomChannelTypeRules(customChannelTypeRules)
|
||||
}}
|
||||
isSaveDisabled={isEqual(customChannelTypeRules, savedCustomChannelTypeRules)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export interface ChannelTypeEditorProps {
|
||||
handleChange: (rules: CustomChannelRule[]) => void
|
||||
initialCustomChannelTypeRules: CustomChannelRule[]
|
||||
channelTypeOptions: LemonInputSelectOption[]
|
||||
isSaveDisabled: boolean
|
||||
onSave: () => void
|
||||
}
|
||||
|
||||
export function ChannelTypeEditor({
|
||||
handleChange,
|
||||
initialCustomChannelTypeRules,
|
||||
channelTypeOptions,
|
||||
isSaveDisabled,
|
||||
onSave,
|
||||
}: ChannelTypeEditorProps): JSX.Element {
|
||||
return (
|
||||
<VerticalNestedDND<CustomChannelCondition, CustomChannelRule>
|
||||
initialItems={initialCustomChannelTypeRules}
|
||||
renderContainerItem={(rule, { updateContainerItem }) => {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div>
|
||||
Set Channel type to{' '}
|
||||
<div className="flex flex-col space-y-2">
|
||||
<div className="flex flex-row items-center space-x-2">
|
||||
<span>Set Channel type to</span>
|
||||
<LemonInputSelect
|
||||
className="flex-1"
|
||||
mode="single"
|
||||
allowCustomValues={true}
|
||||
value={[rule.channel_type]}
|
||||
@ -174,22 +192,27 @@ export function ChannelTypeEditor({
|
||||
channel_type: channelType[0],
|
||||
})
|
||||
}
|
||||
options={channelTypeOptions}
|
||||
placeholder="Enter a channel type name"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{rule.items.length <= 1 ? (
|
||||
'When'
|
||||
) : (
|
||||
<div>
|
||||
When{' '}
|
||||
<LemonSelect
|
||||
value={rule.combiner}
|
||||
options={combinerOptions}
|
||||
onChange={(combiner) => updateContainerItem({ ...rule, combiner })}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{rule.items.length > 0 ? (
|
||||
<div>
|
||||
{rule.items.length == 1 ? (
|
||||
'when this condition is met'
|
||||
) : (
|
||||
<div className="flex flex-row items-center space-x-2">
|
||||
<span>When</span>
|
||||
<LemonSelect
|
||||
value={rule.combiner}
|
||||
options={combinerOptions}
|
||||
onChange={(combiner) => updateContainerItem({ ...rule, combiner })}
|
||||
/>
|
||||
<span>conditions are met</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
@ -211,18 +234,44 @@ export function ChannelTypeEditor({
|
||||
{isNullary(rule.op) ? null : (
|
||||
<PropertyValue
|
||||
key={rule.key}
|
||||
propertyKey={keyToSessionproperty(rule.key)}
|
||||
propertyKey={keyToSessionProperty(rule.key)}
|
||||
type={PropertyFilterType.Session}
|
||||
onSet={(propertyValue: any) => {
|
||||
updateChildItem({ ...rule, value: propertyValue })
|
||||
}}
|
||||
operator={opToPropertyOperator(rule.op)}
|
||||
value={rule.value}
|
||||
placeholder="Enter a value"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
renderAddChildItem={(rule, { onAddChild }) => {
|
||||
return (
|
||||
<LemonButton type="primary" onClick={() => onAddChild(rule.id)} icon={<IconPlus />}>
|
||||
Add condition
|
||||
</LemonButton>
|
||||
)
|
||||
}}
|
||||
renderAddContainerItem={({ onAddContainer }) => {
|
||||
return (
|
||||
<LemonButton type="primary" onClick={onAddContainer} icon={<IconPlus />}>
|
||||
Add rule
|
||||
</LemonButton>
|
||||
)
|
||||
}}
|
||||
renderAdditionalControls={() => {
|
||||
return (
|
||||
<LemonButton
|
||||
onClick={onSave}
|
||||
disabledReason={isSaveDisabled ? 'No changes to save' : undefined}
|
||||
type="primary"
|
||||
>
|
||||
Save custom channel type rules
|
||||
</LemonButton>
|
||||
)
|
||||
}}
|
||||
createNewContainerItem={() => {
|
||||
return {
|
||||
id: uuid(),
|
||||
@ -243,7 +292,7 @@ export function ChannelTypeEditor({
|
||||
id: uuid(),
|
||||
key: CustomChannelField.ReferringDomain,
|
||||
op: CustomChannelOperator.Exact,
|
||||
value: '',
|
||||
value: [],
|
||||
}
|
||||
}}
|
||||
onChange={handleChange}
|
@ -147,7 +147,7 @@ def custom_condition_to_expr(
|
||||
|
||||
def custom_rule_to_expr(custom_rule: CustomChannelRule, source_exprs: ChannelTypeExprs) -> ast.Expr:
|
||||
conditions: list[Union[ast.Expr | ast.Call]] = []
|
||||
for condition in custom_rule.conditions:
|
||||
for condition in custom_rule.items:
|
||||
if condition.key == CustomChannelField.UTM_SOURCE:
|
||||
expr = source_exprs.source
|
||||
elif condition.key == CustomChannelField.UTM_MEDIUM:
|
||||
|
Loading…
Reference in New Issue
Block a user