mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-03 01:18:43 +02:00
Feat: context field search and filter improvements (#6732)
Adds highlighting to search values Search also looks in `description` behind a flag - it could possibly degrade performance when too many items. Tested with 200 and it's ok but anything above might degrade: Adds a Select/Unselect all button Shows the selected values above the search Closes # [1-2232](https://linear.app/unleash/issue/1-2232/context-field-ui-filter-and-search) https://github.com/Unleash/unleash/assets/104830839/ba2fe56f-c5db-4ce7-bc3c-1e7988682984 --------- Signed-off-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
parent
11f4155d5a
commit
c868b5a868
@ -1,6 +1,5 @@
|
|||||||
import { TextField, InputAdornment, Chip } from '@mui/material';
|
import { TextField, InputAdornment } from '@mui/material';
|
||||||
import Search from '@mui/icons-material/Search';
|
import Search from '@mui/icons-material/Search';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
|
|
||||||
interface IConstraintValueSearchProps {
|
interface IConstraintValueSearchProps {
|
||||||
filter: string;
|
filter: string;
|
||||||
@ -13,7 +12,7 @@ export const ConstraintValueSearch = ({
|
|||||||
}: IConstraintValueSearchProps) => {
|
}: IConstraintValueSearchProps) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
<div style={{ width: '300px' }}>
|
<div style={{ width: '100%' }}>
|
||||||
<TextField
|
<TextField
|
||||||
label='Search'
|
label='Search'
|
||||||
name='search'
|
name='search'
|
||||||
@ -35,16 +34,6 @@ export const ConstraintValueSearch = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ConditionallyRender
|
|
||||||
condition={Boolean(filter)}
|
|
||||||
show={
|
|
||||||
<Chip
|
|
||||||
style={{ marginLeft: '1rem' }}
|
|
||||||
label={`filter active: ${filter}`}
|
|
||||||
onDelete={() => setFilter('')}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -4,8 +4,6 @@ import { styled } from '@mui/material';
|
|||||||
const StyledHeader = styled('h3')(({ theme }) => ({
|
const StyledHeader = styled('h3')(({ theme }) => ({
|
||||||
fontSize: theme.fontSizes.bodySize,
|
fontSize: theme.fontSizes.bodySize,
|
||||||
fontWeight: theme.typography.fontWeightRegular,
|
fontWeight: theme.typography.fontWeightRegular,
|
||||||
marginTop: theme.spacing(2),
|
|
||||||
marginBottom: theme.spacing(0.5),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const ConstraintFormHeader: React.FC<
|
export const ConstraintFormHeader: React.FC<
|
||||||
|
@ -9,7 +9,7 @@ export const StyledContainer = styled('div')(({ theme }) => ({
|
|||||||
borderRadius: theme.shape.borderRadius,
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
border: `2px solid ${theme.palette.primary.main}`,
|
border: `1px solid ${theme.palette.primary.main}`,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -6,13 +6,19 @@ import {
|
|||||||
} from './LegalValueLabel.styles';
|
} from './LegalValueLabel.styles';
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { FormControlLabel } from '@mui/material';
|
import { FormControlLabel } from '@mui/material';
|
||||||
|
import { Highlighter } from 'component/common/Highlighter/Highlighter';
|
||||||
|
|
||||||
interface ILegalValueTextProps {
|
interface ILegalValueTextProps {
|
||||||
legal: ILegalValue;
|
legal: ILegalValue;
|
||||||
control: React.ReactElement;
|
control: React.ReactElement;
|
||||||
|
filter?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LegalValueLabel = ({ legal, control }: ILegalValueTextProps) => {
|
export const LegalValueLabel = ({
|
||||||
|
legal,
|
||||||
|
control,
|
||||||
|
filter,
|
||||||
|
}: ILegalValueTextProps) => {
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
@ -20,9 +26,15 @@ export const LegalValueLabel = ({ legal, control }: ILegalValueTextProps) => {
|
|||||||
control={control}
|
control={control}
|
||||||
label={
|
label={
|
||||||
<>
|
<>
|
||||||
<StyledValue>{legal.value}</StyledValue>
|
<StyledValue>
|
||||||
|
<Highlighter search={filter}>
|
||||||
|
{legal.value}
|
||||||
|
</Highlighter>
|
||||||
|
</StyledValue>
|
||||||
<StyledDescription>
|
<StyledDescription>
|
||||||
|
<Highlighter search={filter}>
|
||||||
{legal.description}
|
{legal.description}
|
||||||
|
</Highlighter>
|
||||||
</StyledDescription>
|
</StyledDescription>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@ -36,6 +48,9 @@ export const filterLegalValues = (
|
|||||||
filter: string,
|
filter: string,
|
||||||
): ILegalValue[] => {
|
): ILegalValue[] => {
|
||||||
return legalValues.filter((legalValue) => {
|
return legalValues.filter((legalValue) => {
|
||||||
return legalValue.value.includes(filter);
|
return (
|
||||||
|
legalValue.value.toLowerCase().includes(filter.toLowerCase()) ||
|
||||||
|
legalValue.description?.toLowerCase().includes(filter.toLowerCase())
|
||||||
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
import { filterLegalValues } from './LegalValueLabel';
|
||||||
|
|
||||||
|
describe('filterLegalValues function tests', () => {
|
||||||
|
const mockLegalValues = [
|
||||||
|
{ value: 'Apple', description: 'A fruit' },
|
||||||
|
{ value: 'Banana', description: 'Yellow fruit' },
|
||||||
|
{ value: 'Carrot', description: 'A vegetable' },
|
||||||
|
{ value: 'SE', description: 'Sweden' },
|
||||||
|
{ value: 'Eggplant', description: undefined },
|
||||||
|
];
|
||||||
|
|
||||||
|
test('Basic functionality with value property', () => {
|
||||||
|
const filter = 'apple';
|
||||||
|
const expected = [{ value: 'Apple', description: 'A fruit' }];
|
||||||
|
expect(filterLegalValues(mockLegalValues, filter)).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Filters based on description property', () => {
|
||||||
|
const filter = 'vegetable';
|
||||||
|
const expected = [{ value: 'Carrot', description: 'A vegetable' }];
|
||||||
|
expect(filterLegalValues(mockLegalValues, filter)).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Case insensitivity', () => {
|
||||||
|
const filter = 'BANANA';
|
||||||
|
const expected = [{ value: 'Banana', description: 'Yellow fruit' }];
|
||||||
|
expect(filterLegalValues(mockLegalValues, filter)).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('No matches found', () => {
|
||||||
|
const filter = 'Zucchini';
|
||||||
|
expect(filterLegalValues(mockLegalValues, filter)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Empty filter string', () => {
|
||||||
|
const filter = '';
|
||||||
|
expect(filterLegalValues(mockLegalValues, filter)).toEqual(
|
||||||
|
mockLegalValues,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Special characters in filter', () => {
|
||||||
|
const filter = 'a fruit';
|
||||||
|
const expected = [{ value: 'Apple', description: 'A fruit' }];
|
||||||
|
expect(filterLegalValues(mockLegalValues, filter)).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Empty input array', () => {
|
||||||
|
const filter = 'anything';
|
||||||
|
expect(filterLegalValues([], filter)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Exact match', () => {
|
||||||
|
const filter = 'Carrot';
|
||||||
|
const expected = [{ value: 'Carrot', description: 'A vegetable' }];
|
||||||
|
expect(filterLegalValues(mockLegalValues, filter)).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Partial match', () => {
|
||||||
|
const filter = 'sw';
|
||||||
|
const expected = [{ value: 'SE', description: 'Sweden' }];
|
||||||
|
expect(filterLegalValues(mockLegalValues, filter)).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Combination of match and no match', () => {
|
||||||
|
const filter = 'a';
|
||||||
|
const expected = [
|
||||||
|
{ value: 'Apple', description: 'A fruit' },
|
||||||
|
{ value: 'Banana', description: 'Yellow fruit' },
|
||||||
|
{ value: 'Carrot', description: 'A vegetable' },
|
||||||
|
{ value: 'Eggplant', description: undefined },
|
||||||
|
];
|
||||||
|
expect(filterLegalValues(mockLegalValues, filter)).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
@ -1,6 +1,11 @@
|
|||||||
import { render } from 'utils/testRenderer';
|
import { render } from 'utils/testRenderer';
|
||||||
import { screen } from '@testing-library/react';
|
import { fireEvent, screen } from '@testing-library/react';
|
||||||
import { RestrictiveLegalValues } from './RestrictiveLegalValues';
|
import { RestrictiveLegalValues } from './RestrictiveLegalValues';
|
||||||
|
import { vi } from 'vitest';
|
||||||
|
|
||||||
|
vi.mock('../../../../../../hooks/useUiFlag', () => ({
|
||||||
|
useUiFlag: vi.fn(() => true),
|
||||||
|
}));
|
||||||
|
|
||||||
test('should show alert when you have illegal legal values', async () => {
|
test('should show alert when you have illegal legal values', async () => {
|
||||||
const contextDefinitionValues = [{ value: 'value1' }, { value: 'value2' }];
|
const contextDefinitionValues = [{ value: 'value1' }, { value: 'value2' }];
|
||||||
@ -52,3 +57,65 @@ test('Should remove illegal legal values from internal value state when mounting
|
|||||||
|
|
||||||
expect(localValues).toEqual(['value2']);
|
expect(localValues).toEqual(['value2']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Should select all', async () => {
|
||||||
|
const contextDefinitionValues = [{ value: 'value1' }, { value: 'value2' }];
|
||||||
|
let localValues: string[] = [];
|
||||||
|
|
||||||
|
const setValuesWithRecord = (values: string[]) => {
|
||||||
|
localValues = values;
|
||||||
|
};
|
||||||
|
|
||||||
|
render(
|
||||||
|
<RestrictiveLegalValues
|
||||||
|
data={{
|
||||||
|
legalValues: contextDefinitionValues,
|
||||||
|
deletedLegalValues: [{ value: 'value3' }],
|
||||||
|
}}
|
||||||
|
constraintValues={[]}
|
||||||
|
values={localValues}
|
||||||
|
setValues={() => {}}
|
||||||
|
setValuesWithRecord={setValuesWithRecord}
|
||||||
|
error={''}
|
||||||
|
setError={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedAllButton = await screen.findByText(/Select all/i);
|
||||||
|
|
||||||
|
console.log(selectedAllButton);
|
||||||
|
|
||||||
|
fireEvent.click(selectedAllButton);
|
||||||
|
expect(localValues).toEqual(['value1', 'value2']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should unselect all', async () => {
|
||||||
|
const contextDefinitionValues = [{ value: 'value1' }, { value: 'value2' }];
|
||||||
|
let localValues: string[] = ['value1', 'value2'];
|
||||||
|
|
||||||
|
const setValuesWithRecord = (values: string[]) => {
|
||||||
|
localValues = values;
|
||||||
|
};
|
||||||
|
|
||||||
|
render(
|
||||||
|
<RestrictiveLegalValues
|
||||||
|
data={{
|
||||||
|
legalValues: contextDefinitionValues,
|
||||||
|
deletedLegalValues: [{ value: 'value3' }],
|
||||||
|
}}
|
||||||
|
constraintValues={[]}
|
||||||
|
values={localValues}
|
||||||
|
setValues={() => {}}
|
||||||
|
setValuesWithRecord={setValuesWithRecord}
|
||||||
|
error={''}
|
||||||
|
setError={() => {}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedAllButton = await screen.findByText(/Unselect all/i);
|
||||||
|
|
||||||
|
console.log(selectedAllButton);
|
||||||
|
|
||||||
|
fireEvent.click(selectedAllButton);
|
||||||
|
expect(localValues).toEqual([]);
|
||||||
|
});
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { Alert, Checkbox, styled } from '@mui/material';
|
import { Alert, Button, Checkbox, Chip, Stack, styled } from '@mui/material';
|
||||||
import { useThemeStyles } from 'themes/themeStyles';
|
|
||||||
import { ConstraintValueSearch } from 'component/common/ConstraintAccordion/ConstraintValueSearch/ConstraintValueSearch';
|
import { ConstraintValueSearch } from 'component/common/ConstraintAccordion/ConstraintValueSearch/ConstraintValueSearch';
|
||||||
import { ConstraintFormHeader } from '../ConstraintFormHeader/ConstraintFormHeader';
|
import { ConstraintFormHeader } from '../ConstraintFormHeader/ConstraintFormHeader';
|
||||||
import type { ILegalValue } from 'interfaces/context';
|
import type { ILegalValue } from 'interfaces/context';
|
||||||
@ -9,6 +8,7 @@ import {
|
|||||||
filterLegalValues,
|
filterLegalValues,
|
||||||
LegalValueLabel,
|
LegalValueLabel,
|
||||||
} from '../LegalValueLabel/LegalValueLabel';
|
} from '../LegalValueLabel/LegalValueLabel';
|
||||||
|
import { useUiFlag } from 'hooks/useUiFlag';
|
||||||
|
|
||||||
interface IRestrictiveLegalValuesProps {
|
interface IRestrictiveLegalValuesProps {
|
||||||
data: {
|
data: {
|
||||||
@ -60,6 +60,16 @@ const StyledValuesContainer = styled('div')(({ theme }) => ({
|
|||||||
maxHeight: '378px',
|
maxHeight: '378px',
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
}));
|
}));
|
||||||
|
const StyledStack = styled(Stack)(({ theme }) => ({
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
marginBottom: theme.spacing(0.5),
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const ErrorText = styled('p')(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.smallBody,
|
||||||
|
color: theme.palette.error.main,
|
||||||
|
}));
|
||||||
|
|
||||||
export const RestrictiveLegalValues = ({
|
export const RestrictiveLegalValues = ({
|
||||||
data,
|
data,
|
||||||
@ -77,7 +87,8 @@ export const RestrictiveLegalValues = ({
|
|||||||
|
|
||||||
// Lazily initialise the values because there might be a lot of them.
|
// Lazily initialise the values because there might be a lot of them.
|
||||||
const [valuesMap, setValuesMap] = useState(() => createValuesMap(values));
|
const [valuesMap, setValuesMap] = useState(() => createValuesMap(values));
|
||||||
const { classes: styles } = useThemeStyles();
|
|
||||||
|
const newContextFieldsUI = useUiFlag('newContextFieldsUI');
|
||||||
|
|
||||||
const cleanDeletedLegalValues = (constraintValues: string[]): string[] => {
|
const cleanDeletedLegalValues = (constraintValues: string[]): string[] => {
|
||||||
const deletedValuesSet = getLegalValueSet(deletedLegalValues);
|
const deletedValuesSet = getLegalValueSet(deletedLegalValues);
|
||||||
@ -116,6 +127,19 @@ export const RestrictiveLegalValues = ({
|
|||||||
setValuesWithRecord([...cleanDeletedLegalValues(values), legalValue]);
|
setValuesWithRecord([...cleanDeletedLegalValues(values), legalValue]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isAllSelected = legalValues.every((value) =>
|
||||||
|
values.includes(value.value),
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSelectAll = () => {
|
||||||
|
if (isAllSelected) {
|
||||||
|
return setValuesWithRecord([]);
|
||||||
|
}
|
||||||
|
setValuesWithRecord([
|
||||||
|
...legalValues.map((legalValue) => legalValue.value),
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
@ -134,17 +158,46 @@ export const RestrictiveLegalValues = ({
|
|||||||
</Alert>
|
</Alert>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<StyledStack direction={'row'}>
|
||||||
<ConstraintFormHeader>
|
<ConstraintFormHeader>
|
||||||
Select values from a predefined set
|
Select values from a predefined set
|
||||||
</ConstraintFormHeader>
|
</ConstraintFormHeader>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={newContextFieldsUI}
|
||||||
|
show={
|
||||||
|
<Button variant={'text'} onClick={onSelectAll}>
|
||||||
|
{isAllSelected ? 'Unselect all' : 'Select all'}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledStack>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={legalValues.length > 100}
|
condition={legalValues.length > 100}
|
||||||
show={
|
show={
|
||||||
|
<>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={
|
||||||
|
Boolean(newContextFieldsUI) && Boolean(values)
|
||||||
|
}
|
||||||
|
show={
|
||||||
|
<StyledValuesContainer sx={{ border: 0 }}>
|
||||||
|
{values.map((value) => {
|
||||||
|
return (
|
||||||
|
<Chip
|
||||||
|
key={value}
|
||||||
|
label={value}
|
||||||
|
onDelete={() => onChange(value)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</StyledValuesContainer>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<ConstraintValueSearch
|
<ConstraintValueSearch
|
||||||
filter={filter}
|
filter={filter}
|
||||||
setFilter={setFilter}
|
setFilter={setFilter}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<StyledValuesContainer>
|
<StyledValuesContainer>
|
||||||
@ -152,6 +205,7 @@ export const RestrictiveLegalValues = ({
|
|||||||
<LegalValueLabel
|
<LegalValueLabel
|
||||||
key={match.value}
|
key={match.value}
|
||||||
legal={match}
|
legal={match}
|
||||||
|
filter={filter}
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={Boolean(valuesMap[match.value])}
|
checked={Boolean(valuesMap[match.value])}
|
||||||
@ -168,7 +222,7 @@ export const RestrictiveLegalValues = ({
|
|||||||
</StyledValuesContainer>
|
</StyledValuesContainer>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={Boolean(error)}
|
condition={Boolean(error)}
|
||||||
show={<p className={styles.error}>{error}</p>}
|
show={<ErrorText>{error}</ErrorText>}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -78,6 +78,7 @@ export type UiFlags = {
|
|||||||
outdatedSdksBanner?: boolean;
|
outdatedSdksBanner?: boolean;
|
||||||
projectOverviewRefactor?: string;
|
projectOverviewRefactor?: string;
|
||||||
collectTrafficDataUsage?: boolean;
|
collectTrafficDataUsage?: boolean;
|
||||||
|
newContextFieldsUI?: boolean;
|
||||||
variantDependencies?: boolean;
|
variantDependencies?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -128,6 +128,7 @@ exports[`should create default config 1`] = `
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"migrationLock": true,
|
"migrationLock": true,
|
||||||
|
"newContextFieldsUI": false,
|
||||||
"newStrategyConfigurationFeedback": false,
|
"newStrategyConfigurationFeedback": false,
|
||||||
"outdatedSdksBanner": false,
|
"outdatedSdksBanner": false,
|
||||||
"personalAccessTokensKillSwitch": false,
|
"personalAccessTokensKillSwitch": false,
|
||||||
|
@ -55,7 +55,8 @@ export type IFlagKey =
|
|||||||
| 'globalFrontendApiCache'
|
| 'globalFrontendApiCache'
|
||||||
| 'returnGlobalFrontendApiCache'
|
| 'returnGlobalFrontendApiCache'
|
||||||
| 'projectOverviewRefactor'
|
| 'projectOverviewRefactor'
|
||||||
| 'variantDependencies';
|
| 'variantDependencies'
|
||||||
|
| 'newContextFieldsUI';
|
||||||
|
|
||||||
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
||||||
|
|
||||||
@ -268,6 +269,10 @@ const flags: IFlags = {
|
|||||||
process.env.UNLEASH_EXPERIMENTAL_PROJECT_OVERVIEW_REFACTOR,
|
process.env.UNLEASH_EXPERIMENTAL_PROJECT_OVERVIEW_REFACTOR,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
newContextFieldsUI: parseEnvVarBoolean(
|
||||||
|
process.env.UNLEASH_EXPERIMENTAL_NEW_CONTEXT_FIELDS_UI,
|
||||||
|
false,
|
||||||
|
),
|
||||||
variantDependencies: parseEnvVarBoolean(
|
variantDependencies: parseEnvVarBoolean(
|
||||||
process.env.UNLEASH_EXPERIMENTAL_VARIANT_DEPENDENCIES,
|
process.env.UNLEASH_EXPERIMENTAL_VARIANT_DEPENDENCIES,
|
||||||
false,
|
false,
|
||||||
|
@ -52,6 +52,7 @@ process.nextTick(async () => {
|
|||||||
globalFrontendApiCache: true,
|
globalFrontendApiCache: true,
|
||||||
returnGlobalFrontendApiCache: false,
|
returnGlobalFrontendApiCache: false,
|
||||||
projectOverviewRefactor: true,
|
projectOverviewRefactor: true,
|
||||||
|
newContextFieldsUI: true,
|
||||||
variantDependencies: true,
|
variantDependencies: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user