Skip to content

Commit

Permalink
Add describe node
Browse files Browse the repository at this point in the history
  • Loading branch information
ajthinking committed Dec 20, 2023
1 parent 79c7b78 commit c2152a5
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 1 deletion.
153 changes: 153 additions & 0 deletions packages/core/src/computers/Describe/Describe.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { when } from '../../support/computerTester/ComputerTester';
import { Describe } from './Describe';

it('describes occurances', async () => {
await when(Describe)
.hasDefaultParams()
.getsInput([
{id: 1, isCool: true, name: {first: 'John', last: 'Doe'}},
{id: 2, isCool: true, name: {first: 'Jane', last: 'Doe'}},
{id: 3, isCool: false, name: {first: 'Copy', last: 'Cat'}},
])
.doRun()
.expectOutputs({
truncated: [
{
id: {
1: 1,
2: 1,
3: 1,
},
isCool: {
true: 2,
false: 1,
},
name: {
first: {
John: 1,
Jane: 1,
Copy: 1,
},
last: {
Doe: 2,
Cat: 1,
},
},
}
],
full: [
{
id: {
1: 1,
2: 1,
3: 1,
},
isCool: {
true: 2,
false: 1,
},
name: {
first: {
John: 1,
Jane: 1,
Copy: 1,
},
last: {
Doe: 2,
Cat: 1,
},
},
}
]
})
.ok()
})

it('describes occurances at a path', async () => {
await when(Describe)
.hasParams({
path: 'name'
})
.getsInput([
{id: 1, isCool: true, name: {first: 'John', last: 'Doe'}},
{id: 2, isCool: true, name: {first: 'Jane', last: 'Doe'}},
{id: 3, isCool: false, name: {first: 'Copy', last: 'Cat'}},
])
.doRun()
.expectOutputs({
truncated: [
{
first: {
John: 1,
Jane: 1,
Copy: 1,
},
last: {
Doe: 2,
Cat: 1,
},
}
],
full: [
{
first: {
John: 1,
Jane: 1,
Copy: 1,
},
last: {
Doe: 2,
Cat: 1,
},
}
],
})
.ok()
})

it('can truncate the description', async () => {
await when(Describe)
.hasParams({
truncate_limit: 3
})
.getsInput([
{id: 1, isCool: true},
{id: 2, isCool: true},
{id: 3, isCool: true},
{id: 4, isCool: true},
{id: 5, isCool: false},
])
.doRun()
.expectOutputs({
truncated: [
{
id: {
1: 1,
2: 1,
3: 1,
'OTHER_VALUES': 2,
},
isCool: {
true: 4,
false: 1,
},
}
],
full: [
{
id: {
1: 1,
2: 1,
3: 1,
4: 1,
5: 1,
},
isCool: {
true: 4,
false: 1,
},
}
],
})
.ok()
})
66 changes: 66 additions & 0 deletions packages/core/src/computers/Describe/Describe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@

import { ComputerConfig } from '../../types/ComputerConfig';
import { multiline } from '../../utils/multiline';
import { describeCollection, truncateDescription } from './describeCollection';
import { NumberCast, StringCast } from '../../Param';
import { get } from '../../utils/get';

export const Describe: ComputerConfig = {
name: 'Describe',
docs: multiline`
Describes the data structure of inputed items. Full outputs a nested object with occurrences of each item/key. Use the "truncated" port to get a similar summary more suitable for humans.
`,
inputs: ['input'],
outputs: ['truncated', 'full'],
params: [
{
name: 'path',
label: 'Path',
help: 'Dot notated path to desired description root. Leave empty to use the root of the collection.',
inputMode: {
type: 'Stringable',
multiline: false,
canInterpolate: true,
interpolate: true,
casts: [
{...StringCast, selected: true}
],
value: ''
},
alternativeInputModes: []
},
{
name: 'truncate_limit',
label: 'Truncate limit',
help: 'How many keys to display?',
inputMode: {
type: 'Stringable',
multiline: false,
canInterpolate: true,
interpolate: true,
casts: [
{...NumberCast, selected: true}
],
value: String(10)
},
alternativeInputModes: []
},
],

canRun({ input }) {
return input.haveAllItemsAtInput('input')
},

async *run({ input, output, params }) {
const incoming = input!.pull()
.map(i => get(i.value, String(params.path)))

const description = describeCollection(incoming)
const truncated = truncateDescription(description, Number(params.truncate_limit))

output.pushTo('full', [description])
output.pushTo('truncated', [truncated])

yield;
},
};
55 changes: 55 additions & 0 deletions packages/core/src/computers/Describe/describeCollection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
export function describeCollection(arr: any[]) {
const result = {};

// Function to recursively process each object
const processObject = (obj: any, res: any) => {
Object.keys(obj).forEach(key => {
if (obj[key] !== null && typeof obj[key] === 'object') {
res[key] = res[key] || {};
processObject(obj[key], res[key]);
} else {
res[key] = res[key] || {};
res[key][obj[key]] = (res[key][obj[key]] || 0) + 1;
}
});
};

// Iterate over each object in the array
arr.forEach(obj => {
processObject(obj, result);
});

return result;
}

export function truncateDescription(description: any, threshold = 10) {
const truncate = (obj: any) => {
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
const entries = Object.entries(obj[key]);
if (entries.length > threshold) {
// Sort entries by their count, descending
entries.sort((a: any, b: any) => b[1] - a[1]);

// Keep the top (threshold - 1) entries and sum the rest
const topEntries = entries.slice(0, threshold);
const otherCount = entries.slice(threshold).reduce((acc, [, count]) => acc + (count as any), 0);

// Create a new object with the top entries and 'OTHER_VALUES'
obj[key] = topEntries.reduce((acc: any, [value, count]) => {
acc[value] = count;
return acc;
}, {});
obj[key]['OTHER_VALUES'] = otherCount;
} else {
truncate(obj[key]);
}
}
});
};

// Clone the object to avoid modifying the original
const truncatedDescription = JSON.parse(JSON.stringify(description));
truncate(truncatedDescription);
return truncatedDescription;
}
1 change: 1 addition & 0 deletions packages/core/src/computers/Describe/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Describe } from './Describe'
1 change: 1 addition & 0 deletions packages/core/src/computers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { Comment } from './Comment';
export { ConsoleLog } from './ConsoleLog'
// export { CreateProperty } from './CreateProperty';
export { CreateJson } from './CreateJson'
export { Describe } from './Describe'
// export { Dump } from './Dump';
// export { Eval } from './Eval';
export { Fake } from './Fake'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export const NodeSettingsModal = () => {
className="pr-4 mt-4 bg-white flex flex-col align-center justify-center text-lg text-gray-400 font-bold tracking widest"
/>
<div className="flex">
{form.getValues('label') !== 'Signal' && <div className="flex flex-col pr-4 my-2 mt-3 italic flex flex-col align-center justify-center text-sm text-gray-400 font-base tracking widest">
{form.getValues('label') !== node.data.computer && <div className="flex flex-col pr-4 my-2 mt-3 italic flex flex-col align-center justify-center text-sm text-gray-400 font-base tracking widest">
renamed from {node.data.computer}
</div>}
<div className="cursor-pointer p-1 ml-auto text-black float-right text-3xl leading-none font-semibold outline-none focus:outline-none" onClick={close}>
Expand Down

0 comments on commit c2152a5

Please sign in to comment.