diff --git a/packages/cli/BREAKING-CHANGES.md b/packages/cli/BREAKING-CHANGES.md
index a63c2d2deb..865d0bb419 100644
--- a/packages/cli/BREAKING-CHANGES.md
+++ b/packages/cli/BREAKING-CHANGES.md
@@ -2,6 +2,20 @@
This list shows all the versions which include breaking changes and how to upgrade.
+## 0.95.0
+
+### What changed?
+
+In the Harvest Node, we moved the account field from the credentials to the node parameters. This will allow you to work witn multiples accounts without having to create multiples credentials.
+
+### When is action necessary?
+
+If you are using the Harvest Node.
+
+### How to upgrade:
+
+Open the node set the parameter `Account ID`.
+
## 0.94.0
### What changed?
diff --git a/packages/nodes-base/credentials/HarvestApi.credentials.ts b/packages/nodes-base/credentials/HarvestApi.credentials.ts
index 683010aa16..080ad2198d 100644
--- a/packages/nodes-base/credentials/HarvestApi.credentials.ts
+++ b/packages/nodes-base/credentials/HarvestApi.credentials.ts
@@ -8,13 +8,6 @@ export class HarvestApi implements ICredentialType {
displayName = 'Harvest API';
documentationUrl = 'harvest';
properties = [
- {
- displayName: 'Account ID',
- name: 'accountId',
- type: 'string' as NodePropertyTypes,
- default: '',
- description: 'Visit your account details page, and grab the Account ID. See Harvest Personal Access Tokens.',
- },
{
displayName: 'Access Token',
name: 'accessToken',
diff --git a/packages/nodes-base/credentials/HarvestOAuth2Api.credentials.ts b/packages/nodes-base/credentials/HarvestOAuth2Api.credentials.ts
new file mode 100644
index 0000000000..0232af5cab
--- /dev/null
+++ b/packages/nodes-base/credentials/HarvestOAuth2Api.credentials.ts
@@ -0,0 +1,48 @@
+import {
+ ICredentialType,
+ NodePropertyTypes,
+} from 'n8n-workflow';
+
+
+export class HarvestOAuth2Api implements ICredentialType {
+ name = 'harvestOAuth2Api';
+ extends = [
+ 'oAuth2Api',
+ ];
+ displayName = 'Harvest OAuth2 API';
+ properties = [
+ {
+ displayName: 'Authorization URL',
+ name: 'authUrl',
+ type: 'hidden' as NodePropertyTypes,
+ default: 'https://id.getharvest.com/oauth2/authorize',
+ required: true,
+ },
+ {
+ displayName: 'Access Token URL',
+ name: 'accessTokenUrl',
+ type: 'hidden' as NodePropertyTypes,
+ default: 'https://id.getharvest.com/api/v2/oauth2/token',
+ required: true,
+ },
+ {
+ displayName: 'Scope',
+ name: 'scope',
+ type: 'hidden' as NodePropertyTypes,
+ default: 'all',
+ },
+ {
+ displayName: 'Auth URI Query Parameters',
+ name: 'authQueryParameters',
+ type: 'hidden' as NodePropertyTypes,
+ default: '',
+ },
+ {
+ displayName: 'Authentication',
+ name: 'authentication',
+ type: 'hidden' as NodePropertyTypes,
+ default: 'body',
+ description: 'Resource to consume.',
+ },
+ ];
+}
diff --git a/packages/nodes-base/nodes/Harvest/CompanyDescription.ts b/packages/nodes-base/nodes/Harvest/CompanyDescription.ts
index d67939afa0..917b29deb6 100644
--- a/packages/nodes-base/nodes/Harvest/CompanyDescription.ts
+++ b/packages/nodes-base/nodes/Harvest/CompanyDescription.ts
@@ -1,6 +1,6 @@
import { INodeProperties } from 'n8n-workflow';
-const resource = [ 'company' ];
+const resource = ['company'];
export const companyOperations = [
{
diff --git a/packages/nodes-base/nodes/Harvest/EstimateDescription.ts b/packages/nodes-base/nodes/Harvest/EstimateDescription.ts
index aac2090e26..f827a27d8b 100644
--- a/packages/nodes-base/nodes/Harvest/EstimateDescription.ts
+++ b/packages/nodes-base/nodes/Harvest/EstimateDescription.ts
@@ -1,6 +1,6 @@
import { INodeProperties } from 'n8n-workflow';
-const resource = [ 'estimate' ];
+const resource = ['estimate'];
export const estimateOperations = [
{
@@ -47,149 +47,149 @@ export const estimateOperations = [
export const estimateFields = [
-/* -------------------------------------------------------------------------- */
-/* estimate:getAll */
-/* -------------------------------------------------------------------------- */
+ /* -------------------------------------------------------------------------- */
+ /* estimate:getAll */
+ /* -------------------------------------------------------------------------- */
-{
- displayName: 'Return All',
- name: 'returnAll',
- type: 'boolean',
- displayOptions: {
- show: {
- resource,
- operation: [
- 'getAll',
- ],
- },
- },
- default: false,
- description: 'Returns a list of your estimates.',
-},
-{
- displayName: 'Limit',
- name: 'limit',
- type: 'number',
- displayOptions: {
- show: {
- resource,
- operation: [
- 'getAll',
- ],
- returnAll: [
- false,
- ],
- },
- },
- typeOptions: {
- minValue: 1,
- maxValue: 100,
- },
- default: 100,
- description: 'How many results to return.',
-},
-{
- displayName: 'Filters',
- name: 'filters',
- type: 'collection',
- placeholder: 'Add Filter',
- default: {},
- displayOptions: {
- show: {
- resource,
- operation: [
- 'getAll',
- ],
- },
- },
- options: [
- {
- displayName: 'Client ID',
- name: 'client_id',
- type: 'string',
- default: '',
- description: 'Only return time entries belonging to the client with the given ID.',
- },
- {
- displayName: 'From',
- name: 'from',
- type: 'dateTime',
- default: '',
- description: 'Only return time entries with a spent_date on or after the given date.',
- },
- {
- displayName: 'State',
- name: 'state',
- type: 'string',
- default: '',
- description: 'Only return estimates with a state matching the value provided. Options: draft, sent, accepted, or declined.',
- },
- {
- displayName: 'To',
- name: 'to',
- type: 'dateTime',
- default: '',
- description: 'Only return time entries with a spent_date on or before the given date.',
- },
- {
- displayName: 'Updated Since',
- name: 'updated_since',
- type: 'dateTime',
- default: '',
- description: 'Only return time entries that have been updated since the given date and time.',
- },
- {
- displayName: 'Page',
- name: 'page',
- type: 'number',
- typeOptions: {
- minValue: 1,
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ resource,
+ operation: [
+ 'getAll',
+ ],
},
- default: 1,
- description: 'The page number to use in pagination. For instance, if you make a list request and receive 100 records, your subsequent call can include page=2 to retrieve the next page of the list. (Default: 1)',
- },
- ],
-},
-
-/* -------------------------------------------------------------------------- */
-/* estimate:get */
-/* -------------------------------------------------------------------------- */
-{
- displayName: 'Estimate Id',
- name: 'id',
- type: 'string',
- default: '',
- required: true,
- displayOptions: {
- show: {
- operation: [
- 'get',
- ],
- resource,
},
+ default: false,
+ description: 'Returns a list of your estimates.',
},
- description: 'The ID of the estimate you are retrieving.',
-},
-
-/* -------------------------------------------------------------------------- */
-/* estimate:delete */
-/* -------------------------------------------------------------------------- */
-{
- displayName: 'Estimate Id',
- name: 'id',
- type: 'string',
- default: '',
- required: true,
- displayOptions: {
- show: {
- operation: [
- 'delete',
- ],
- resource,
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ resource,
+ operation: [
+ 'getAll',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
},
+ typeOptions: {
+ minValue: 1,
+ maxValue: 100,
+ },
+ default: 100,
+ description: 'How many results to return.',
+ },
+ {
+ displayName: 'Filters',
+ name: 'filters',
+ type: 'collection',
+ placeholder: 'Add Filter',
+ default: {},
+ displayOptions: {
+ show: {
+ resource,
+ operation: [
+ 'getAll',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Client ID',
+ name: 'client_id',
+ type: 'string',
+ default: '',
+ description: 'Only return time entries belonging to the client with the given ID.',
+ },
+ {
+ displayName: 'From',
+ name: 'from',
+ type: 'dateTime',
+ default: '',
+ description: 'Only return time entries with a spent_date on or after the given date.',
+ },
+ {
+ displayName: 'State',
+ name: 'state',
+ type: 'string',
+ default: '',
+ description: 'Only return estimates with a state matching the value provided. Options: draft, sent, accepted, or declined.',
+ },
+ {
+ displayName: 'To',
+ name: 'to',
+ type: 'dateTime',
+ default: '',
+ description: 'Only return time entries with a spent_date on or before the given date.',
+ },
+ {
+ displayName: 'Updated Since',
+ name: 'updated_since',
+ type: 'dateTime',
+ default: '',
+ description: 'Only return time entries that have been updated since the given date and time.',
+ },
+ {
+ displayName: 'Page',
+ name: 'page',
+ type: 'number',
+ typeOptions: {
+ minValue: 1,
+ },
+ default: 1,
+ description: 'The page number to use in pagination. For instance, if you make a list request and receive 100 records, your subsequent call can include page=2 to retrieve the next page of the list. (Default: 1)',
+ },
+ ],
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* estimate:get */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Estimate Id',
+ name: 'id',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ operation: [
+ 'get',
+ ],
+ resource,
+ },
+ },
+ description: 'The ID of the estimate you are retrieving.',
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* estimate:delete */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Estimate Id',
+ name: 'id',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ operation: [
+ 'delete',
+ ],
+ resource,
+ },
+ },
+ description: 'The ID of the estimate want to delete.',
},
- description: 'The ID of the estimate want to delete.',
-},
/* -------------------------------------------------------------------------- */
/* estimate:create */
@@ -291,7 +291,7 @@ export const estimateFields = [
],
},
- /* -------------------------------------------------------------------------- */
+ /* -------------------------------------------------------------------------- */
/* estimate:update */
/* -------------------------------------------------------------------------- */
{
diff --git a/packages/nodes-base/nodes/Harvest/ExpenseDescription.ts b/packages/nodes-base/nodes/Harvest/ExpenseDescription.ts
index 0b78b10543..a32e836c4b 100644
--- a/packages/nodes-base/nodes/Harvest/ExpenseDescription.ts
+++ b/packages/nodes-base/nodes/Harvest/ExpenseDescription.ts
@@ -1,6 +1,6 @@
import { INodeProperties } from 'n8n-workflow';
-const resource = [ 'expense' ];
+const resource = ['expense'];
export const expenseOperations = [
{
@@ -47,163 +47,163 @@ export const expenseOperations = [
export const expenseFields = [
-/* -------------------------------------------------------------------------- */
-/* expense:getAll */
-/* -------------------------------------------------------------------------- */
+ /* -------------------------------------------------------------------------- */
+ /* expense:getAll */
+ /* -------------------------------------------------------------------------- */
-{
- displayName: 'Return All',
- name: 'returnAll',
- type: 'boolean',
- displayOptions: {
- show: {
- resource,
- operation: [
- 'getAll',
- ],
- },
- },
- default: false,
- description: 'Returns a list of your expenses.',
-},
-{
- displayName: 'Limit',
- name: 'limit',
- type: 'number',
- displayOptions: {
- show: {
- resource,
- operation: [
- 'getAll',
- ],
- returnAll: [
- false,
- ],
- },
- },
- typeOptions: {
- minValue: 1,
- maxValue: 100,
- },
- default: 100,
- description: 'How many results to return.',
-},
-{
- displayName: 'Filters',
- name: 'filters',
- type: 'collection',
- placeholder: 'Add Filter',
- default: {},
- displayOptions: {
- show: {
- resource,
- operation: [
- 'getAll',
- ],
- },
- },
- options: [
- {
- displayName: 'Client ID',
- name: 'client_id',
- type: 'string',
- default: '',
- description: 'Only return time entries belonging to the client with the given ID.',
- },
- {
- displayName: 'From',
- name: 'from',
- type: 'dateTime',
- default: '',
- description: 'Only return time entries with a spent_date on or after the given date.',
- },
- {
- displayName: 'Is Billed',
- name: 'is_billed',
- type: 'boolean',
- default: false,
- description: 'Pass true to only return time entries that have been invoiced and false to return time entries that have not been invoiced.',
- },
- {
- displayName: 'Page',
- name: 'page',
- type: 'number',
- typeOptions: {
- minValue: 1,
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ resource,
+ operation: [
+ 'getAll',
+ ],
},
- default: 1,
- description: 'The page number to use in pagination. For instance, if you make a list request and receive 100 records, your subsequent call can include page=2 to retrieve the next page of the list. (Default: 1)',
- },
- {
- displayName: 'Project ID',
- name: 'project_id',
- type: 'string',
- default: '',
- description: 'Only return time entries belonging to the client with the given ID.',
- },
- {
- displayName: 'To',
- name: 'to',
- type: 'dateTime',
- default: '',
- description: 'Only return time entries with a spent_date on or before the given date.',
- },
- {
- displayName: 'Updated Since',
- name: 'updated_since',
- type: 'dateTime',
- default: '',
- description: 'Only return time entries that have been updated since the given date and time.',
- },
- {
- displayName: 'User ID',
- name: 'user_id',
- type: 'string',
- default: '',
- description: 'Only return time entries belonging to the user with the given ID.',
- },
- ],
-},
-
-/* -------------------------------------------------------------------------- */
-/* expense:get */
-/* -------------------------------------------------------------------------- */
-{
- displayName: 'Expense Id',
- name: 'id',
- type: 'string',
- default: '',
- required: true,
- displayOptions: {
- show: {
- operation: [
- 'get',
- ],
- resource,
},
+ default: false,
+ description: 'Returns a list of your expenses.',
},
- description: 'The ID of the expense you are retrieving.',
-},
-
-/* -------------------------------------------------------------------------- */
-/* expense:delete */
-/* -------------------------------------------------------------------------- */
-{
- displayName: 'Expense Id',
- name: 'id',
- type: 'string',
- default: '',
- required: true,
- displayOptions: {
- show: {
- operation: [
- 'delete',
- ],
- resource,
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ resource,
+ operation: [
+ 'getAll',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
},
+ typeOptions: {
+ minValue: 1,
+ maxValue: 100,
+ },
+ default: 100,
+ description: 'How many results to return.',
+ },
+ {
+ displayName: 'Filters',
+ name: 'filters',
+ type: 'collection',
+ placeholder: 'Add Filter',
+ default: {},
+ displayOptions: {
+ show: {
+ resource,
+ operation: [
+ 'getAll',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Client ID',
+ name: 'client_id',
+ type: 'string',
+ default: '',
+ description: 'Only return time entries belonging to the client with the given ID.',
+ },
+ {
+ displayName: 'From',
+ name: 'from',
+ type: 'dateTime',
+ default: '',
+ description: 'Only return time entries with a spent_date on or after the given date.',
+ },
+ {
+ displayName: 'Is Billed',
+ name: 'is_billed',
+ type: 'boolean',
+ default: false,
+ description: 'Pass true to only return time entries that have been invoiced and false to return time entries that have not been invoiced.',
+ },
+ {
+ displayName: 'Page',
+ name: 'page',
+ type: 'number',
+ typeOptions: {
+ minValue: 1,
+ },
+ default: 1,
+ description: 'The page number to use in pagination. For instance, if you make a list request and receive 100 records, your subsequent call can include page=2 to retrieve the next page of the list. (Default: 1)',
+ },
+ {
+ displayName: 'Project ID',
+ name: 'project_id',
+ type: 'string',
+ default: '',
+ description: 'Only return time entries belonging to the client with the given ID.',
+ },
+ {
+ displayName: 'To',
+ name: 'to',
+ type: 'dateTime',
+ default: '',
+ description: 'Only return time entries with a spent_date on or before the given date.',
+ },
+ {
+ displayName: 'Updated Since',
+ name: 'updated_since',
+ type: 'dateTime',
+ default: '',
+ description: 'Only return time entries that have been updated since the given date and time.',
+ },
+ {
+ displayName: 'User ID',
+ name: 'user_id',
+ type: 'string',
+ default: '',
+ description: 'Only return time entries belonging to the user with the given ID.',
+ },
+ ],
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* expense:get */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Expense Id',
+ name: 'id',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ operation: [
+ 'get',
+ ],
+ resource,
+ },
+ },
+ description: 'The ID of the expense you are retrieving.',
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* expense:delete */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Expense Id',
+ name: 'id',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ operation: [
+ 'delete',
+ ],
+ resource,
+ },
+ },
+ description: 'The ID of the expense you want to delete.',
},
- description: 'The ID of the expense you want to delete.',
-},
/* -------------------------------------------------------------------------- */
/* expense:create */
@@ -309,7 +309,7 @@ export const expenseFields = [
],
},
- /* -------------------------------------------------------------------------- */
+ /* -------------------------------------------------------------------------- */
/* invoice:update */
/* -------------------------------------------------------------------------- */
{
diff --git a/packages/nodes-base/nodes/Harvest/GenericFunctions.ts b/packages/nodes-base/nodes/Harvest/GenericFunctions.ts
index 2f9cec0738..fd5ed8178c 100644
--- a/packages/nodes-base/nodes/Harvest/GenericFunctions.ts
+++ b/packages/nodes-base/nodes/Harvest/GenericFunctions.ts
@@ -1,60 +1,53 @@
-import { OptionsWithUri } from 'request';
+import {
+ OptionsWithUri,
+} from 'request';
+
import {
IExecuteFunctions,
IExecuteSingleFunctions,
IHookFunctions,
ILoadOptionsFunctions,
} from 'n8n-core';
-import { IDataObject } from 'n8n-workflow';
-export async function harvestApiRequest(
- this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
- method: string,
- qs: IDataObject = {},
- uri: string,
- body: IDataObject = {},
- option: IDataObject = {},
- ): Promise { // tslint:disable-line:no-any
-
- const credentials = this.getCredentials('harvestApi') as IDataObject;
-
- if (credentials === undefined) {
- throw new Error('No credentials got returned!');
- }
-
- qs.access_token = credentials.accessToken;
- qs.account_id = credentials.accountId;
- // Convert to query string into a format the API can read
- const queryStringElements: string[] = [];
- for (const key of Object.keys(qs)) {
- if (Array.isArray(qs[key])) {
- (qs[key] as string[]).forEach(value => {
- queryStringElements.push(`${key}=${value}`);
- });
- } else {
- queryStringElements.push(`${key}=${qs[key]}`);
- }
- }
+import {
+ IDataObject
+} from 'n8n-workflow';
+export async function harvestApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, qs: IDataObject = {}, path: string, body: IDataObject = {}, option: IDataObject = {}, uri?: string): Promise { // tslint:disable-line:no-any
let options: OptionsWithUri = {
+ headers: {
+ 'Harvest-Account-Id': `${this.getNodeParameter('accountId', 0)}`,
+ 'User-Agent': 'Harvest App',
+ 'Authorization': '',
+ },
method,
body,
- uri: `https://api.harvestapp.com/v2/${uri}?${queryStringElements.join('&')}`,
+ uri: uri || `https://api.harvestapp.com/v2/${path}`,
+ qs,
json: true,
- headers: {
- "User-Agent": "Harvest API",
- },
};
options = Object.assign({}, options, option);
if (Object.keys(options.body).length === 0) {
delete options.body;
}
+ const authenticationMethod = this.getNodeParameter('authentication', 0);
try {
- const result = await this.helpers.request!(options);
+ if (authenticationMethod === 'accessToken') {
+ const credentials = this.getCredentials('harvestApi') as IDataObject;
- return result;
+ if (credentials === undefined) {
+ throw new Error('No credentials got returned!');
+ }
+
+ //@ts-ignore
+ options.headers['Authorization'] = `Bearer ${credentials.accessToken}`;
+
+ return await this.helpers.request!(options);
+ } else {
+ return await this.helpers.requestOAuth2!.call(this, 'harvestOAuth2Api', options);
+ }
} catch (error) {
if (error.statusCode === 401) {
// Return a clear error
@@ -76,27 +69,51 @@ export async function harvestApiRequest(
* and return all results
*/
export async function harvestApiRequestAllItems(
- this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
- method: string,
- qs: IDataObject = {},
- uri: string,
- resource: string,
- body: IDataObject = {},
- option: IDataObject = {},
- ): Promise { // tslint:disable-line:no-any
+ this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
+ method: string,
+ qs: IDataObject = {},
+ uri: string,
+ resource: string,
+ body: IDataObject = {},
+ option: IDataObject = {},
+): Promise { // tslint:disable-line:no-any
const returnData: IDataObject[] = [];
let responseData;
- try {
- do {
- responseData = await harvestApiRequest.call(this, method, qs, uri, body, option);
- qs.page = responseData.next_page;
- returnData.push.apply(returnData, responseData[resource]);
- } while (responseData.next_page);
- return returnData;
- } catch(error) {
- throw error;
- }
+ do {
+ responseData = await harvestApiRequest.call(this, method, qs, uri, body, option);
+ qs.page = responseData.next_page;
+ returnData.push.apply(returnData, responseData[resource]);
+ } while (responseData.next_page);
+
+ return returnData;
}
+
+/**
+ * fetch All resource using paginated calls
+ */
+export async function getAllResource(this: IExecuteFunctions | ILoadOptionsFunctions, resource: string, i: number) {
+ const endpoint = resource;
+ const qs: IDataObject = {};
+ const requestMethod = 'GET';
+
+ qs.per_page = 100;
+
+ const additionalFields = this.getNodeParameter('filters', i) as IDataObject;
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
+
+ Object.assign(qs, additionalFields);
+
+ let responseData: IDataObject = {};
+ if (returnAll) {
+ responseData[resource] = await harvestApiRequestAllItems.call(this, requestMethod, qs, endpoint, resource);
+ } else {
+ const limit = this.getNodeParameter('limit', i) as string;
+ qs.per_page = limit;
+ responseData = await harvestApiRequest.call(this, requestMethod, qs, endpoint);
+ }
+ return responseData[resource] as IDataObject[];
+}
+
diff --git a/packages/nodes-base/nodes/Harvest/Harvest.node.ts b/packages/nodes-base/nodes/Harvest/Harvest.node.ts
index c41bc400e3..b7edc1a7d5 100644
--- a/packages/nodes-base/nodes/Harvest/Harvest.node.ts
+++ b/packages/nodes-base/nodes/Harvest/Harvest.node.ts
@@ -1,9 +1,12 @@
import {
IExecuteFunctions,
+ ILoadOptionsFunctions,
} from 'n8n-core';
+
import {
IDataObject,
INodeExecutionData,
+ INodePropertyOptions,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
@@ -12,31 +15,41 @@ import {
clientFields,
clientOperations,
} from './ClientDescription';
+
import {
contactFields,
contactOperations,
} from './ContactDescription';
-import { companyOperations } from './CompanyDescription';
+
+import {
+ companyOperations,
+} from './CompanyDescription';
+
import {
estimateFields,
estimateOperations,
} from './EstimateDescription';
+
import {
expenseFields,
expenseOperations,
} from './ExpenseDescription';
+
import {
+ getAllResource,
harvestApiRequest,
- harvestApiRequestAllItems,
} from './GenericFunctions';
+
import {
invoiceFields,
invoiceOperations,
} from './InvoiceDescription';
+
import {
projectFields,
projectOperations,
} from './ProjectDescription';
+
import {
taskFields,
taskOperations,
@@ -45,36 +58,12 @@ import {
timeEntryFields,
timeEntryOperations,
} from './TimeEntryDescription';
+
import {
userFields,
userOperations,
} from './UserDescription';
-/**
- * fetch All resource using paginated calls
- */
-async function getAllResource(this: IExecuteFunctions, resource: string, i: number) {
- const endpoint = resource;
- const qs: IDataObject = {};
- const requestMethod = 'GET';
-
- qs.per_page = 100;
-
- const additionalFields = this.getNodeParameter('filters', i) as IDataObject;
- const returnAll = this.getNodeParameter('returnAll', i) as boolean;
- Object.assign(qs, additionalFields);
-
- let responseData: IDataObject = {};
- if (returnAll) {
- responseData[resource] = await harvestApiRequestAllItems.call(this, requestMethod, qs, endpoint, resource);
- } else {
- const limit = this.getNodeParameter('limit', i) as string;
- qs.per_page = limit;
- responseData = await harvestApiRequest.call(this, requestMethod, qs, endpoint);
- }
- return responseData[resource] as IDataObject[];
-}
-
export class Harvest implements INodeType {
description: INodeTypeDescription = {
displayName: 'Harvest',
@@ -86,7 +75,7 @@ export class Harvest implements INodeType {
description: 'Access data on Harvest',
defaults: {
name: 'Harvest',
- color: '#22BB44',
+ color: '#e7863f',
},
inputs: ['main'],
outputs: ['main'],
@@ -94,9 +83,46 @@ export class Harvest implements INodeType {
{
name: 'harvestApi',
required: true,
+ displayOptions: {
+ show: {
+ authentication: [
+ 'accessToken',
+ ],
+ },
+ },
+ },
+ {
+ name: 'harvestOAuth2Api',
+ required: true,
+ displayOptions: {
+ show: {
+ authentication: [
+ 'oAuth2',
+ ],
+ },
+ },
},
],
properties: [
+ {
+ displayName: 'Authentication',
+ name: 'authentication',
+ type: 'options',
+ options: [
+ {
+ name: 'Access Token',
+ value: 'accessToken',
+ },
+ {
+ name: 'OAuth2',
+ value: 'oAuth2',
+ },
+ ],
+ default: 'accessToken',
+ description: 'Method of authentication.',
+ },
+
+
{
displayName: 'Resource',
name: 'resource',
@@ -148,6 +174,17 @@ export class Harvest implements INodeType {
description: 'The resource to operate on.',
},
+ {
+ displayName: 'Account ID',
+ name: 'accountId',
+ type: 'options',
+ required: true,
+ typeOptions: {
+ loadOptionsMethod: 'getAccounts',
+ },
+ default: '',
+ },
+
// operations
...clientOperations,
...companyOperations,
@@ -173,6 +210,26 @@ export class Harvest implements INodeType {
],
};
+ methods = {
+ loadOptions: {
+ // Get all the available accounts to display them to user so that he can
+ // select them easily
+ async getAccounts(this: ILoadOptionsFunctions): Promise {
+ const returnData: INodePropertyOptions[] = [];
+ const { accounts } = await harvestApiRequest.call(this, 'GET', {}, '', {}, {}, 'https://id.getharvest.com/api/v2/accounts');
+ for (const account of accounts) {
+ const accountName = account.name;
+ const accountId = account.id;
+ returnData.push({
+ name: accountName,
+ value: accountId,
+ });
+ }
+ return returnData;
+ },
+ },
+ };
+
async execute(this: IExecuteFunctions): Promise {
const items = this.getInputData();
const returnData: IDataObject[] = [];
diff --git a/packages/nodes-base/nodes/Harvest/InvoiceDescription.ts b/packages/nodes-base/nodes/Harvest/InvoiceDescription.ts
index 83c457752b..4778507a1a 100644
--- a/packages/nodes-base/nodes/Harvest/InvoiceDescription.ts
+++ b/packages/nodes-base/nodes/Harvest/InvoiceDescription.ts
@@ -1,6 +1,6 @@
import { INodeProperties } from 'n8n-workflow';
-const resource = [ 'invoice' ];
+const resource = ['invoice'];
export const invoiceOperations = [
{
@@ -47,174 +47,174 @@ export const invoiceOperations = [
export const invoiceFields = [
-/* -------------------------------------------------------------------------- */
-/* invoice:getAll */
-/* -------------------------------------------------------------------------- */
+ /* -------------------------------------------------------------------------- */
+ /* invoice:getAll */
+ /* -------------------------------------------------------------------------- */
-{
- displayName: 'Return All',
- name: 'returnAll',
- type: 'boolean',
- displayOptions: {
- show: {
- resource,
- operation: [
- 'getAll',
- ],
- },
- },
- default: false,
- description: 'Returns a list of your invoices.',
-},
-{
- displayName: 'Limit',
- name: 'limit',
- type: 'number',
- displayOptions: {
- show: {
- resource,
- operation: [
- 'getAll',
- ],
- returnAll: [
- false,
- ],
- },
- },
- typeOptions: {
- minValue: 1,
- maxValue: 100,
- },
- default: 100,
- description: 'How many results to return.',
-},
-{
- displayName: 'Filters',
- name: 'filters',
- type: 'collection',
- placeholder: 'Add Filter',
- default: {},
- displayOptions: {
- show: {
- resource,
- operation: [
- 'getAll',
- ],
- },
- },
- options: [
- {
- displayName: 'Client ID',
- name: 'client_id',
- type: 'string',
- default: '',
- description: 'Only return time entries belonging to the client with the given ID.',
- },
- {
- displayName: 'Project ID',
- name: 'project_id',
- type: 'string',
- default: '',
- description: 'Only return time entries belonging to the client with the given ID.',
- },
- {
- displayName: 'Updated Since',
- name: 'updated_since',
- type: 'dateTime',
- default: '',
- description: 'Only return time entries that have been updated since the given date and time.',
- },
- {
- displayName: 'From',
- name: 'from',
- type: 'dateTime',
- default: '',
- description: 'Only return time entries with a spent_date on or after the given date.',
- },
- {
- displayName: 'To',
- name: 'to',
- type: 'dateTime',
- default: '',
- description: 'Only return time entries with a spent_date on or before the given date.',
- },
- {
- displayName: 'State',
- name: 'state',
- type: 'multiOptions',
- options: [
- {
- name: 'draft',
- value: 'draft',
- },
- {
- name: 'open',
- value: 'open',
- },
- {
- name: 'paid',
- value: 'paid',
- },
- {
- name: 'closed',
- value: 'closed',
- },
- ],
- default: [],
- description: 'Only return invoices with a state matching the value provided. Options: draft, open, paid, or closed.',
- },
- {
- displayName: 'Page',
- name: 'page',
- type: 'number',
- typeOptions: {
- minValue: 1,
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ resource,
+ operation: [
+ 'getAll',
+ ],
},
- default: 1,
- description: 'The page number to use in pagination. For instance, if you make a list request and receive 100 records, your subsequent call can include page=2 to retrieve the next page of the list. (Default: 1)',
- },
- ],
-},
-
-/* -------------------------------------------------------------------------- */
-/* invoice:get */
-/* -------------------------------------------------------------------------- */
-{
- displayName: 'Invoice Id',
- name: 'id',
- type: 'string',
- default: '',
- required: true,
- displayOptions: {
- show: {
- operation: [
- 'get',
- ],
- resource,
},
+ default: false,
+ description: 'Returns a list of your invoices.',
},
- description: 'The ID of the invoice you are retrieving.',
-},
-
-/* -------------------------------------------------------------------------- */
-/* invoice:delete */
-/* -------------------------------------------------------------------------- */
-{
- displayName: 'Invoice Id',
- name: 'id',
- type: 'string',
- default: '',
- required: true,
- displayOptions: {
- show: {
- operation: [
- 'delete',
- ],
- resource,
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ resource,
+ operation: [
+ 'getAll',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
},
+ typeOptions: {
+ minValue: 1,
+ maxValue: 100,
+ },
+ default: 100,
+ description: 'How many results to return.',
+ },
+ {
+ displayName: 'Filters',
+ name: 'filters',
+ type: 'collection',
+ placeholder: 'Add Filter',
+ default: {},
+ displayOptions: {
+ show: {
+ resource,
+ operation: [
+ 'getAll',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Client ID',
+ name: 'client_id',
+ type: 'string',
+ default: '',
+ description: 'Only return time entries belonging to the client with the given ID.',
+ },
+ {
+ displayName: 'Project ID',
+ name: 'project_id',
+ type: 'string',
+ default: '',
+ description: 'Only return time entries belonging to the client with the given ID.',
+ },
+ {
+ displayName: 'Updated Since',
+ name: 'updated_since',
+ type: 'dateTime',
+ default: '',
+ description: 'Only return time entries that have been updated since the given date and time.',
+ },
+ {
+ displayName: 'From',
+ name: 'from',
+ type: 'dateTime',
+ default: '',
+ description: 'Only return time entries with a spent_date on or after the given date.',
+ },
+ {
+ displayName: 'To',
+ name: 'to',
+ type: 'dateTime',
+ default: '',
+ description: 'Only return time entries with a spent_date on or before the given date.',
+ },
+ {
+ displayName: 'State',
+ name: 'state',
+ type: 'multiOptions',
+ options: [
+ {
+ name: 'draft',
+ value: 'draft',
+ },
+ {
+ name: 'open',
+ value: 'open',
+ },
+ {
+ name: 'paid',
+ value: 'paid',
+ },
+ {
+ name: 'closed',
+ value: 'closed',
+ },
+ ],
+ default: [],
+ description: 'Only return invoices with a state matching the value provided. Options: draft, open, paid, or closed.',
+ },
+ {
+ displayName: 'Page',
+ name: 'page',
+ type: 'number',
+ typeOptions: {
+ minValue: 1,
+ },
+ default: 1,
+ description: 'The page number to use in pagination. For instance, if you make a list request and receive 100 records, your subsequent call can include page=2 to retrieve the next page of the list. (Default: 1)',
+ },
+ ],
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* invoice:get */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Invoice Id',
+ name: 'id',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ operation: [
+ 'get',
+ ],
+ resource,
+ },
+ },
+ description: 'The ID of the invoice you are retrieving.',
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* invoice:delete */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Invoice Id',
+ name: 'id',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ operation: [
+ 'delete',
+ ],
+ resource,
+ },
+ },
+ description: 'The ID of the invoice want to delete.',
},
- description: 'The ID of the invoice want to delete.',
-},
/* -------------------------------------------------------------------------- */
/* invoice:create */
@@ -344,7 +344,7 @@ export const invoiceFields = [
],
},
- /* -------------------------------------------------------------------------- */
+ /* -------------------------------------------------------------------------- */
/* invoice:update */
/* -------------------------------------------------------------------------- */
{
diff --git a/packages/nodes-base/nodes/Harvest/TaskDescription.ts b/packages/nodes-base/nodes/Harvest/TaskDescription.ts
index 2be01d5fe9..a4926d70d1 100644
--- a/packages/nodes-base/nodes/Harvest/TaskDescription.ts
+++ b/packages/nodes-base/nodes/Harvest/TaskDescription.ts
@@ -48,7 +48,6 @@ export const taskFields = [
/* -------------------------------------------------------------------------- */
/* task:getAll */
/* -------------------------------------------------------------------------- */
-
{
displayName: 'Return All',
name: 'returnAll',
diff --git a/packages/nodes-base/nodes/Harvest/TimeEntryDescription.ts b/packages/nodes-base/nodes/Harvest/TimeEntryDescription.ts
index be6d6d02b9..b4c40e8ce9 100644
--- a/packages/nodes-base/nodes/Harvest/TimeEntryDescription.ts
+++ b/packages/nodes-base/nodes/Harvest/TimeEntryDescription.ts
@@ -1,5 +1,5 @@
import { INodeProperties } from 'n8n-workflow';
-export const resource = [ 'timeEntry' ];
+export const resource = ['timeEntry'];
export const timeEntryOperations = [
{
displayName: 'Operation',
@@ -63,485 +63,485 @@ export const timeEntryOperations = [
] as INodeProperties[];
export const timeEntryFields = [
-/* -------------------------------------------------------------------------- */
-/* timeEntry:getAll */
-/* -------------------------------------------------------------------------- */
+ /* -------------------------------------------------------------------------- */
+ /* timeEntry:getAll */
+ /* -------------------------------------------------------------------------- */
-{
- displayName: 'Return All',
- name: 'returnAll',
- type: 'boolean',
- displayOptions: {
- show: {
- resource,
- operation: [
- 'getAll',
- ],
- },
- },
- default: false,
- description: 'Returns a list of your time entries.',
-},
-{
- displayName: 'Limit',
- name: 'limit',
- type: 'number',
- displayOptions: {
- show: {
- resource,
- operation: [
- 'getAll',
- ],
- returnAll: [
- false,
- ],
- },
- },
- typeOptions: {
- minValue: 1,
- maxValue: 100,
- },
- default: 100,
- description: 'How many results to return.',
-},
-{
- displayName: 'Filters',
- name: 'filters',
- type: 'collection',
- placeholder: 'Add Filter',
- default: {},
- displayOptions: {
- show: {
- resource,
- operation: [
- 'getAll',
- ],
- },
- },
- options: [
- {
- displayName: 'Client ID',
- name: 'client_id',
- type: 'string',
- default: '',
- description: 'Only return time entries belonging to the client with the given ID.',
- },
- {
- displayName: 'From',
- name: 'from',
- type: 'dateTime',
- default: '',
- description: 'Only return time entries with a spent_date on or after the given date.',
- },
- {
- displayName: 'Is Billed',
- name: 'is_billed',
- type: 'boolean',
- default: true,
- description: 'Pass true to only return time entries that have been invoiced and false to return time entries that have not been invoiced.',
- },
- {
- displayName: 'Is Running',
- name: 'is_running',
- type: 'boolean',
- default: true,
- description: 'Pass true to only return running time entries and false to return non-running time entries.',
- },
- {
- displayName: 'To',
- name: 'to',
- type: 'dateTime',
- default: '',
- description: 'Only return time entries with a spent_date on or before the given date.',
- },
- {
- displayName: 'Updated Since',
- name: 'updated_since',
- type: 'dateTime',
- default: '',
- description: 'Only return time entries that have been updated since the given date and time.',
- },
- {
- displayName: 'Page',
- name: 'page',
- type: 'number',
- typeOptions: {
- minValue: 1,
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ resource,
+ operation: [
+ 'getAll',
+ ],
},
- default: 1,
- description: 'The page number to use in pagination. For instance, if you make a list request and receive 100 records, your subsequent call can include page=2 to retrieve the next page of the list. (Default: 1)',
- },
- {
- displayName: 'User ID',
- name: 'user_id',
- type: 'string',
- default: '',
- description: 'Only return time entries belonging to the user with the given ID.',
- },
- ],
-},
-
-/* -------------------------------------------------------------------------- */
-/* timeEntry:get */
-/* -------------------------------------------------------------------------- */
-{
- displayName: 'Time Entry Id',
- name: 'id',
- type: 'string',
- default: '',
- required: true,
- displayOptions: {
- show: {
- operation: [
- 'get',
- ],
- resource,
},
+ default: false,
+ description: 'Returns a list of your time entries.',
},
- description: 'The ID of the time entry you are retrieving.',
-},
-
-/* -------------------------------------------------------------------------- */
-/* timeEntry:delete */
-/* -------------------------------------------------------------------------- */
-{
- displayName: 'Time Entry Id',
- name: 'id',
- type: 'string',
- default: '',
- required: true,
- displayOptions: {
- show: {
- operation: [
- 'delete',
- ],
- resource,
- },
- },
- description: 'The ID of the time entry you are deleting.',
-},
-
-/* -------------------------------------------------------------------------- */
-/* timeEntry:deleteExternal */
-/* -------------------------------------------------------------------------- */
-{
- displayName: 'Time Entry Id',
- name: 'id',
- type: 'string',
- default: '',
- required: true,
- displayOptions: {
- show: {
- operation: [
- 'deleteExternal',
- ],
- resource,
- },
- },
- description: 'The ID of the time entry whose external reference you are deleting.',
-},
-
-/* -------------------------------------------------------------------------- */
-/* timeEntry:stopTime */
-/* -------------------------------------------------------------------------- */
-{
- displayName: 'Time Entry Id',
- name: 'id',
- type: 'string',
- default: '',
- required: true,
- displayOptions: {
- show: {
- operation: [
- 'stopTime',
- ],
- resource,
- },
- },
- description: 'Stop a running time entry. Stopping a time entry is only possible if it’s currently running.',
-},
-
-/* -------------------------------------------------------------------------- */
-/* timeEntry:restartTime */
-/* -------------------------------------------------------------------------- */
-{
- displayName: 'Time Entry Id',
- name: 'id',
- type: 'string',
- default: '',
- required: true,
- displayOptions: {
- show: {
- operation: [
- 'restartTime',
- ],
- resource,
- },
- },
- description: 'Restart a stopped time entry. Restarting a time entry is only possible if it isn’t currently running.',
-},
-
-/* -------------------------------------------------------------------------- */
-/* timeEntry:update */
-/* -------------------------------------------------------------------------- */
-{
- displayName: 'Time Entry Id',
- name: 'id',
- type: 'string',
- default: '',
- required: true,
- displayOptions: {
- show: {
- operation: [
- 'update',
- ],
- resource,
- },
- },
- description: 'The ID of the time entry to update.',
-},
-{
- displayName: 'Update Fields',
- name: 'updateFields',
- type: 'collection',
- placeholder: 'Add Field',
- displayOptions: {
- show: {
- operation: [
- 'update',
- ],
- resource,
- },
- },
- default: {},
- options: [
- {
- displayName: 'Ended Time',
- name: 'ended_time',
- type: 'string',
- default: '',
- placeholder: '3:00pm',
- description: 'The time the entry ended.',
- },
- {
- displayName: 'Hours',
- name: 'hours',
- type: 'number',
- typeOptions: {
- minValue: 0,
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ resource,
+ operation: [
+ 'getAll',
+ ],
+ returnAll: [
+ false,
+ ],
},
- default: 0,
- description: 'The current amount of time tracked.',
},
- {
- displayName: 'Notes',
- name: 'notes',
- type: 'string',
- default: '',
- description: 'These are notes about the time entry..',
- },
- {
- displayName: 'Started Time',
- name: 'started_time',
- type: 'string',
- default: '',
- placeholder: '3:00pm',
- description: 'The time the entry started. Defaults to the current time. Example: “8:00am”.',
- },
- ],
-},
-
-/* -------------------------------------------------------------------------- */
-/* timeEntry:createByDuration */
-/* -------------------------------------------------------------------------- */
-{
- displayName: 'Project Id',
- name: 'projectId',
- type: 'string',
- displayOptions: {
- show: {
- operation: [
- 'createByDuration',
- ],
- resource,
+ typeOptions: {
+ minValue: 1,
+ maxValue: 100,
},
+ default: 100,
+ description: 'How many results to return.',
},
- default: '',
- required: true,
- description: 'The ID of the project to associate with the time entry.',
-},
-{
- displayName: 'Task Id',
- name: 'taskId',
- type: 'string',
- displayOptions: {
- show: {
- operation: [
- 'createByDuration',
- ],
- resource,
- },
- },
- default: '',
- required: true,
- description: 'The ID of the task to associate with the time entry.',
-},
-{
- displayName: 'Spent Date',
- name: 'spentDate',
- type: 'dateTime',
- displayOptions: {
- show: {
- operation: [
- 'createByDuration',
- ],
- resource,
- },
- },
- default: '',
- required: true,
- description: 'The ISO 8601 formatted date the time entry was spent.',
-},
-{
- displayName: 'Additional Fields',
- name: 'additionalFields',
- type: 'collection',
- placeholder: 'Add Field',
- displayOptions: {
- show: {
- operation: [
- 'createByDuration',
- ],
- resource,
- },
- },
- default: {},
- options: [
- {
- displayName: 'Hours',
- name: 'hours',
- type: 'number',
- typeOptions: {
- minValue: 0,
+ {
+ displayName: 'Filters',
+ name: 'filters',
+ type: 'collection',
+ placeholder: 'Add Filter',
+ default: {},
+ displayOptions: {
+ show: {
+ resource,
+ operation: [
+ 'getAll',
+ ],
},
- default: 0,
- description: 'The current amount of time tracked.',
},
- {
- displayName: 'Notes',
- name: 'notes',
- type: 'string',
- default: '',
- description: 'These are notes about the time entry..',
- },
- {
- displayName: 'User ID',
- name: 'user_id',
- type: 'string',
- default: '',
- description: 'The ID of the user to associate with the time entry. Defaults to the currently authenticated user’s ID.',
- },
- ],
-},
+ options: [
+ {
+ displayName: 'Client ID',
+ name: 'client_id',
+ type: 'string',
+ default: '',
+ description: 'Only return time entries belonging to the client with the given ID.',
+ },
+ {
+ displayName: 'From',
+ name: 'from',
+ type: 'dateTime',
+ default: '',
+ description: 'Only return time entries with a spent_date on or after the given date.',
+ },
+ {
+ displayName: 'Is Billed',
+ name: 'is_billed',
+ type: 'boolean',
+ default: true,
+ description: 'Pass true to only return time entries that have been invoiced and false to return time entries that have not been invoiced.',
+ },
+ {
+ displayName: 'Is Running',
+ name: 'is_running',
+ type: 'boolean',
+ default: true,
+ description: 'Pass true to only return running time entries and false to return non-running time entries.',
+ },
+ {
+ displayName: 'To',
+ name: 'to',
+ type: 'dateTime',
+ default: '',
+ description: 'Only return time entries with a spent_date on or before the given date.',
+ },
+ {
+ displayName: 'Updated Since',
+ name: 'updated_since',
+ type: 'dateTime',
+ default: '',
+ description: 'Only return time entries that have been updated since the given date and time.',
+ },
+ {
+ displayName: 'Page',
+ name: 'page',
+ type: 'number',
+ typeOptions: {
+ minValue: 1,
+ },
+ default: 1,
+ description: 'The page number to use in pagination. For instance, if you make a list request and receive 100 records, your subsequent call can include page=2 to retrieve the next page of the list. (Default: 1)',
+ },
+ {
+ displayName: 'User ID',
+ name: 'user_id',
+ type: 'string',
+ default: '',
+ description: 'Only return time entries belonging to the user with the given ID.',
+ },
+ ],
+ },
-/* -------------------------------------------------------------------------- */
-/* timeEntry:createByStartEnd */
-/* -------------------------------------------------------------------------- */
-{
- displayName: 'Project Id',
- name: 'projectId',
- type: 'string',
- displayOptions: {
- show: {
- operation: [
- 'createByStartEnd',
- ],
- resource,
+ /* -------------------------------------------------------------------------- */
+ /* timeEntry:get */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Time Entry Id',
+ name: 'id',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ operation: [
+ 'get',
+ ],
+ resource,
+ },
},
+ description: 'The ID of the time entry you are retrieving.',
},
- default: '',
- required: true,
- description: 'The ID of the project to associate with the time entry.',
-},
-{
- displayName: 'Task Id',
- name: 'taskId',
- type: 'string',
- displayOptions: {
- show: {
- operation: [
- 'createByStartEnd',
- ],
- resource,
+
+ /* -------------------------------------------------------------------------- */
+ /* timeEntry:delete */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Time Entry Id',
+ name: 'id',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ operation: [
+ 'delete',
+ ],
+ resource,
+ },
},
+ description: 'The ID of the time entry you are deleting.',
},
- default: '',
- required: true,
- description: 'The ID of the task to associate with the time entry.',
-},
-{
- displayName: 'Spent Date',
- name: 'spentDate',
- type: 'dateTime',
- displayOptions: {
- show: {
- operation: [
- 'createByStartEnd',
- ],
- resource,
+
+ /* -------------------------------------------------------------------------- */
+ /* timeEntry:deleteExternal */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Time Entry Id',
+ name: 'id',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ operation: [
+ 'deleteExternal',
+ ],
+ resource,
+ },
},
+ description: 'The ID of the time entry whose external reference you are deleting.',
},
- default: '',
- required: true,
- description: 'The ISO 8601 formatted date the time entry was spent.',
-},
-{
- displayName: 'Additional Fields',
- name: 'additionalFields',
- type: 'collection',
- placeholder: 'Add Field',
- displayOptions: {
- show: {
- operation: [
- 'createByStartEnd',
- ],
- resource,
+
+ /* -------------------------------------------------------------------------- */
+ /* timeEntry:stopTime */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Time Entry Id',
+ name: 'id',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ operation: [
+ 'stopTime',
+ ],
+ resource,
+ },
},
+ description: 'Stop a running time entry. Stopping a time entry is only possible if it’s currently running.',
},
- default: {},
- options: [
- {
- displayName: 'Ended Time',
- name: 'ended_time',
- type: 'string',
- default: '',
- placeholder: '3:00pm',
- description: 'The time the entry ended.',
+
+ /* -------------------------------------------------------------------------- */
+ /* timeEntry:restartTime */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Time Entry Id',
+ name: 'id',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ operation: [
+ 'restartTime',
+ ],
+ resource,
+ },
},
- {
- displayName: 'Notes',
- name: 'notes',
- type: 'string',
- default: '',
- description: 'These are notes about the time entry..',
+ description: 'Restart a stopped time entry. Restarting a time entry is only possible if it isn’t currently running.',
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* timeEntry:update */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Time Entry Id',
+ name: 'id',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ operation: [
+ 'update',
+ ],
+ resource,
+ },
},
- {
- displayName: 'Started Time',
- name: 'started_time',
- type: 'string',
- default: '',
- placeholder: '8:00am',
- description: 'The time the entry started. Defaults to the current time. Example: “8:00am”.',
+ description: 'The ID of the time entry to update.',
+ },
+ {
+ displayName: 'Update Fields',
+ name: 'updateFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ displayOptions: {
+ show: {
+ operation: [
+ 'update',
+ ],
+ resource,
+ },
},
- {
- displayName: 'User ID',
- name: 'user_id',
- type: 'string',
- default: '',
- description: 'The ID of the user to associate with the time entry. Defaults to the currently authenticated user’s ID.',
+ default: {},
+ options: [
+ {
+ displayName: 'Ended Time',
+ name: 'ended_time',
+ type: 'string',
+ default: '',
+ placeholder: '3:00pm',
+ description: 'The time the entry ended.',
+ },
+ {
+ displayName: 'Hours',
+ name: 'hours',
+ type: 'number',
+ typeOptions: {
+ minValue: 0,
+ },
+ default: 0,
+ description: 'The current amount of time tracked.',
+ },
+ {
+ displayName: 'Notes',
+ name: 'notes',
+ type: 'string',
+ default: '',
+ description: 'These are notes about the time entry..',
+ },
+ {
+ displayName: 'Started Time',
+ name: 'started_time',
+ type: 'string',
+ default: '',
+ placeholder: '3:00pm',
+ description: 'The time the entry started. Defaults to the current time. Example: “8:00am”.',
+ },
+ ],
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* timeEntry:createByDuration */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Project Id',
+ name: 'projectId',
+ type: 'string',
+ displayOptions: {
+ show: {
+ operation: [
+ 'createByDuration',
+ ],
+ resource,
+ },
},
- ],
-},
+ default: '',
+ required: true,
+ description: 'The ID of the project to associate with the time entry.',
+ },
+ {
+ displayName: 'Task Id',
+ name: 'taskId',
+ type: 'string',
+ displayOptions: {
+ show: {
+ operation: [
+ 'createByDuration',
+ ],
+ resource,
+ },
+ },
+ default: '',
+ required: true,
+ description: 'The ID of the task to associate with the time entry.',
+ },
+ {
+ displayName: 'Spent Date',
+ name: 'spentDate',
+ type: 'dateTime',
+ displayOptions: {
+ show: {
+ operation: [
+ 'createByDuration',
+ ],
+ resource,
+ },
+ },
+ default: '',
+ required: true,
+ description: 'The ISO 8601 formatted date the time entry was spent.',
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ displayOptions: {
+ show: {
+ operation: [
+ 'createByDuration',
+ ],
+ resource,
+ },
+ },
+ default: {},
+ options: [
+ {
+ displayName: 'Hours',
+ name: 'hours',
+ type: 'number',
+ typeOptions: {
+ minValue: 0,
+ },
+ default: 0,
+ description: 'The current amount of time tracked.',
+ },
+ {
+ displayName: 'Notes',
+ name: 'notes',
+ type: 'string',
+ default: '',
+ description: 'These are notes about the time entry..',
+ },
+ {
+ displayName: 'User ID',
+ name: 'user_id',
+ type: 'string',
+ default: '',
+ description: 'The ID of the user to associate with the time entry. Defaults to the currently authenticated user’s ID.',
+ },
+ ],
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* timeEntry:createByStartEnd */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Project Id',
+ name: 'projectId',
+ type: 'string',
+ displayOptions: {
+ show: {
+ operation: [
+ 'createByStartEnd',
+ ],
+ resource,
+ },
+ },
+ default: '',
+ required: true,
+ description: 'The ID of the project to associate with the time entry.',
+ },
+ {
+ displayName: 'Task Id',
+ name: 'taskId',
+ type: 'string',
+ displayOptions: {
+ show: {
+ operation: [
+ 'createByStartEnd',
+ ],
+ resource,
+ },
+ },
+ default: '',
+ required: true,
+ description: 'The ID of the task to associate with the time entry.',
+ },
+ {
+ displayName: 'Spent Date',
+ name: 'spentDate',
+ type: 'dateTime',
+ displayOptions: {
+ show: {
+ operation: [
+ 'createByStartEnd',
+ ],
+ resource,
+ },
+ },
+ default: '',
+ required: true,
+ description: 'The ISO 8601 formatted date the time entry was spent.',
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ displayOptions: {
+ show: {
+ operation: [
+ 'createByStartEnd',
+ ],
+ resource,
+ },
+ },
+ default: {},
+ options: [
+ {
+ displayName: 'Ended Time',
+ name: 'ended_time',
+ type: 'string',
+ default: '',
+ placeholder: '3:00pm',
+ description: 'The time the entry ended.',
+ },
+ {
+ displayName: 'Notes',
+ name: 'notes',
+ type: 'string',
+ default: '',
+ description: 'These are notes about the time entry..',
+ },
+ {
+ displayName: 'Started Time',
+ name: 'started_time',
+ type: 'string',
+ default: '',
+ placeholder: '8:00am',
+ description: 'The time the entry started. Defaults to the current time. Example: “8:00am”.',
+ },
+ {
+ displayName: 'User ID',
+ name: 'user_id',
+ type: 'string',
+ default: '',
+ description: 'The ID of the user to associate with the time entry. Defaults to the currently authenticated user’s ID.',
+ },
+ ],
+ },
diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json
index b08d3ac0f3..e2c4f39c14 100644
--- a/packages/nodes-base/package.json
+++ b/packages/nodes-base/package.json
@@ -95,6 +95,7 @@
"dist/credentials/YouTubeOAuth2Api.credentials.js",
"dist/credentials/GumroadApi.credentials.js",
"dist/credentials/HarvestApi.credentials.js",
+ "dist/credentials/HarvestOAuth2Api.credentials.js",
"dist/credentials/HelpScoutOAuth2Api.credentials.js",
"dist/credentials/HttpBasicAuth.credentials.js",
"dist/credentials/HttpDigestAuth.credentials.js",