n8n-nodes-toggl
Version:
n8n community node for Toggl Track time tracking integration with comprehensive operations including client management and webhook support
749 lines (748 loc) • 31.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Toggl = void 0;
const TogglApi_helper_1 = require("./TogglApi.helper");
class Toggl {
constructor() {
this.description = {
displayName: 'Toggl',
name: 'toggl',
icon: 'file:toggl.png',
group: ['output'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Interact with Toggl Track API',
defaults: {
name: 'Toggl',
},
inputs: ["main" /* NodeConnectionType.Main */],
outputs: ["main" /* NodeConnectionType.Main */],
credentials: [
{
name: 'togglApi',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Time Entry',
value: 'timeEntry',
},
{
name: 'Project',
value: 'project',
},
{
name: 'Client',
value: 'client',
},
{
name: 'Raw Request',
value: 'rawRequest',
},
],
default: 'timeEntry',
},
// Time Entry Operations
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['timeEntry'],
},
},
options: [
{
name: 'Start Time Entry',
value: 'startTimeEntry',
description: 'Start a new time entry',
action: 'Start a time entry',
},
{
name: 'Stop Time Entry',
value: 'stopTimeEntry',
description: 'Stop the current running time entry',
action: 'Stop a time entry',
},
{
name: 'Get Current Time Entry',
value: 'getCurrentTimeEntry',
description: 'Get the currently running time entry',
action: 'Get current time entry',
},
{
name: 'List Time Entries',
value: 'listTimeEntries',
description: 'Get a list of time entries',
action: 'List time entries',
},
],
default: 'startTimeEntry',
},
// Project Operations
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['project'],
},
},
options: [
{
name: 'Create Project',
value: 'createProject',
description: 'Create a new project',
action: 'Create a project',
},
{
name: 'List Projects',
value: 'listProjects',
description: 'Get a list of projects',
action: 'List projects',
},
],
default: 'createProject',
},
// Client Operations
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['client'],
},
},
options: [
{
name: 'Create Client',
value: 'createClient',
description: 'Create a new client',
action: 'Create a client',
},
{
name: 'List Clients',
value: 'listClients',
description: 'Get a list of clients',
action: 'List clients',
},
],
default: 'createClient',
},
// Raw Request Operation
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['rawRequest'],
},
},
options: [
{
name: 'Raw Request',
value: 'rawRequest',
description: 'Make a raw API request',
action: 'Make a raw request',
},
],
default: 'rawRequest',
},
// Start Time Entry Fields
{
displayName: 'Description',
name: 'description',
type: 'string',
displayOptions: {
show: {
resource: ['timeEntry'],
operation: ['startTimeEntry'],
},
},
default: '',
description: 'Description of the time entry',
},
{
displayName: 'Project ID',
name: 'projectId',
type: 'number',
displayOptions: {
show: {
resource: ['timeEntry'],
operation: ['startTimeEntry'],
},
},
default: 0,
description: 'ID of the project to assign the time entry to',
},
{
displayName: 'Tags',
name: 'tags',
type: 'string',
displayOptions: {
show: {
resource: ['timeEntry'],
operation: ['startTimeEntry'],
},
},
default: '',
description: 'Comma-separated list of tags',
},
{
displayName: 'Workspace ID',
name: 'workspaceId',
type: 'number',
displayOptions: {
show: {
resource: ['timeEntry'],
operation: ['startTimeEntry'],
},
},
default: 0,
description: 'ID of the workspace to create the time entry in',
required: true,
},
// Stop Time Entry Fields
{
displayName: 'Time Entry ID',
name: 'timeEntryId',
type: 'number',
displayOptions: {
show: {
resource: ['timeEntry'],
operation: ['stopTimeEntry'],
},
},
default: 0,
description: 'ID of the time entry to stop. Leave empty to stop the current running entry.',
},
{
displayName: 'Workspace ID',
name: 'workspaceId',
type: 'number',
displayOptions: {
show: {
resource: ['timeEntry'],
operation: ['stopTimeEntry'],
timeEntryId: [{ _cnd: { gte: 1 } }],
},
},
default: 0,
description: 'ID of the workspace containing the time entry',
required: true,
},
// List Time Entries Fields
{
displayName: 'Start Date',
name: 'startDate',
type: 'dateTime',
displayOptions: {
show: {
resource: ['timeEntry'],
operation: ['listTimeEntries'],
},
},
default: '',
description: 'Start date for the time entries to retrieve',
},
{
displayName: 'End Date',
name: 'endDate',
type: 'dateTime',
displayOptions: {
show: {
resource: ['timeEntry'],
operation: ['listTimeEntries'],
},
},
default: '',
description: 'End date for the time entries to retrieve',
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['timeEntry'],
operation: ['listTimeEntries'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['timeEntry'],
operation: ['listTimeEntries'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
},
default: 50,
description: 'Max number of results to return',
},
// Create Project Fields
{
displayName: 'Project Name',
name: 'projectName',
type: 'string',
displayOptions: {
show: {
resource: ['project'],
operation: ['createProject'],
},
},
default: '',
description: 'Name of the project to create',
required: true,
},
{
displayName: 'Workspace ID',
name: 'workspaceId',
type: 'number',
displayOptions: {
show: {
resource: ['project'],
operation: ['createProject'],
},
},
default: 0,
description: 'ID of the workspace to create the project in',
required: true,
},
{
displayName: 'Client ID',
name: 'clientId',
type: 'number',
displayOptions: {
show: {
resource: ['project'],
operation: ['createProject'],
},
},
default: 0,
description: 'ID of the client to assign the project to',
},
{
displayName: 'Color',
name: 'color',
type: 'color',
displayOptions: {
show: {
resource: ['project'],
operation: ['createProject'],
},
},
default: '#06aaf5',
description: 'Hex color code for the project',
},
// List Projects Fields
{
displayName: 'Workspace ID',
name: 'workspaceId',
type: 'number',
displayOptions: {
show: {
resource: ['project'],
operation: ['listProjects'],
},
},
default: 0,
description: 'ID of the workspace to list projects from',
required: true,
},
{
displayName: 'Include Archived',
name: 'includeArchived',
type: 'boolean',
displayOptions: {
show: {
resource: ['project'],
operation: ['listProjects'],
},
},
default: false,
description: 'Whether to include archived projects',
},
// Create Client Fields
{
displayName: 'Client Name',
name: 'clientName',
type: 'string',
displayOptions: {
show: {
resource: ['client'],
operation: ['createClient'],
},
},
default: '',
description: 'Name of the client to create',
required: true,
},
{
displayName: 'Workspace ID',
name: 'workspaceId',
type: 'number',
displayOptions: {
show: {
resource: ['client'],
operation: ['createClient'],
},
},
default: 0,
description: 'ID of the workspace to create the client in',
required: true,
},
{
displayName: 'External Reference',
name: 'externalReference',
type: 'string',
displayOptions: {
show: {
resource: ['client'],
operation: ['createClient'],
},
},
default: '',
description: 'External reference for the client',
},
{
displayName: 'Notes',
name: 'notes',
type: 'string',
displayOptions: {
show: {
resource: ['client'],
operation: ['createClient'],
},
},
default: '',
description: 'Notes for the client',
},
// List Clients Fields
{
displayName: 'Workspace ID',
name: 'workspaceId',
type: 'number',
displayOptions: {
show: {
resource: ['client'],
operation: ['listClients'],
},
},
default: 0,
description: 'ID of the workspace to list clients from',
required: true,
},
{
displayName: 'Status',
name: 'status',
type: 'options',
displayOptions: {
show: {
resource: ['client'],
operation: ['listClients'],
},
},
options: [
{
name: 'Active',
value: 'active',
},
{
name: 'Archived',
value: 'archived',
},
{
name: 'Both',
value: 'both',
},
],
default: 'active',
description: 'Status of clients to retrieve',
},
{
displayName: 'Client Name Filter',
name: 'nameFilter',
type: 'string',
displayOptions: {
show: {
resource: ['client'],
operation: ['listClients'],
},
},
default: '',
description: 'Filter clients by name (case insensitive)',
},
// Raw Request Fields
{
displayName: 'HTTP Method',
name: 'method',
type: 'options',
displayOptions: {
show: {
resource: ['rawRequest'],
operation: ['rawRequest'],
},
},
options: [
{
name: 'GET',
value: 'GET',
},
{
name: 'POST',
value: 'POST',
},
{
name: 'PUT',
value: 'PUT',
},
{
name: 'DELETE',
value: 'DELETE',
},
],
default: 'GET',
description: 'HTTP method to use',
},
{
displayName: 'Endpoint',
name: 'endpoint',
type: 'string',
displayOptions: {
show: {
resource: ['rawRequest'],
operation: ['rawRequest'],
},
},
default: '',
description: 'API endpoint (e.g., /me, /workspaces, /time_entries)',
required: true,
},
{
displayName: 'Body',
name: 'body',
type: 'json',
displayOptions: {
show: {
resource: ['rawRequest'],
operation: ['rawRequest'],
method: ['POST', 'PUT'],
},
},
default: '{}',
description: 'Request body as JSON',
},
{
displayName: 'Query Parameters',
name: 'queryParameters',
type: 'json',
displayOptions: {
show: {
resource: ['rawRequest'],
operation: ['rawRequest'],
},
},
default: '{}',
description: 'Query parameters as JSON object',
},
],
};
}
async execute() {
const items = this.getInputData();
const returnData = [];
const resource = this.getNodeParameter('resource', 0);
const operation = this.getNodeParameter('operation', 0);
for (let i = 0; i < items.length; i++) {
try {
let responseData;
if (resource === 'timeEntry') {
if (operation === 'startTimeEntry') {
const description = this.getNodeParameter('description', i);
const projectId = this.getNodeParameter('projectId', i);
const tags = this.getNodeParameter('tags', i);
const workspaceId = this.getNodeParameter('workspaceId', i);
const body = {
description,
created_with: 'n8n',
workspace_id: workspaceId,
duration: -1,
start: new Date().toISOString(),
};
if (projectId) {
body.project_id = projectId;
}
if (tags) {
body.tags = tags.split(',').map(tag => tag.trim());
}
responseData = await TogglApi_helper_1.togglApiRequest.call(this, 'POST', `/workspaces/${workspaceId}/time_entries`, body);
}
if (operation === 'stopTimeEntry') {
const timeEntryId = this.getNodeParameter('timeEntryId', i);
if (timeEntryId) {
// Get workspace ID for the time entry
const workspaceId = this.getNodeParameter('workspaceId', i);
responseData = await TogglApi_helper_1.togglApiRequest.call(this, 'PATCH', `/workspaces/${workspaceId}/time_entries/${timeEntryId}/stop`);
}
else {
// Get current time entry first
const currentEntry = await TogglApi_helper_1.togglApiRequest.call(this, 'GET', '/me/time_entries/current');
if (currentEntry && currentEntry.id && currentEntry.workspace_id) {
responseData = await TogglApi_helper_1.togglApiRequest.call(this, 'PATCH', `/workspaces/${currentEntry.workspace_id}/time_entries/${currentEntry.id}/stop`);
}
else {
throw new Error('No running time entry found');
}
}
}
if (operation === 'getCurrentTimeEntry') {
responseData = await TogglApi_helper_1.togglApiRequest.call(this, 'GET', '/me/time_entries/current');
}
if (operation === 'listTimeEntries') {
const startDate = this.getNodeParameter('startDate', i);
const endDate = this.getNodeParameter('endDate', i);
const returnAll = this.getNodeParameter('returnAll', i);
const limit = this.getNodeParameter('limit', i);
const qs = {};
if (startDate) {
qs.start_date = new Date(startDate).toISOString();
}
if (endDate) {
qs.end_date = new Date(endDate).toISOString();
}
if (returnAll) {
responseData = await TogglApi_helper_1.togglApiRequestAllItems.call(this, 'data', 'GET', '/me/time_entries', {}, qs);
}
else {
qs.per_page = limit;
responseData = await TogglApi_helper_1.togglApiRequest.call(this, 'GET', '/me/time_entries', {}, qs);
}
}
}
if (resource === 'project') {
if (operation === 'createProject') {
const projectName = this.getNodeParameter('projectName', i);
const workspaceId = this.getNodeParameter('workspaceId', i);
const clientId = this.getNodeParameter('clientId', i);
const color = this.getNodeParameter('color', i);
const body = {
name: projectName,
color,
};
if (clientId) {
body.client_id = clientId;
}
responseData = await TogglApi_helper_1.togglApiRequest.call(this, 'POST', `/workspaces/${workspaceId}/projects`, body);
}
if (operation === 'listProjects') {
const workspaceId = this.getNodeParameter('workspaceId', i);
const includeArchived = this.getNodeParameter('includeArchived', i);
const qs = {};
if (includeArchived) {
qs.include_archived = true;
}
responseData = await TogglApi_helper_1.togglApiRequest.call(this, 'GET', `/workspaces/${workspaceId}/projects`, {}, qs);
}
}
if (resource === 'client') {
if (operation === 'createClient') {
const clientName = this.getNodeParameter('clientName', i);
const workspaceId = this.getNodeParameter('workspaceId', i);
const externalReference = this.getNodeParameter('externalReference', i);
const notes = this.getNodeParameter('notes', i);
const body = {
name: clientName,
};
if (externalReference) {
body.external_reference = externalReference;
}
if (notes) {
body.notes = notes;
}
responseData = await TogglApi_helper_1.togglApiRequest.call(this, 'POST', `/workspaces/${workspaceId}/clients`, body);
}
if (operation === 'listClients') {
const workspaceId = this.getNodeParameter('workspaceId', i);
const status = this.getNodeParameter('status', i);
const nameFilter = this.getNodeParameter('nameFilter', i);
const qs = {};
if (status && status !== 'active') {
qs.status = status;
}
if (nameFilter) {
qs.name = nameFilter;
}
responseData = await TogglApi_helper_1.togglApiRequest.call(this, 'GET', `/workspaces/${workspaceId}/clients`, {}, qs);
}
}
if (resource === 'rawRequest') {
if (operation === 'rawRequest') {
const method = this.getNodeParameter('method', i);
const endpoint = this.getNodeParameter('endpoint', i);
const body = this.getNodeParameter('body', i, '{}');
const queryParameters = this.getNodeParameter('queryParameters', i, '{}');
let parsedBody = {};
let parsedQuery = {};
try {
parsedBody = JSON.parse(body);
}
catch (error) {
throw new Error('Invalid JSON in body parameter');
}
try {
parsedQuery = JSON.parse(queryParameters);
}
catch (error) {
throw new Error('Invalid JSON in query parameters');
}
responseData = await TogglApi_helper_1.togglApiRequest.call(this, method, endpoint, parsedBody, parsedQuery);
}
}
if (Array.isArray(responseData)) {
returnData.push.apply(returnData, responseData.map(item => ({ json: item })));
}
else {
returnData.push({ json: responseData });
}
}
catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: {
error: error.message,
},
pairedItem: { item: i },
});
continue;
}
throw error;
}
}
return [returnData];
}
}
exports.Toggl = Toggl;