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: 'node:track-time-saved', iconColor: 'black', group: ['organization'], version: 1, description: 'Dynamically track time saved based on the workflow’s execution path and the number of items processed', defaults: { name: 'Time Saved', }, 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, numberPrecision: 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 { 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; } } }