Skip to content

Commit

Permalink
feat: playground custom properties are nested (#4686)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwasniew authored Sep 14, 2023
1 parent 31fcf60 commit 878780f
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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>>;
Expand Down Expand Up @@ -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));
}
Expand All @@ -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
)
Expand Down
Original file line number Diff line number Diff line change
@@ -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',
},
});
});
52 changes: 52 additions & 0 deletions frontend/src/component/playground/Playground/playground.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
25 changes: 25 additions & 0 deletions src/lib/features/playground/generateObjectCombinations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
21 changes: 13 additions & 8 deletions src/lib/features/playground/generateObjectCombinations.ts
Original file line number Diff line number Diff line change
@@ -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>,
Expand Down

0 comments on commit 878780f

Please sign in to comment.