first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,19 @@
{
"node": "n8n-nodes-base.sort",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"details": "",
"categories": ["Core Nodes"],
"resources": {
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.sort/"
}
],
"generic": []
},
"alias": ["Sort", "Order", "Transform", "Array", "List", "Item", "Random"],
"subcategories": {
"Core Nodes": ["Data Transformation"]
}
}
@@ -0,0 +1,289 @@
import get from 'lodash/get';
import isEqual from 'lodash/isEqual';
import lt from 'lodash/lt';
import {
NodeOperationError,
type IDataObject,
type IExecuteFunctions,
type INodeExecutionData,
type INodeType,
type INodeTypeDescription,
NodeConnectionTypes,
} from 'n8n-workflow';
import { shuffleArray } from '@utils/utilities';
import { sortByCode } from './utils';
export class Sort implements INodeType {
description: INodeTypeDescription = {
displayName: 'Sort',
name: 'sort',
icon: 'file:sort.svg',
group: ['transform'],
subtitle: '',
version: 1,
description: 'Change items order',
defaults: {
name: 'Sort',
},
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
properties: [
{
displayName: 'Type',
name: 'type',
type: 'options',
options: [
{
name: 'Simple',
value: 'simple',
},
{
name: 'Random',
value: 'random',
},
{
name: 'Code',
value: 'code',
},
],
default: 'simple',
description: 'The type of sorting to perform',
},
{
displayName: 'Fields To Sort By',
name: 'sortFieldsUi',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
placeholder: 'Add Field To Sort By',
options: [
{
displayName: '',
name: 'sortField',
values: [
{
displayName: 'Field Name',
name: 'fieldName',
type: 'string',
required: true,
default: '',
description: 'The field to sort by',
// eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
placeholder: 'e.g. id',
hint: ' Enter the field name as text',
requiresDataPath: 'single',
},
{
displayName: 'Order',
name: 'order',
type: 'options',
options: [
{
name: 'Ascending',
value: 'ascending',
},
{
name: 'Descending',
value: 'descending',
},
],
default: 'ascending',
description: 'The order to sort by',
},
],
},
],
default: {},
description: 'The fields of the input items to sort by',
displayOptions: {
show: {
type: ['simple'],
},
},
},
{
displayName: 'Code',
name: 'code',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
editor: 'jsEditor',
rows: 10,
},
default: `// The two items to compare are in the variables a and b
// Access the fields in a.json and b.json
// Return -1 if a should go before b
// Return 1 if b should go before a
// Return 0 if there's no difference
fieldName = 'myField';
if (a.json[fieldName] < b.json[fieldName]) {
return -1;
}
if (a.json[fieldName] > b.json[fieldName]) {
return 1;
}
return 0;`,
description: 'Javascript code to determine the order of any two items',
displayOptions: {
show: {
type: ['code'],
},
},
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
type: ['simple'],
},
},
options: [
{
displayName: 'Disable Dot Notation',
name: 'disableDotNotation',
type: 'boolean',
default: false,
description:
'Whether to disallow referencing child fields using `parent.child` in the field name',
},
],
},
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
let returnData = [...items];
const type = this.getNodeParameter('type', 0) as string;
const disableDotNotation = this.getNodeParameter(
'options.disableDotNotation',
0,
false,
) as boolean;
if (type === 'random') {
shuffleArray(returnData);
return [returnData];
}
if (type === 'simple') {
const sortFieldsUi = this.getNodeParameter('sortFieldsUi', 0) as IDataObject;
const sortFields = sortFieldsUi.sortField as Array<{
fieldName: string;
order: 'ascending' | 'descending';
}>;
if (!sortFields?.length) {
throw new NodeOperationError(
this.getNode(),
'No sorting specified. Please add a field to sort by',
);
}
for (const { fieldName } of sortFields) {
let found = false;
for (const item of items) {
if (!disableDotNotation) {
if (get(item.json, fieldName) !== undefined) {
found = true;
}
} else if (item.json.hasOwnProperty(fieldName)) {
found = true;
}
}
if (!found && disableDotNotation && fieldName.includes('.')) {
throw new NodeOperationError(
this.getNode(),
`Couldn't find the field '${fieldName}' in the input data`,
{
description:
"If you're trying to use a nested field, make sure you turn off 'disable dot notation' in the node options",
},
);
} else if (!found) {
throw new NodeOperationError(
this.getNode(),
`Couldn't find the field '${fieldName}' in the input data`,
);
}
}
const sortFieldsWithDirection = sortFields.map((field) => ({
name: field.fieldName,
dir: field.order === 'ascending' ? 1 : -1,
}));
returnData.sort((a, b) => {
let result = 0;
for (const field of sortFieldsWithDirection) {
let equal;
if (!disableDotNotation) {
const _a =
typeof get(a.json, field.name) === 'string'
? (get(a.json, field.name) as string).toLowerCase()
: get(a.json, field.name);
const _b =
typeof get(b.json, field.name) === 'string'
? (get(b.json, field.name) as string).toLowerCase()
: get(b.json, field.name);
equal = isEqual(_a, _b);
} else {
const _a =
typeof a.json[field.name] === 'string'
? (a.json[field.name] as string).toLowerCase()
: a.json[field.name];
const _b =
typeof b.json[field.name] === 'string'
? (b.json[field.name] as string).toLowerCase()
: b.json[field.name];
equal = isEqual(_a, _b);
}
if (!equal) {
let lessThan;
if (!disableDotNotation) {
const _a =
typeof get(a.json, field.name) === 'string'
? (get(a.json, field.name) as string).toLowerCase()
: get(a.json, field.name);
const _b =
typeof get(b.json, field.name) === 'string'
? (get(b.json, field.name) as string).toLowerCase()
: get(b.json, field.name);
lessThan = lt(_a, _b);
} else {
const _a =
typeof a.json[field.name] === 'string'
? (a.json[field.name] as string).toLowerCase()
: a.json[field.name];
const _b =
typeof b.json[field.name] === 'string'
? (b.json[field.name] as string).toLowerCase()
: b.json[field.name];
lessThan = lt(_a, _b);
}
if (lessThan) {
result = -1 * field.dir;
} else {
result = 1 * field.dir;
}
break;
}
}
return result;
});
} else {
returnData = await sortByCode.call(this);
}
return [returnData];
}
}
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" fill="none"><path fill="#8287EB" fill-rule="evenodd" d="M130.5 422.5c-6.627 0-12-5.373-12-12v-351c0-6.627 5.373-12 12-12h24c6.627 0 12 5.373 12 12v351c0 6.627-5.373 12-12 12z" clip-rule="evenodd"/><path fill="#8287EB" fill-rule="evenodd" d="M36.077 333.482c9.398-9.346 24.594-9.304 33.94.095l72.483 72.887 72.482-72.887c9.347-9.399 24.543-9.441 33.941-.095s9.441 24.543.095 33.941l-89.5 90a24 24 0 0 1-34.036 0l-89.5-90c-9.346-9.398-9.304-24.594.095-33.941M381.5 89.5c6.627 0 12 5.373 12 12v351c0 6.627-5.373 12-12 12h-24c-6.627 0-12-5.373-12-12v-351c0-6.627 5.373-12 12-12z" clip-rule="evenodd"/><path fill="#8287EB" fill-rule="evenodd" d="M475.923 178.518c-9.398 9.346-24.594 9.304-33.941-.095L369.5 105.536l-72.482 72.887c-9.347 9.399-24.543 9.441-33.941.095s-9.441-24.543-.095-33.941l89.5-90a24 24 0 0 1 34.036 0l89.5 90c9.346 9.398 9.304 24.594-.095 33.941" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 955 B

@@ -0,0 +1,25 @@
import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import { JsTaskRunnerSandbox } from '../../Code/JsTaskRunnerSandbox';
const returnRegExp = /\breturn\b/;
export async function sortByCode(this: IExecuteFunctions): Promise<INodeExecutionData[]> {
const userCode = this.getNodeParameter('code', 0) as string;
if (!returnRegExp.test(userCode)) {
throw new NodeOperationError(
this.getNode(),
"Sort code doesn't return. Please add a 'return' statement to your code",
);
}
const mode = this.getMode();
const items = this.getInputData();
const code = `return items.sort((a, b) => { ${userCode} })`;
const chunkSize = undefined;
const sandbox = new JsTaskRunnerSandbox(mode, this, chunkSize, { items });
const executionResult = await sandbox.runCode<INodeExecutionData[]>(code);
return executionResult;
}