mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Constraint values list (#9592)
This commit is contained in:
		
							parent
							
								
									e018ee2f34
								
							
						
					
					
						commit
						dd62b3dbcd
					
				@ -1,12 +1,14 @@
 | 
			
		||||
import type { FC } from 'react';
 | 
			
		||||
import {
 | 
			
		||||
    StrategyEvaluationItem,
 | 
			
		||||
    type StrategyEvaluationItemProps,
 | 
			
		||||
} from '../StrategyEvaluationItem/StrategyEvaluationItem';
 | 
			
		||||
import type { ComponentProps, FC } from 'react';
 | 
			
		||||
import { StrategyEvaluationItem } from '../StrategyEvaluationItem/StrategyEvaluationItem';
 | 
			
		||||
import type { ConstraintSchema } from 'openapi';
 | 
			
		||||
import { formatOperatorDescription } from 'component/common/ConstraintAccordion/ConstraintOperator/formatOperatorDescription';
 | 
			
		||||
import { StrategyEvaluationChip } from '../StrategyEvaluationChip/StrategyEvaluationChip';
 | 
			
		||||
import { styled, Tooltip } from '@mui/material';
 | 
			
		||||
import { Truncator } from 'component/common/Truncator/Truncator';
 | 
			
		||||
import { ValuesList } from '../ValuesList/ValuesList';
 | 
			
		||||
import { useLocationSettings } from 'hooks/useLocationSettings';
 | 
			
		||||
import { formatConstraintValue } from 'utils/formatConstraintValue';
 | 
			
		||||
import { useConstraintTooltips } from './hooks/useConstraintTooltips';
 | 
			
		||||
import { ReactComponent as CaseSensitiveIcon } from 'assets/icons/case-sensitive.svg';
 | 
			
		||||
import { isCaseSensitive } from './isCaseSensitive';
 | 
			
		||||
 | 
			
		||||
@ -52,32 +54,45 @@ const StyledOperatorGroup = styled('div')(({ theme }) => ({
 | 
			
		||||
    gap: theme.spacing(0.5),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledConstraintName = styled('div')(({ theme }) => ({
 | 
			
		||||
    maxWidth: '150px',
 | 
			
		||||
    paddingRight: theme.spacing(0.5),
 | 
			
		||||
    overflow: 'hidden',
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
export const ConstraintItemHeader: FC<
 | 
			
		||||
    ConstraintSchema & Pick<StrategyEvaluationItemProps, 'onSetTruncated'>
 | 
			
		||||
> = ({
 | 
			
		||||
    caseInsensitive,
 | 
			
		||||
    contextName,
 | 
			
		||||
    inverted,
 | 
			
		||||
    operator,
 | 
			
		||||
    value,
 | 
			
		||||
    values,
 | 
			
		||||
    onSetTruncated,
 | 
			
		||||
}) => {
 | 
			
		||||
    const items = value ? [value, ...(values || [])] : values || [];
 | 
			
		||||
    ConstraintSchema & Pick<ComponentProps<typeof ValuesList>, 'onSetTruncated'>
 | 
			
		||||
> = ({ onSetTruncated, ...constraint }) => {
 | 
			
		||||
    const { caseInsensitive, contextName, inverted, operator, value, values } =
 | 
			
		||||
        constraint;
 | 
			
		||||
    const { locationSettings } = useLocationSettings();
 | 
			
		||||
    const items = value
 | 
			
		||||
        ? [
 | 
			
		||||
              formatConstraintValue(constraint, locationSettings) || '',
 | 
			
		||||
              ...(values || []),
 | 
			
		||||
          ]
 | 
			
		||||
        : values || [];
 | 
			
		||||
 | 
			
		||||
    const tooltips = useConstraintTooltips(contextName, values || []);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <StrategyEvaluationItem
 | 
			
		||||
            type='Constraint'
 | 
			
		||||
            values={items}
 | 
			
		||||
            onSetTruncated={onSetTruncated}
 | 
			
		||||
        >
 | 
			
		||||
            {contextName}
 | 
			
		||||
        <StrategyEvaluationItem type='Constraint'>
 | 
			
		||||
            <StyledConstraintName>
 | 
			
		||||
                <Truncator lines={2} title={contextName} arrow>
 | 
			
		||||
                    {contextName}
 | 
			
		||||
                </Truncator>
 | 
			
		||||
            </StyledConstraintName>
 | 
			
		||||
            <StyledOperatorGroup>
 | 
			
		||||
                <Operator label={operator} inverted={inverted} />
 | 
			
		||||
                {isCaseSensitive(operator, caseInsensitive) ? (
 | 
			
		||||
                    <CaseSensitive />
 | 
			
		||||
                ) : null}
 | 
			
		||||
            </StyledOperatorGroup>
 | 
			
		||||
            <ValuesList
 | 
			
		||||
                values={items}
 | 
			
		||||
                onSetTruncated={onSetTruncated}
 | 
			
		||||
                tooltips={tooltips}
 | 
			
		||||
            />
 | 
			
		||||
        </StrategyEvaluationItem>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,99 @@
 | 
			
		||||
import { vi } from 'vitest';
 | 
			
		||||
import { renderHook } from '@testing-library/react';
 | 
			
		||||
import { useConstraintTooltips } from './useConstraintTooltips';
 | 
			
		||||
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
 | 
			
		||||
 | 
			
		||||
vi.mock('hooks/api/getters/useUnleashContext/useUnleashContext', () => ({
 | 
			
		||||
    default: vi.fn(),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
describe('useConstraintTooltips', () => {
 | 
			
		||||
    beforeEach(() => {
 | 
			
		||||
        vi.resetAllMocks();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('returns tooltip mapping for legal values with descriptions', () => {
 | 
			
		||||
        (
 | 
			
		||||
            useUnleashContext as unknown as ReturnType<typeof vi.fn>
 | 
			
		||||
        ).mockReturnValue({
 | 
			
		||||
            context: [
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'contextA',
 | 
			
		||||
                    description: 'Test context A',
 | 
			
		||||
                    createdAt: '2021-01-01',
 | 
			
		||||
                    sortOrder: 1,
 | 
			
		||||
                    stickiness: false,
 | 
			
		||||
                    legalValues: [
 | 
			
		||||
                        { value: 'value1', description: 'Tooltip 1' },
 | 
			
		||||
                        { value: 'value2', description: 'Tooltip 2' },
 | 
			
		||||
                        { value: 'value3' }, // No description provided
 | 
			
		||||
                    ],
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const { result } = renderHook(() =>
 | 
			
		||||
            useConstraintTooltips('contextA', [
 | 
			
		||||
                'value1',
 | 
			
		||||
                'value2',
 | 
			
		||||
                'value3',
 | 
			
		||||
                'nonExisting',
 | 
			
		||||
            ]),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        expect(result.current).toEqual({
 | 
			
		||||
            value1: 'Tooltip 1',
 | 
			
		||||
            value2: 'Tooltip 2',
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('returns an empty object when the context is not found', () => {
 | 
			
		||||
        (
 | 
			
		||||
            useUnleashContext as unknown as ReturnType<typeof vi.fn>
 | 
			
		||||
        ).mockReturnValue({
 | 
			
		||||
            context: [
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'otherContext',
 | 
			
		||||
                    description: 'Other context',
 | 
			
		||||
                    createdAt: '2021-01-01',
 | 
			
		||||
                    sortOrder: 1,
 | 
			
		||||
                    stickiness: false,
 | 
			
		||||
                    legalValues: [
 | 
			
		||||
                        { value: 'value1', description: 'Tooltip 1' },
 | 
			
		||||
                    ],
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const { result } = renderHook(() =>
 | 
			
		||||
            useConstraintTooltips('contextA', ['value1']),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        expect(result.current).toEqual({});
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('returns an empty object when no values are provided', () => {
 | 
			
		||||
        (
 | 
			
		||||
            useUnleashContext as unknown as ReturnType<typeof vi.fn>
 | 
			
		||||
        ).mockReturnValue({
 | 
			
		||||
            context: [
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'contextA',
 | 
			
		||||
                    description: 'Test context A',
 | 
			
		||||
                    createdAt: '2021-01-01',
 | 
			
		||||
                    sortOrder: 1,
 | 
			
		||||
                    stickiness: false,
 | 
			
		||||
                    legalValues: [
 | 
			
		||||
                        { value: 'value1', description: 'Tooltip 1' },
 | 
			
		||||
                    ],
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const { result } = renderHook(() =>
 | 
			
		||||
            useConstraintTooltips('contextA', []),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        expect(result.current).toEqual({});
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,27 @@
 | 
			
		||||
import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext';
 | 
			
		||||
import { useMemo } from 'react';
 | 
			
		||||
 | 
			
		||||
export const useConstraintTooltips = (
 | 
			
		||||
    contextName: string,
 | 
			
		||||
    values: string[],
 | 
			
		||||
) => {
 | 
			
		||||
    const { context } = useUnleashContext();
 | 
			
		||||
    const contextDefinition = useMemo(
 | 
			
		||||
        () => context.find(({ name }) => name === contextName),
 | 
			
		||||
        [contextName, context],
 | 
			
		||||
    );
 | 
			
		||||
    return useMemo<Record<string, string>>(
 | 
			
		||||
        () =>
 | 
			
		||||
            Object.fromEntries(
 | 
			
		||||
                values
 | 
			
		||||
                    ?.map((item) => [
 | 
			
		||||
                        item,
 | 
			
		||||
                        contextDefinition?.legalValues?.find(
 | 
			
		||||
                            ({ value }) => value === item,
 | 
			
		||||
                        )?.description,
 | 
			
		||||
                    ])
 | 
			
		||||
                    .filter(([_, tooltip]) => !!tooltip) || [],
 | 
			
		||||
            ),
 | 
			
		||||
        [context, values],
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
@ -1,16 +1,11 @@
 | 
			
		||||
import { styled } from '@mui/material';
 | 
			
		||||
import {
 | 
			
		||||
    Truncator,
 | 
			
		||||
    type TruncatorProps,
 | 
			
		||||
} from 'component/common/Truncator/Truncator';
 | 
			
		||||
import { disabledStrategyClassName } from 'component/common/StrategyItemContainer/disabled-strategy-utils';
 | 
			
		||||
import type { FC, ReactNode } from 'react';
 | 
			
		||||
import { styled } from '@mui/material';
 | 
			
		||||
import { disabledStrategyClassName } from 'component/common/StrategyItemContainer/disabled-strategy-utils';
 | 
			
		||||
 | 
			
		||||
export type StrategyEvaluationItemProps = {
 | 
			
		||||
    type?: ReactNode;
 | 
			
		||||
    children?: ReactNode;
 | 
			
		||||
    values?: string[];
 | 
			
		||||
} & Pick<TruncatorProps, 'onSetTruncated'>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const StyledContainer = styled('div')(({ theme }) => ({
 | 
			
		||||
    display: 'flex',
 | 
			
		||||
@ -38,35 +33,17 @@ const StyledType = styled('span')(({ theme }) => ({
 | 
			
		||||
    color: theme.palette.text.secondary,
 | 
			
		||||
    width: theme.spacing(10),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Abstract building block for a list of constraints, segments and other items inside a strategy
 | 
			
		||||
 */
 | 
			
		||||
export const StrategyEvaluationItem: FC<StrategyEvaluationItemProps> = ({
 | 
			
		||||
    type,
 | 
			
		||||
    children,
 | 
			
		||||
    values,
 | 
			
		||||
    onSetTruncated,
 | 
			
		||||
}) => (
 | 
			
		||||
    <StyledContainer>
 | 
			
		||||
        <StyledType>{type}</StyledType>
 | 
			
		||||
        <StyledContent>
 | 
			
		||||
            {children}
 | 
			
		||||
            {values && values?.length === 1 ? (
 | 
			
		||||
                <Truncator
 | 
			
		||||
                    title={values[0]}
 | 
			
		||||
                    arrow
 | 
			
		||||
                    lines={2}
 | 
			
		||||
                    onSetTruncated={() => onSetTruncated?.(false)}
 | 
			
		||||
                >
 | 
			
		||||
                    {values[0]}
 | 
			
		||||
                </Truncator>
 | 
			
		||||
            ) : null}
 | 
			
		||||
            {values && values?.length > 1 ? (
 | 
			
		||||
                <Truncator title='' lines={2} onSetTruncated={onSetTruncated}>
 | 
			
		||||
                    {values.join(', ')}
 | 
			
		||||
                </Truncator>
 | 
			
		||||
            ) : null}
 | 
			
		||||
        </StyledContent>
 | 
			
		||||
    </StyledContainer>
 | 
			
		||||
);
 | 
			
		||||
}) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <StyledContainer>
 | 
			
		||||
            <StyledType>{type}</StyledType>
 | 
			
		||||
            <StyledContent>{children}</StyledContent>
 | 
			
		||||
        </StyledContainer>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,66 @@
 | 
			
		||||
import type { FC } from 'react';
 | 
			
		||||
import { styled, Tooltip } from '@mui/material';
 | 
			
		||||
import {
 | 
			
		||||
    Truncator,
 | 
			
		||||
    type TruncatorProps,
 | 
			
		||||
} from 'component/common/Truncator/Truncator';
 | 
			
		||||
 | 
			
		||||
export type ValuesListProps = {
 | 
			
		||||
    values?: string[];
 | 
			
		||||
    tooltips?: Record<string, string | undefined>;
 | 
			
		||||
} & Pick<TruncatorProps, 'onSetTruncated'>;
 | 
			
		||||
 | 
			
		||||
const StyledValuesContainer = styled('div')({
 | 
			
		||||
    flex: '1 1 0',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const StyledValueItem = styled('span')(({ theme }) => ({
 | 
			
		||||
    padding: theme.spacing(0.25),
 | 
			
		||||
    display: 'inline-block',
 | 
			
		||||
    span: {
 | 
			
		||||
        background: theme.palette.background.elevation2,
 | 
			
		||||
        borderRadius: theme.shape.borderRadiusLarge,
 | 
			
		||||
        display: 'inline-block',
 | 
			
		||||
        padding: theme.spacing(0.25, 1),
 | 
			
		||||
    },
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledSingleValue = styled('div')(({ theme }) => ({
 | 
			
		||||
    padding: theme.spacing(0.25, 1),
 | 
			
		||||
    background: theme.palette.background.elevation2,
 | 
			
		||||
    borderRadius: theme.shape.borderRadiusLarge,
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
export const ValuesList: FC<ValuesListProps> = ({
 | 
			
		||||
    values,
 | 
			
		||||
    tooltips,
 | 
			
		||||
    onSetTruncated,
 | 
			
		||||
}) => (
 | 
			
		||||
    <StyledValuesContainer>
 | 
			
		||||
        {values && values?.length === 1 ? (
 | 
			
		||||
            <StyledSingleValue>
 | 
			
		||||
                <Truncator
 | 
			
		||||
                    title={values[0]}
 | 
			
		||||
                    arrow
 | 
			
		||||
                    lines={2}
 | 
			
		||||
                    onSetTruncated={() => onSetTruncated?.(false)}
 | 
			
		||||
                >
 | 
			
		||||
                    <Tooltip title={tooltips?.[values[0]] || ''}>
 | 
			
		||||
                        <span>{values[0]}</span>
 | 
			
		||||
                    </Tooltip>
 | 
			
		||||
                </Truncator>
 | 
			
		||||
            </StyledSingleValue>
 | 
			
		||||
        ) : null}
 | 
			
		||||
        {values && values?.length > 1 ? (
 | 
			
		||||
            <Truncator title='' lines={2} onSetTruncated={onSetTruncated}>
 | 
			
		||||
                {values.map((value) => (
 | 
			
		||||
                    <Tooltip title={tooltips?.[value] || ''} key={value}>
 | 
			
		||||
                        <StyledValueItem>
 | 
			
		||||
                            <span>{value}</span>
 | 
			
		||||
                        </StyledValueItem>
 | 
			
		||||
                    </Tooltip>
 | 
			
		||||
                ))}
 | 
			
		||||
            </Truncator>
 | 
			
		||||
        ) : null}
 | 
			
		||||
    </StyledValuesContainer>
 | 
			
		||||
);
 | 
			
		||||
@ -45,8 +45,7 @@ const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledAccordionDetails = styled(AccordionDetails)(({ theme }) => ({
 | 
			
		||||
    borderTop: `1px dashed ${theme.palette.divider}`,
 | 
			
		||||
    padding: theme.spacing(1.5, 3, 2.5),
 | 
			
		||||
    padding: theme.spacing(0.5, 3, 2.5),
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const StyledLink = styled(Link)({
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,7 @@ import type {
 | 
			
		||||
    StrategySchemaParametersItem,
 | 
			
		||||
} from 'openapi';
 | 
			
		||||
import type { IFeatureStrategyPayload } from 'interfaces/strategy';
 | 
			
		||||
import { ValuesList } from 'component/common/ConstraintsList/ValuesList/ValuesList';
 | 
			
		||||
 | 
			
		||||
export const useCustomStrategyParameters = (
 | 
			
		||||
    strategy: Pick<
 | 
			
		||||
@ -48,14 +49,11 @@ export const useCustomStrategyParameters = (
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return (
 | 
			
		||||
                    <StrategyEvaluationItem
 | 
			
		||||
                        key={key}
 | 
			
		||||
                        type={typeItem}
 | 
			
		||||
                        values={values}
 | 
			
		||||
                    >
 | 
			
		||||
                    <StrategyEvaluationItem key={key} type={typeItem}>
 | 
			
		||||
                        {values.length === 1
 | 
			
		||||
                            ? 'has 1 item:'
 | 
			
		||||
                            : `has ${values.length} items:`}
 | 
			
		||||
                        <ValuesList values={values} />
 | 
			
		||||
                    </StrategyEvaluationItem>
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
@ -82,12 +80,11 @@ export const useCustomStrategyParameters = (
 | 
			
		||||
                const value = parseParameterString(parameters[name]);
 | 
			
		||||
 | 
			
		||||
                return (
 | 
			
		||||
                    <StrategyEvaluationItem
 | 
			
		||||
                        key={key}
 | 
			
		||||
                        type={typeItem}
 | 
			
		||||
                        values={value === '' ? undefined : [value]}
 | 
			
		||||
                    >
 | 
			
		||||
                    <StrategyEvaluationItem key={key} type={typeItem}>
 | 
			
		||||
                        {value === '' ? 'is an empty string' : 'is set to'}
 | 
			
		||||
                        <ValuesList
 | 
			
		||||
                            values={value === '' ? undefined : [value]}
 | 
			
		||||
                        />
 | 
			
		||||
                    </StrategyEvaluationItem>
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
@ -95,12 +92,9 @@ export const useCustomStrategyParameters = (
 | 
			
		||||
            case 'number': {
 | 
			
		||||
                const value = parseParameterNumber(parameters[name]);
 | 
			
		||||
                return (
 | 
			
		||||
                    <StrategyEvaluationItem
 | 
			
		||||
                        key={key}
 | 
			
		||||
                        type={typeItem}
 | 
			
		||||
                        values={[`${value}`]}
 | 
			
		||||
                    >
 | 
			
		||||
                    <StrategyEvaluationItem key={key} type={typeItem}>
 | 
			
		||||
                        is a number set to
 | 
			
		||||
                        <ValuesList values={[`${value}`]} />
 | 
			
		||||
                    </StrategyEvaluationItem>
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,9 @@
 | 
			
		||||
import { useMemo } from 'react';
 | 
			
		||||
import { parseParameterStrings } from 'utils/parseParameter';
 | 
			
		||||
import { StrategyEvaluationItem } from 'component/common/ConstraintsList/StrategyEvaluationItem/StrategyEvaluationItem';
 | 
			
		||||
import type { FeatureStrategySchema } from 'openapi';
 | 
			
		||||
import { RolloutParameter } from '../RolloutParameter/RolloutParameter';
 | 
			
		||||
import { ValuesList } from 'component/common/ConstraintsList/ValuesList/ValuesList';
 | 
			
		||||
import { parseParameterStrings } from 'utils/parseParameter';
 | 
			
		||||
 | 
			
		||||
export const useStrategyParameters = (
 | 
			
		||||
    strategy: Partial<
 | 
			
		||||
@ -36,11 +37,9 @@ export const useStrategyParameters = (
 | 
			
		||||
 | 
			
		||||
        if (['userids', 'hostnames', 'ips'].includes(type)) {
 | 
			
		||||
            return (
 | 
			
		||||
                <StrategyEvaluationItem
 | 
			
		||||
                    key={key}
 | 
			
		||||
                    type={key}
 | 
			
		||||
                    values={parseParameterStrings(value)}
 | 
			
		||||
                />
 | 
			
		||||
                <StrategyEvaluationItem key={key} type={key}>
 | 
			
		||||
                    <ValuesList values={parseParameterStrings(value)} />
 | 
			
		||||
                </StrategyEvaluationItem>
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,10 @@
 | 
			
		||||
import type { IConstraint } from 'interfaces/strategy';
 | 
			
		||||
import { formatDateYMDHMS } from 'utils/formatDate';
 | 
			
		||||
import type { ILocationSettings } from 'hooks/useLocationSettings';
 | 
			
		||||
import { CURRENT_TIME_CONTEXT_FIELD } from 'utils/operatorsForContext';
 | 
			
		||||
import type { ConstraintSchema } from 'openapi';
 | 
			
		||||
 | 
			
		||||
export const formatConstraintValue = (
 | 
			
		||||
    constraint: IConstraint,
 | 
			
		||||
    constraint: Pick<ConstraintSchema, 'value' | 'contextName'>,
 | 
			
		||||
    locationSettings: ILocationSettings,
 | 
			
		||||
): string | undefined => {
 | 
			
		||||
    if (
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user