Add Harvest OAuth2 support (#697)

* credentials, generic functions fit to oauth2, UI elements

*  Improvements to Harvest-Node

* Update Harvest.node.ts

*  Add OAuth2 and move account id from credentials to node parameters

*  Minor improvements to Harvest-Node

Co-authored-by: Rupenieks <ru@myos,co>
Co-authored-by: ricardo <ricardoespinoza105@gmail.com>
Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
Ronalds Upenieks 2020-11-24 14:15:47 +01:00 committed by GitHub
parent af9d8e1cba
commit 3351113f28
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 1133 additions and 1004 deletions

View file

@ -2,6 +2,20 @@
This list shows all the versions which include breaking changes and how to upgrade. 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 ## 0.94.0
### What changed? ### What changed?

View file

@ -8,13 +8,6 @@ export class HarvestApi implements ICredentialType {
displayName = 'Harvest API'; displayName = 'Harvest API';
documentationUrl = 'harvest'; documentationUrl = 'harvest';
properties = [ properties = [
{
displayName: 'Account ID',
name: 'accountId',
type: 'string' as NodePropertyTypes,
default: '',
description: 'Visit your account details page, and grab the Account ID. See <a href="https://help.getharvest.com/api-v2/authentication-api/authentication/authentication/">Harvest Personal Access Tokens</a>.',
},
{ {
displayName: 'Access Token', displayName: 'Access Token',
name: 'accessToken', name: 'accessToken',

View file

@ -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.',
},
];
}

View file

@ -1,6 +1,6 @@
import { INodeProperties } from 'n8n-workflow'; import { INodeProperties } from 'n8n-workflow';
const resource = [ 'company' ]; const resource = ['company'];
export const companyOperations = [ export const companyOperations = [
{ {

View file

@ -1,6 +1,6 @@
import { INodeProperties } from 'n8n-workflow'; import { INodeProperties } from 'n8n-workflow';
const resource = [ 'estimate' ]; const resource = ['estimate'];
export const estimateOperations = [ export const estimateOperations = [
{ {
@ -47,11 +47,11 @@ export const estimateOperations = [
export const estimateFields = [ export const estimateFields = [
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* estimate:getAll */ /* estimate:getAll */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Return All', displayName: 'Return All',
name: 'returnAll', name: 'returnAll',
type: 'boolean', type: 'boolean',
@ -65,8 +65,8 @@ export const estimateFields = [
}, },
default: false, default: false,
description: 'Returns a list of your estimates.', description: 'Returns a list of your estimates.',
}, },
{ {
displayName: 'Limit', displayName: 'Limit',
name: 'limit', name: 'limit',
type: 'number', type: 'number',
@ -87,8 +87,8 @@ export const estimateFields = [
}, },
default: 100, default: 100,
description: 'How many results to return.', description: 'How many results to return.',
}, },
{ {
displayName: 'Filters', displayName: 'Filters',
name: 'filters', name: 'filters',
type: 'collection', type: 'collection',
@ -149,12 +149,12 @@ export const estimateFields = [
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)', 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 */ /* estimate:get */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Estimate Id', displayName: 'Estimate Id',
name: 'id', name: 'id',
type: 'string', type: 'string',
@ -169,12 +169,12 @@ export const estimateFields = [
}, },
}, },
description: 'The ID of the estimate you are retrieving.', description: 'The ID of the estimate you are retrieving.',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* estimate:delete */ /* estimate:delete */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Estimate Id', displayName: 'Estimate Id',
name: 'id', name: 'id',
type: 'string', type: 'string',
@ -189,7 +189,7 @@ export const estimateFields = [
}, },
}, },
description: 'The ID of the estimate want to delete.', description: 'The ID of the estimate want to delete.',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* estimate:create */ /* estimate:create */

View file

@ -1,6 +1,6 @@
import { INodeProperties } from 'n8n-workflow'; import { INodeProperties } from 'n8n-workflow';
const resource = [ 'expense' ]; const resource = ['expense'];
export const expenseOperations = [ export const expenseOperations = [
{ {
@ -47,11 +47,11 @@ export const expenseOperations = [
export const expenseFields = [ export const expenseFields = [
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* expense:getAll */ /* expense:getAll */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Return All', displayName: 'Return All',
name: 'returnAll', name: 'returnAll',
type: 'boolean', type: 'boolean',
@ -65,8 +65,8 @@ export const expenseFields = [
}, },
default: false, default: false,
description: 'Returns a list of your expenses.', description: 'Returns a list of your expenses.',
}, },
{ {
displayName: 'Limit', displayName: 'Limit',
name: 'limit', name: 'limit',
type: 'number', type: 'number',
@ -87,8 +87,8 @@ export const expenseFields = [
}, },
default: 100, default: 100,
description: 'How many results to return.', description: 'How many results to return.',
}, },
{ {
displayName: 'Filters', displayName: 'Filters',
name: 'filters', name: 'filters',
type: 'collection', type: 'collection',
@ -163,12 +163,12 @@ export const expenseFields = [
description: 'Only return time entries belonging to the user with the given ID.', description: 'Only return time entries belonging to the user with the given ID.',
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* expense:get */ /* expense:get */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Expense Id', displayName: 'Expense Id',
name: 'id', name: 'id',
type: 'string', type: 'string',
@ -183,12 +183,12 @@ export const expenseFields = [
}, },
}, },
description: 'The ID of the expense you are retrieving.', description: 'The ID of the expense you are retrieving.',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* expense:delete */ /* expense:delete */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Expense Id', displayName: 'Expense Id',
name: 'id', name: 'id',
type: 'string', type: 'string',
@ -203,7 +203,7 @@ export const expenseFields = [
}, },
}, },
description: 'The ID of the expense you want to delete.', description: 'The ID of the expense you want to delete.',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* expense:create */ /* expense:create */

View file

@ -1,60 +1,53 @@
import { OptionsWithUri } from 'request'; import {
OptionsWithUri,
} from 'request';
import { import {
IExecuteFunctions, IExecuteFunctions,
IExecuteSingleFunctions, IExecuteSingleFunctions,
IHookFunctions, IHookFunctions,
ILoadOptionsFunctions, ILoadOptionsFunctions,
} from 'n8n-core'; } from 'n8n-core';
import { IDataObject } from 'n8n-workflow';
export async function harvestApiRequest( import {
this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, IDataObject
method: string, } from 'n8n-workflow';
qs: IDataObject = {},
uri: string,
body: IDataObject = {},
option: IDataObject = {},
): Promise<any> { // 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]}`);
}
}
export async function harvestApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, qs: IDataObject = {}, path: string, body: IDataObject = {}, option: IDataObject = {}, uri?: string): Promise<any> { // tslint:disable-line:no-any
let options: OptionsWithUri = { let options: OptionsWithUri = {
headers: {
'Harvest-Account-Id': `${this.getNodeParameter('accountId', 0)}`,
'User-Agent': 'Harvest App',
'Authorization': '',
},
method, method,
body, body,
uri: `https://api.harvestapp.com/v2/${uri}?${queryStringElements.join('&')}`, uri: uri || `https://api.harvestapp.com/v2/${path}`,
qs,
json: true, json: true,
headers: {
"User-Agent": "Harvest API",
},
}; };
options = Object.assign({}, options, option); options = Object.assign({}, options, option);
if (Object.keys(options.body).length === 0) { if (Object.keys(options.body).length === 0) {
delete options.body; delete options.body;
} }
const authenticationMethod = this.getNodeParameter('authentication', 0);
try { 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) { } catch (error) {
if (error.statusCode === 401) { if (error.statusCode === 401) {
// Return a clear error // Return a clear error
@ -83,20 +76,44 @@ export async function harvestApiRequestAllItems(
resource: string, resource: string,
body: IDataObject = {}, body: IDataObject = {},
option: IDataObject = {}, option: IDataObject = {},
): Promise<any> { // tslint:disable-line:no-any ): Promise<any> { // tslint:disable-line:no-any
const returnData: IDataObject[] = []; const returnData: IDataObject[] = [];
let responseData; let responseData;
try {
do { do {
responseData = await harvestApiRequest.call(this, method, qs, uri, body, option); responseData = await harvestApiRequest.call(this, method, qs, uri, body, option);
qs.page = responseData.next_page; qs.page = responseData.next_page;
returnData.push.apply(returnData, responseData[resource]); returnData.push.apply(returnData, responseData[resource]);
} while (responseData.next_page); } while (responseData.next_page);
return returnData; return returnData;
} catch(error) {
throw error;
}
} }
/**
* 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[];
}

View file

@ -1,9 +1,12 @@
import { import {
IExecuteFunctions, IExecuteFunctions,
ILoadOptionsFunctions,
} from 'n8n-core'; } from 'n8n-core';
import { import {
IDataObject, IDataObject,
INodeExecutionData, INodeExecutionData,
INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
} from 'n8n-workflow'; } from 'n8n-workflow';
@ -12,31 +15,41 @@ import {
clientFields, clientFields,
clientOperations, clientOperations,
} from './ClientDescription'; } from './ClientDescription';
import { import {
contactFields, contactFields,
contactOperations, contactOperations,
} from './ContactDescription'; } from './ContactDescription';
import { companyOperations } from './CompanyDescription';
import {
companyOperations,
} from './CompanyDescription';
import { import {
estimateFields, estimateFields,
estimateOperations, estimateOperations,
} from './EstimateDescription'; } from './EstimateDescription';
import { import {
expenseFields, expenseFields,
expenseOperations, expenseOperations,
} from './ExpenseDescription'; } from './ExpenseDescription';
import { import {
getAllResource,
harvestApiRequest, harvestApiRequest,
harvestApiRequestAllItems,
} from './GenericFunctions'; } from './GenericFunctions';
import { import {
invoiceFields, invoiceFields,
invoiceOperations, invoiceOperations,
} from './InvoiceDescription'; } from './InvoiceDescription';
import { import {
projectFields, projectFields,
projectOperations, projectOperations,
} from './ProjectDescription'; } from './ProjectDescription';
import { import {
taskFields, taskFields,
taskOperations, taskOperations,
@ -45,36 +58,12 @@ import {
timeEntryFields, timeEntryFields,
timeEntryOperations, timeEntryOperations,
} from './TimeEntryDescription'; } from './TimeEntryDescription';
import { import {
userFields, userFields,
userOperations, userOperations,
} from './UserDescription'; } 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 { export class Harvest implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
displayName: 'Harvest', displayName: 'Harvest',
@ -86,7 +75,7 @@ export class Harvest implements INodeType {
description: 'Access data on Harvest', description: 'Access data on Harvest',
defaults: { defaults: {
name: 'Harvest', name: 'Harvest',
color: '#22BB44', color: '#e7863f',
}, },
inputs: ['main'], inputs: ['main'],
outputs: ['main'], outputs: ['main'],
@ -94,9 +83,46 @@ export class Harvest implements INodeType {
{ {
name: 'harvestApi', name: 'harvestApi',
required: true, required: true,
displayOptions: {
show: {
authentication: [
'accessToken',
],
},
},
},
{
name: 'harvestOAuth2Api',
required: true,
displayOptions: {
show: {
authentication: [
'oAuth2',
],
},
},
}, },
], ],
properties: [ 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', displayName: 'Resource',
name: 'resource', name: 'resource',
@ -148,6 +174,17 @@ export class Harvest implements INodeType {
description: 'The resource to operate on.', description: 'The resource to operate on.',
}, },
{
displayName: 'Account ID',
name: 'accountId',
type: 'options',
required: true,
typeOptions: {
loadOptionsMethod: 'getAccounts',
},
default: '',
},
// operations // operations
...clientOperations, ...clientOperations,
...companyOperations, ...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<INodePropertyOptions[]> {
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<INodeExecutionData[][]> { async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData(); const items = this.getInputData();
const returnData: IDataObject[] = []; const returnData: IDataObject[] = [];

View file

@ -1,6 +1,6 @@
import { INodeProperties } from 'n8n-workflow'; import { INodeProperties } from 'n8n-workflow';
const resource = [ 'invoice' ]; const resource = ['invoice'];
export const invoiceOperations = [ export const invoiceOperations = [
{ {
@ -47,11 +47,11 @@ export const invoiceOperations = [
export const invoiceFields = [ export const invoiceFields = [
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* invoice:getAll */ /* invoice:getAll */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Return All', displayName: 'Return All',
name: 'returnAll', name: 'returnAll',
type: 'boolean', type: 'boolean',
@ -65,8 +65,8 @@ export const invoiceFields = [
}, },
default: false, default: false,
description: 'Returns a list of your invoices.', description: 'Returns a list of your invoices.',
}, },
{ {
displayName: 'Limit', displayName: 'Limit',
name: 'limit', name: 'limit',
type: 'number', type: 'number',
@ -87,8 +87,8 @@ export const invoiceFields = [
}, },
default: 100, default: 100,
description: 'How many results to return.', description: 'How many results to return.',
}, },
{ {
displayName: 'Filters', displayName: 'Filters',
name: 'filters', name: 'filters',
type: 'collection', type: 'collection',
@ -174,12 +174,12 @@ export const invoiceFields = [
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)', 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 */ /* invoice:get */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Invoice Id', displayName: 'Invoice Id',
name: 'id', name: 'id',
type: 'string', type: 'string',
@ -194,12 +194,12 @@ export const invoiceFields = [
}, },
}, },
description: 'The ID of the invoice you are retrieving.', description: 'The ID of the invoice you are retrieving.',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* invoice:delete */ /* invoice:delete */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Invoice Id', displayName: 'Invoice Id',
name: 'id', name: 'id',
type: 'string', type: 'string',
@ -214,7 +214,7 @@ export const invoiceFields = [
}, },
}, },
description: 'The ID of the invoice want to delete.', description: 'The ID of the invoice want to delete.',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* invoice:create */ /* invoice:create */

