mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +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 { PlaygroundEditor } from './PlaygroundEditor/PlaygroundEditor';
|
||||
import { parseDateValue, parseValidDate } from 'component/common/util';
|
||||
import { isStringOrStringArray } from '../../playground.utils';
|
||||
import {
|
||||
isStringOrStringArray,
|
||||
normalizeCustomContextProperties,
|
||||
} from '../../playground.utils';
|
||||
interface IPlaygroundCodeFieldsetProps {
|
||||
context: string | undefined;
|
||||
setContext: Dispatch<SetStateAction<string | undefined>>;
|
||||
@ -58,7 +61,10 @@ export const PlaygroundCodeFieldset: VFC<IPlaygroundCodeFieldsetProps> = ({
|
||||
try {
|
||||
const contextValue = JSON.parse(input);
|
||||
|
||||
setFieldExist(contextValue[contextField] !== undefined);
|
||||
setFieldExist(
|
||||
contextValue[contextField] !== undefined ||
|
||||
contextValue?.properties[contextField] !== undefined
|
||||
);
|
||||
} catch (error: unknown) {
|
||||
return setError(formatUnknownError(error));
|
||||
}
|
||||
@ -75,12 +81,13 @@ export const PlaygroundCodeFieldset: VFC<IPlaygroundCodeFieldsetProps> = ({
|
||||
const onAddField = () => {
|
||||
try {
|
||||
const currentValue = JSON.parse(context || '{}');
|
||||
|
||||
setContext(
|
||||
JSON.stringify(
|
||||
{
|
||||
normalizeCustomContextProperties({
|
||||
...currentValue,
|
||||
[contextField]: contextValue,
|
||||
},
|
||||
}),
|
||||
null,
|
||||
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;
|
||||
};
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
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>>(
|
||||
obj: T,
|
||||
): Dict<T> =>
|
||||
Object.fromEntries(
|
||||
Object.entries(obj).map(([key, value]) => [
|
||||
key,
|
||||
typeof value === 'string' ? value.split(',') : [value],
|
||||
]),
|
||||
) as Dict<T>;
|
||||
): Dict<T> => {
|
||||
return Object.entries(obj).reduce((acc, [key, value]) => {
|
||||
if (key === 'properties' && typeof value === 'object') {
|
||||
const nested = splitByComma(value as any);
|
||||
return { ...acc, ...nested };
|
||||
} else if (typeof value === 'string') {
|
||||
return { ...acc, [key]: value.split(',') };
|
||||
} else {
|
||||
return { ...acc, [key]: [value] };
|
||||
}
|
||||
}, {} as Dict<T>);
|
||||
};
|
||||
|
||||
export const generateCombinations = <T extends Record<string, unknown>>(
|
||||
obj: Dict<T>,
|
||||
|
Loading…
Reference in New Issue
Block a user