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,111 @@
import type {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { assertParamIsNumber, NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
export class TimeSaved implements INodeType {
description: INodeTypeDescription = {
displayName: 'Track Time Saved',
name: 'timeSaved',
icon: 'fa:timer',
group: ['organization'],
version: 1,
description:
'Dynamically track time saved based on the workflows execution path and the number of items processed',
defaults: {
name: 'Time Saved',
color: '#1E90FF',
},
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
properties: [
{
displayName:
'For each run, time saved is the sum of all Time Saved nodes that execute. Use this when different execution paths or items save different amounts of time.',
name: 'notice',
type: 'notice',
default: '',
},
{
displayName: 'Calculation Mode',
name: 'mode',
type: 'options',
default: 'once',
noDataExpression: true,
options: [
{
name: 'Once For All Items',
value: 'once',
description: 'Counts minutes saved once for all input items',
},
{
name: 'Per Item',
value: 'perItem',
description: 'Multiply minutes saved by the number of input items',
},
],
},
{
displayName: 'Minutes Saved',
name: 'minutesSaved',
type: 'number',
default: 0,
noDataExpression: true,
typeOptions: {
minValue: 0,
},
description: 'Number of minutes saved by this workflow execution',
},
],
hints: [
{
type: 'info',
message:
'Multiple Saved Time nodes in the same workflow will have their values summed together.',
displayCondition: '=true',
whenToDisplay: 'beforeExecution',
location: 'outputPane',
},
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const mode = this.getNodeParameter('mode', 0) as 'fixed' | 'perItem' | 'expression';
let timeSavedMinutes = this.getNodeParameter('minutesSaved', 0);
assertParamIsNumber('minutesSaved', timeSavedMinutes, this.getNode());
try {
if (mode === 'perItem') {
timeSavedMinutes = items.length * timeSavedMinutes;
}
// Ensure non-negative
if (timeSavedMinutes < 0) {
throw new NodeOperationError(
this.getNode(),
`Time saved cannot be negative, got: ${timeSavedMinutes}`,
);
}
// Set metadata using the clean API
this.setMetadata({
timeSaved: {
minutes: timeSavedMinutes,
},
});
// Pass through all items unchanged
return [items];
} catch (error) {
if (this.continueOnFail()) {
return [[{ json: { error: error.message } }]];
}
throw error;
}
}
}