View file

@ -48,7 +48,6 @@ export const taskFields = [
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* task:getAll */ /* task:getAll */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Return All', displayName: 'Return All',
name: 'returnAll', name: 'returnAll',

View file

@ -1,5 +1,5 @@
import { INodeProperties } from 'n8n-workflow'; import { INodeProperties } from 'n8n-workflow';
export const resource = [ 'timeEntry' ]; export const resource = ['timeEntry'];
export const timeEntryOperations = [ export const timeEntryOperations = [
{ {
displayName: 'Operation', displayName: 'Operation',
@ -63,11 +63,11 @@ export const timeEntryOperations = [
] as INodeProperties[]; ] as INodeProperties[];
export const timeEntryFields = [ export const timeEntryFields = [
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* timeEntry:getAll */ /* timeEntry:getAll */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Return All', displayName: 'Return All',
name: 'returnAll', name: 'returnAll',
type: 'boolean', type: 'boolean',
@ -81,8 +81,8 @@ export const timeEntryFields = [
}, },
default: false, default: false,
description: 'Returns a list of your time entries.', description: 'Returns a list of your time entries.',
}, },
{ {
displayName: 'Limit', displayName: 'Limit',
name: 'limit', name: 'limit',
type: 'number', type: 'number',
@ -103,8 +103,8 @@ export const timeEntryFields = [
}, },
default: 100, default: 100,
description: 'How many results to return.', description: 'How many results to return.',
}, },
{ {
displayName: 'Filters', displayName: 'Filters',
name: 'filters', name: 'filters',
type: 'collection', type: 'collection',
@ -179,12 +179,12 @@ export const timeEntryFields = [
description: 'Only return time entries belonging to the user with the given ID.', description: 'Only return time entries belonging to the user with the given ID.',
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* timeEntry:get */ /* timeEntry:get */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Time Entry Id', displayName: 'Time Entry Id',
name: 'id', name: 'id',
type: 'string', type: 'string',
@ -199,12 +199,12 @@ export const timeEntryFields = [
}, },
}, },
description: 'The ID of the time entry you are retrieving.', description: 'The ID of the time entry you are retrieving.',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* timeEntry:delete */ /* timeEntry:delete */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Time Entry Id', displayName: 'Time Entry Id',
name: 'id', name: 'id',
type: 'string', type: 'string',
@ -219,12 +219,12 @@ export const timeEntryFields = [
}, },
}, },
description: 'The ID of the time entry you are deleting.', description: 'The ID of the time entry you are deleting.',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* timeEntry:deleteExternal */ /* timeEntry:deleteExternal */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Time Entry Id', displayName: 'Time Entry Id',
name: 'id', name: 'id',
type: 'string', type: 'string',
@ -239,12 +239,12 @@ export const timeEntryFields = [
}, },
}, },
description: 'The ID of the time entry whose external reference you are deleting.', description: 'The ID of the time entry whose external reference you are deleting.',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* timeEntry:stopTime */ /* timeEntry:stopTime */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Time Entry Id', displayName: 'Time Entry Id',
name: 'id', name: 'id',
type: 'string', type: 'string',
@ -259,12 +259,12 @@ export const timeEntryFields = [
}, },
}, },
description: 'Stop a running time entry. Stopping a time entry is only possible if its currently running.', description: 'Stop a running time entry. Stopping a time entry is only possible if its currently running.',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* timeEntry:restartTime */ /* timeEntry:restartTime */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Time Entry Id', displayName: 'Time Entry Id',
name: 'id', name: 'id',
type: 'string', type: 'string',
@ -279,12 +279,12 @@ export const timeEntryFields = [
}, },
}, },
description: 'Restart a stopped time entry. Restarting a time entry is only possible if it isnt currently running.', description: 'Restart a stopped time entry. Restarting a time entry is only possible if it isnt currently running.',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* timeEntry:update */ /* timeEntry:update */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Time Entry Id', displayName: 'Time Entry Id',
name: 'id', name: 'id',
type: 'string', type: 'string',
@ -299,8 +299,8 @@ export const timeEntryFields = [
}, },
}, },
description: 'The ID of the time entry to update.', description: 'The ID of the time entry to update.',
}, },
{ {
displayName: 'Update Fields', displayName: 'Update Fields',
name: 'updateFields', name: 'updateFields',
type: 'collection', type: 'collection',
@ -349,12 +349,12 @@ export const timeEntryFields = [
description: 'The time the entry started. Defaults to the current time. Example: “8:00am”.', description: 'The time the entry started. Defaults to the current time. Example: “8:00am”.',
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* timeEntry:createByDuration */ /* timeEntry:createByDuration */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Project Id', displayName: 'Project Id',
name: 'projectId', name: 'projectId',
type: 'string', type: 'string',
@ -369,8 +369,8 @@ export const timeEntryFields = [
default: '', default: '',
required: true, required: true,
description: 'The ID of the project to associate with the time entry.', description: 'The ID of the project to associate with the time entry.',
}, },
{ {
displayName: 'Task Id', displayName: 'Task Id',
name: 'taskId', name: 'taskId',
type: 'string', type: 'string',
@ -385,8 +385,8 @@ export const timeEntryFields = [
default: '', default: '',
required: true, required: true,
description: 'The ID of the task to associate with the time entry.', description: 'The ID of the task to associate with the time entry.',
}, },
{ {
displayName: 'Spent Date', displayName: 'Spent Date',
name: 'spentDate', name: 'spentDate',
type: 'dateTime', type: 'dateTime',
@ -401,8 +401,8 @@ export const timeEntryFields = [
default: '', default: '',
required: true, required: true,
description: 'The ISO 8601 formatted date the time entry was spent.', description: 'The ISO 8601 formatted date the time entry was spent.',
}, },
{ {
displayName: 'Additional Fields', displayName: 'Additional Fields',
name: 'additionalFields', name: 'additionalFields',
type: 'collection', type: 'collection',
@ -442,12 +442,12 @@ export const timeEntryFields = [
description: 'The ID of the user to associate with the time entry. Defaults to the currently authenticated users ID.', description: 'The ID of the user to associate with the time entry. Defaults to the currently authenticated users ID.',
}, },
], ],
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* timeEntry:createByStartEnd */ /* timeEntry:createByStartEnd */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Project Id', displayName: 'Project Id',
name: 'projectId', name: 'projectId',
type: 'string', type: 'string',
@ -462,8 +462,8 @@ export const timeEntryFields = [
default: '', default: '',
required: true, required: true,
description: 'The ID of the project to associate with the time entry.', description: 'The ID of the project to associate with the time entry.',
}, },
{ {
displayName: 'Task Id', displayName: 'Task Id',
name: 'taskId', name: 'taskId',
type: 'string', type: 'string',
@ -478,8 +478,8 @@ export const timeEntryFields = [
default: '', default: '',
required: true, required: true,
description: 'The ID of the task to associate with the time entry.', description: 'The ID of the task to associate with the time entry.',
}, },
{ {
displayName: 'Spent Date', displayName: 'Spent Date',
name: 'spentDate', name: 'spentDate',
type: 'dateTime', type: 'dateTime',
@ -494,8 +494,8 @@ export const timeEntryFields = [
default: '', default: '',
required: true, required: true,
description: 'The ISO 8601 formatted date the time entry was spent.', description: 'The ISO 8601 formatted date the time entry was spent.',
}, },
{ {
displayName: 'Additional Fields', displayName: 'Additional Fields',
name: 'additionalFields', name: 'additionalFields',
type: 'collection', type: 'collection',
@ -541,7 +541,7 @@ export const timeEntryFields = [
description: 'The ID of the user to associate with the time entry. Defaults to the currently authenticated users ID.', description: 'The ID of the user to associate with the time entry. Defaults to the currently authenticated users ID.',
}, },
], ],
}, },

View file

@ -95,6 +95,7 @@
"dist/credentials/YouTubeOAuth2Api.credentials.js", "dist/credentials/YouTubeOAuth2Api.credentials.js",
"dist/credentials/GumroadApi.credentials.js", "dist/credentials/GumroadApi.credentials.js",
"dist/credentials/HarvestApi.credentials.js", "dist/credentials/HarvestApi.credentials.js",
"dist/credentials/HarvestOAuth2Api.credentials.js",
"dist/credentials/HelpScoutOAuth2Api.credentials.js", "dist/credentials/HelpScoutOAuth2Api.credentials.js",
"dist/credentials/HttpBasicAuth.credentials.js", "dist/credentials/HttpBasicAuth.credentials.js",
"dist/credentials/HttpDigestAuth.credentials.js", "dist/credentials/HttpDigestAuth.credentials.js",