mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-11 00:08:30 +01:00
feat: playground custom properties are nested (#4686)
This commit is contained in:
parent
31fcf603ac
commit
878780f068
@ -27,7 +27,10 @@ import { formatUnknownError } from 'utils/formatUnknownError';
|
|||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import { PlaygroundEditor } from './PlaygroundEditor/PlaygroundEditor';
|
import { PlaygroundEditor } from './PlaygroundEditor/PlaygroundEditor';
|
||||||
import { parseDateValue, parseValidDate } from 'component/common/util';
|
import { parseDateValue, parseValidDate } from 'component/common/util';
|
||||||
import { isStringOrStringArray } from '../../playground.utils';
|
import {
|
||||||
|
isStringOrStringArray,
|
||||||
|
normalizeCustomContextProperties,
|
||||||
|
} from '../../playground.utils';
|
||||||
interface IPlaygroundCodeFieldsetProps {
|
interface IPlaygroundCodeFieldsetProps {
|
||||||
context: string | undefined;
|
context: string | undefined;
|
||||||
setContext: Dispatch<SetStateAction<string | undefined>>;
|
setContext: Dispatch<SetStateAction<string | undefined>>;
|
||||||
@ -58,7 +61,10 @@ export const PlaygroundCodeFieldset: VFC<IPlaygroundCodeFieldsetProps> = ({
|
|||||||
try {
|
try {
|
||||||
const contextValue = JSON.parse(input);
|
const contextValue = JSON.parse(input);
|
||||||
|
|
||||||
setFieldExist(contextValue[contextField] !== undefined);
|
setFieldExist(
|
||||||
|
contextValue[contextField] !== undefined ||
|
||||||
|
contextValue?.properties[contextField] !== undefined
|
||||||
|
);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
return setError(formatUnknownError(error));
|
return setError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
@ -75,12 +81,13 @@ export const PlaygroundCodeFieldset: VFC<IPlaygroundCodeFieldsetProps> = ({
|
|||||||
const onAddField = () => {
|
const onAddField = () => {
|
||||||
try {
|
try {
|
||||||
const currentValue = JSON.parse(context || '{}');
|
const currentValue = JSON.parse(context || '{}');
|
||||||
|
|
||||||
setContext(
|
setContext(
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
{
|
normalizeCustomContextProperties({
|
||||||
...currentValue,
|
...currentValue,
|
||||||
[contextField]: contextValue,
|
[contextField]: contextValue,
|
||||||
},
|
}),
|
||||||
null,
|
null,
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
import {
|
||||||
|
normalizeCustomContextProperties,
|
||||||
|
NormalizedContextProperties,
|
||||||
|
} from './playground.utils';
|
||||||
|
|
||||||
|
test('should keep standard properties in their place', () => {
|
||||||
|
const input: NormalizedContextProperties = {
|
||||||
|
appName: 'testApp',
|
||||||
|
environment: 'testEnv',
|
||||||
|
userId: 'testUser',
|
||||||
|
sessionId: 'testSession',
|
||||||
|
remoteAddress: '127.0.0.1',
|
||||||
|
currentTime: 'now',
|
||||||
|
};
|
||||||
|
const output = normalizeCustomContextProperties(input);
|
||||||
|
expect(output).toEqual(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should move non-standard properties to nested properties field', () => {
|
||||||
|
const input = {
|
||||||
|
appName: 'testApp',
|
||||||
|
customProp: 'customValue',
|
||||||
|
anotherCustom: 'anotherValue',
|
||||||
|
};
|
||||||
|
const output = normalizeCustomContextProperties(input);
|
||||||
|
expect(output).toEqual({
|
||||||
|
appName: 'testApp',
|
||||||
|
properties: {
|
||||||
|
customProp: 'customValue',
|
||||||
|
anotherCustom: 'anotherValue',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not have properties field if there are no non-standard properties', () => {
|
||||||
|
const input = {
|
||||||
|
appName: 'testApp',
|
||||||
|
};
|
||||||
|
const output = normalizeCustomContextProperties(input);
|
||||||
|
expect(output).toEqual(input);
|
||||||
|
expect(output.properties).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should combine existing properties field with non-standard properties', () => {
|
||||||
|
const input = {
|
||||||
|
appName: 'testApp',
|
||||||
|
properties: {
|
||||||
|
existingProp: 'existingValue',
|
||||||
|
},
|
||||||
|
customProp: 'customValue',
|
||||||
|
};
|
||||||
|
const output = normalizeCustomContextProperties(input);
|
||||||
|
expect(output).toEqual({
|
||||||
|
appName: 'testApp',
|
||||||
|
properties: {
|
||||||
|
existingProp: 'existingValue',
|
||||||
|
customProp: 'customValue',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should add multiple standard properties without breaking custom properties', () => {
|
||||||
|
const input = {
|
||||||
|
appName: 'testApp',
|
||||||
|
properties: {
|
||||||
|
existingProp: 'existingValue',
|
||||||
|
},
|
||||||
|
currentTime: 'value',
|
||||||
|
};
|
||||||
|
const output = normalizeCustomContextProperties(input);
|
||||||
|
expect(output).toEqual({
|
||||||
|
appName: 'testApp',
|
||||||
|
currentTime: 'value',
|
||||||
|
properties: {
|
||||||
|
existingProp: 'existingValue',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
@ -73,3 +73,55 @@ export const isStringOrStringArray = (
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type InputContextProperties = {
|
||||||
|
appName?: string;
|
||||||
|
environment?: string;
|
||||||
|
userId?: string;
|
||||||
|
sessionId?: string;
|
||||||
|
remoteAddress?: string;
|
||||||
|
currentTime?: string;
|
||||||
|
properties?: { [key: string]: any };
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NormalizedContextProperties = Omit<
|
||||||
|
InputContextProperties,
|
||||||
|
'properties'
|
||||||
|
> & {
|
||||||
|
properties?: { [key: string]: any };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const normalizeCustomContextProperties = (
|
||||||
|
input: InputContextProperties
|
||||||
|
): NormalizedContextProperties => {
|
||||||
|
const standardProps = new Set([
|
||||||
|
'appName',
|
||||||
|
'environment',
|
||||||
|
'userId',
|
||||||
|
'sessionId',
|
||||||
|
'remoteAddress',
|
||||||
|
'currentTime',
|
||||||
|
'properties',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const output: InputContextProperties = { ...input };
|
||||||
|
let hasCustomProperties = false;
|
||||||
|
|
||||||
|
for (const key in input) {
|
||||||
|
if (!standardProps.has(key)) {
|
||||||
|
if (!output.properties) {
|
||||||
|
output.properties = {};
|
||||||
|
}
|
||||||
|
output.properties[key] = input[key];
|
||||||
|
delete output[key];
|
||||||
|
hasCustomProperties = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasCustomProperties && !input.properties) {
|
||||||
|
delete output.properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
@ -37,3 +37,28 @@ test('should generate all combinations correctly when only one combination', ()
|
|||||||
|
|
||||||
expect(actualCombinations).toEqual(expectedCombinations);
|
expect(actualCombinations).toEqual(expectedCombinations);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should generate combinations with nested properties', () => {
|
||||||
|
const obj = {
|
||||||
|
sessionId: '1,2',
|
||||||
|
nonString: 2,
|
||||||
|
properties: {
|
||||||
|
nonString: 1,
|
||||||
|
channels: 'internet',
|
||||||
|
appName: 'a,b,c',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedCombinations = [
|
||||||
|
{ sessionId: '1', appName: 'a', channels: 'internet', nonString: 1 },
|
||||||
|
{ sessionId: '1', appName: 'b', channels: 'internet', nonString: 1 },
|
||||||
|
{ sessionId: '1', appName: 'c', channels: 'internet', nonString: 1 },
|
||||||
|
{ sessionId: '2', appName: 'a', channels: 'internet', nonString: 1 },
|
||||||
|
{ sessionId: '2', appName: 'b', channels: 'internet', nonString: 1 },
|
||||||
|
{ sessionId: '2', appName: 'c', channels: 'internet', nonString: 1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const actualCombinations = generateObjectCombinations(obj);
|
||||||
|
|
||||||
|
expect(actualCombinations).toEqual(expectedCombinations);
|
||||||
|
});
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
type Dict<T> = { [K in keyof T]: string[] };
|
type Dict<T> = { [K in keyof T]: (string | number)[] };
|
||||||
|
|
||||||
export const splitByComma = <T extends Record<string, unknown>>(
|
export const splitByComma = <T extends Record<string, unknown>>(
|
||||||
obj: T,
|
obj: T,
|
||||||
): Dict<T> =>
|
): Dict<T> => {
|
||||||
Object.fromEntries(
|
return Object.entries(obj).reduce((acc, [key, value]) => {
|
||||||
Object.entries(obj).map(([key, value]) => [
|
if (key === 'properties' && typeof value === 'object') {
|
||||||
key,
|
const nested = splitByComma(value as any);
|
||||||
typeof value === 'string' ? value.split(',') : [value],
|
return { ...acc, ...nested };
|
||||||
]),
|
} else if (typeof value === 'string') {
|
||||||
) as Dict<T>;
|
return { ...acc, [key]: value.split(',') };
|
||||||
|
} else {
|
||||||
|
return { ...acc, [key]: [value] };
|
||||||
|
}
|
||||||
|
}, {} as Dict<T>);
|
||||||
|
};
|
||||||
|
|
||||||
export const generateCombinations = <T extends Record<string, unknown>>(
|
export const generateCombinations = <T extends Record<string, unknown>>(
|
||||||
obj: Dict<T>,
|
obj: Dict<T>,
|
||||||
|
Loading…
Reference in New Issue
Block a user