From dc77594a1eaec73fa34ed09c52d108482002ffff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Silva?= Date: Fri, 27 May 2022 17:15:05 +0100 Subject: [PATCH] feat(Todoist Node): Add support for specifying the parent task when adding and listing tasks (#3161) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Todoist: Update parent field to parent_id `parent` is deprecated. * Todoist: Move Labels to "additional fields" when adding a task Improves consistency with other operations. * Todoist: Improve section re-load when switching projects It only worked when creating tasks, due to naming and nesting inconsistencies between different operations. Making it consistent adds predictability and allows reuse between operations. * Todoist: Add support for parent_id when creating and listing tasks * :zap: fixes * :zap: test credentials fix * :zap: parameters naming fix * :zap: undo for breaking change * feat(Salesforce Node): Add country field (#3314) * fix(Slack Node): Fix Channel->Kick (#3365) * feat(core): Allow credential reuse on HTTP Request node (#3228) * :sparkles: Create controller * :zap: Mount controller * :pencil2: Add error messages * :sparkles: Create scopes fetcher * :zap: Account for non-existent credential type * :blue_book: Type scopes request * :zap: Adjust error message * :test_tube: Add tests * :sparkles: Introduce simple node versioning * :zap: Add example how to read version in node-code for custom logic * :bug: Fix setting of parameters * :bug: Fix another instance where it sets the wrong parameter * :zap: Remove unnecessary TOODs * :sparkles: Re-version HTTP Request node * :shirt: Satisfy linter * :zap: Retrieve node version * :rewind: Undo Jan's changes to Set node * :test_tube: Fix CI/CD for `/oauth2-credential` tests (#3230) * :bug: Fix notice warning missing background color (#3231) * :bug: Check for generic auth in node cred types * :zap: Refactor credentials dropdown for HTTP Request node (#3222) * :zap: Discoverability flow (#3229) * :sparkles: Added node credentials type proxy. Changed node credentials input order. * :zap: Add computed property from versioning branch * :bug: Fix cred ref lost and unsaved * :zap: Make options consistent with cred type names * :zap: Use prop to set component order * :zap: Use constant and version * :zap: Fix rendering for generic auth creds * :zap: Mark as required on first selection * :zap: Implement discoverability flow * :zap: Mark as required on subsequent selections * :zap: Fix marking as required after cred deletion * :zap: Refactor to clean up * :zap: Detect position automatically * :zap: Add i18n to option label * :zap: Hide subtitle for custom action * :zap: Detect active credential type * :zap: Prop drilling to re-render select * :fire: Remove unneeded property * :pencil2: Rename arg * :fire: Remove unused import * :fire: Remove unneeded getters * :fire: Remove unused import * :zap: Generalize cred component positioning * :zap: Set up request * :bug: Fix edge case in endpoint * :zap: Display scopes alert box * :rewind: Revert "Generalize cred comp positioning" This reverts commit 75eea89273b854110fa6d1f96c7c1d78dd3b0731. * :zap: Consolidate HTTPRN check * :zap: Fix hue percentage to degree * :fire: Remove unused import * :fire: Remove unused import * :fire: Remove unused class * :fire: Remove unused import * :blue_book: Create type for HTTPRN v2 auth params * :pencil2: Rename check * :fire: Remove unused import * :pencil2: Add i18n to `reportUnsetCredential()` * :zap: Refactor Alex's spacing changes * :zap: Post-merge fixes * :zap: Add docs link * :fire: Exclude Notion OAuth cred * :pencil2: Update copy * :pencil2: Rename param * :art: Reposition notice and simplify styling * :pencil2: Update copy * :pencil2: Update copy * :zap: Hide params during custom action * :zap: Show notice if any cred type supported * :bug: Prevent scopes text overflow * :fire: Remove superfluous check * :pencil2: Break up docstring * :art: Tweak notice styling * :zap: Reorder cred param in Webhook node * :pencil2: Shorten cred name in scopes notice * :test_tube: Update Notice snapshots * :bug: Fix check when `globalRole` is `undefined` * :rewind: Revert 3f2c4a6 * :zap: Apply feedback from Product * :test_tube: Update snapshot * :zap: Adjust regex expansion pattern for singular * :fire: Remove unused import * :fire: Remove logging * :zap: Make `somethingElse` key more unique * :zap: Move something else to constants * :zap: Consolidate notice component * :zap: Apply latest feedback * :test_tube: Update tests * :test_tube: Update snapshot * :pencil2: Fix singular version * :test_tube: Finalize tests * :pencil2: Rename constant * :test_tube: Expand tests * :fire: Remove `truncate` prop * :truck: Move scopes fetching to store * :truck: Move method to component * :zap: Use constant * :zap: Refactor `Notice` component * :test_tube: Update tests * :fire: Remove unused keys * :zap: Inject custom API call option * :fire: Remove unused props * :art: Use `compact` prop * :test_tube: Update snapshots * :truck: Move scopes to store * :truck: Move `nodeCredentialTypes` to parent * :pencil2: Rename cred types per branding * :bug: Clear scopes when none * :zap: Add default * :truck: Move `newHttpRequestNodeCredentialType` to parent * :fire: Remove test data * :zap: Separate lines for readability * :zap: Change reference from node to node name * :pencil2: Rename i18n keys * :zap: Refactor OAuth check * :fire: Remove unused key * :truck: Move `OAuth1/2 API` to i18n * :zap: Refactor `skipCheck` * :zap: Add `stopPropagation` and `preventDefault` * :truck: Move active credential scopes logic to store * :art: Fix spacing for `NodeWebhooks` component * :zap: Implement feedback * :zap: Update HTTPRN default and issue copy * Refactor to use `CredentialsSelect` param (#3304) * :zap: Refactor into cred type param * :zap: Componentize scopes notice * :fire: Remove unused data * :fire: Remove unused `loadOptions` * :zap: Componentize `NodeCredentialType` * :bug: Fix param validation * :fire: Remove dup methods * :zap: Refactor all references to `isHttpRequestNodeV2` * :art: Fix styling * :fire: Remove unused import * :fire: Remove unused properties * :art: Fix spacing for Pipedrive Trigger node * :art: Undo Webhook node styling change * :fire: Remove unused style * :zap: Cover `httpHeaderAuth` edge case * :bug: Fix `this.node` reference * :truck: Rename to `credentialsSelect` * :bug: Fix mistaken renaming * :zap: Set one attribute per line * :zap: Move condition to instantiation site * :truck: Rename prop * :zap: Refactor away `prepareScopesNotice` * :pencil2: Rename i18n keys * :pencil2: Update i18n calls * :pencil2: Add more i18n keys * :fire: Remove unused props * :pencil2: Add explanatory comment * :zap: Adjust check in `hasProxyAuth` * :zap: Refactor `credentialSelected` from prop to event * :zap: Eventify `valueChanged`, `setFocus`, `onBlur` * :zap: Eventify `optionSelected` * :zap: Add `noDataExpression` * :fire: Remove logging * :fire: Remove URL from scopes * :zap: Disregard expressions for display * :art: Use CSS modules * :blue_book: Tigthen interface * :bug: Fix generic auth display * :bug: Fix generic auth validation * :blue_book: Loosen type * :truck: Move event params to end * :zap: Generalize reference * :zap: Refactor generic auth as `credentialsSelect` param * :rewind: Restore check for `httpHeaderAuth ` * :truck: Rename `existing` to `predefined` * Extend metrics for HTTP Request node (#3282) * :zap: Extend metrics * :test_tube: Add tests * :zap: Update param names Co-authored-by: Alex Grozav * :zap: Update check per new branch * :zap: Include generic auth check * :zap: Adjust telemetry (#3359) * :zap: Filter credential types by label Co-authored-by: Jan Oberhauser Co-authored-by: Alex Grozav * :arrow_up: Update package-lock.json file * :bookmark: Release n8n-workflow@0.100.0 * :arrow_up: Set n8n-workflow@0.100.0 on n8n-core * :bookmark: Release n8n-core@0.118.0 * :arrow_up: Set n8n-core@0.118.0 and n8n-workflow@0.100.0 on n8n-node-dev * :bookmark: Release n8n-node-dev@0.57.0 * :arrow_up: Set n8n-core@0.118.0 and n8n-workflow@0.100.0 on n8n-nodes-base * :bookmark: Release n8n-nodes-base@0.176.0 * :bookmark: Release n8n-design-system@0.21.0 * :arrow_up: Set n8n-design-system@0.21.0 and n8n-workflow@0.100.0 on n8n-editor-ui * :bookmark: Release n8n-editor-ui@0.144.0 * :arrow_up: Set n8n-core@0.118.0, n8n-editor-ui@0.144.0, n8n-nodes-base@0.176.0 and n8n-workflow@0.100.0 on n8n * :bookmark: Release n8n@0.178.0 * :bookmark: Update main package.json to 0.178.0 * :books: Update CHANGELOG.md with version 0.178.0 * fix(editor): Fix problem with HTTP Request Node 1 credentials to be set (#3371) * :bookmark: Release n8n-editor-ui@0.144.1 * :arrow_up: Set n8n-editor-ui@0.144.1 on n8n * :bookmark: Release n8n@0.178.1 * :bookmark: Update main package.json to 0.178.1 * :books: Update CHANGELOG.md with version 0.178.1 * fix(editor): Fix parameter loading bug (#3374) * fix parameter loading bug * remove duplicate check * :bookmark: Release n8n-editor-ui@0.144.2 * :arrow_up: Set n8n-editor-ui@0.144.2 on n8n * :bookmark: Release n8n@0.178.2 * :bookmark: Update main package.json to 0.178.2 * :books: Update CHANGELOG.md with version 0.178.2 * :zap: Improvements * fix(core): Fix issue that "closeFunction" got called twice * fix(core): Prevent expressions XSS (#3366) * :sparkles: Added checks for window object access in template strings. * :sparkles: Added self, prompt and confirm to blocklist. Changed window usage condition. * fix(editor): Fix conflicting hover states between sticky button and node view (#3368) * 🐛 Fixing conflicting hover states between sticky button and node view. * 🔨 Updating and optimizing sticky menu hover logic * 📇 Removing redundant comments from `NodeView`. * fix(editor): Fix credential display bug (#3372) * :zap: Filter credentials by version * :zap: Reuse helper * fix(NextCloud Node): Fix folder list with Nextcloud v24 (#3386) * initial fix for v24 folder listing * implemented new credential methods * Nodelinter fixes * feat(PostBin Node): Add PostBin node (#3236) * 🚧 Initial progress on PostBin node. * ✨ Implemented Bin and Request operations for PostBin node. * 🚧 Reworked the node in the declarative way. * 🚧 PosBin node refactoring after reworking it. * ✨ Implemented Bin id parsing in PostBin node. Done some final refactoring and documentation. * :zap: Improvements * :zap: Add comments * 👌Updating the PostBin node based on the product review * 💄Updating PostBin node Bin ID validation logic * :zap: Small improvements * :zap: Transform the bin requests and add additional properties Co-authored-by: ricardo Co-authored-by: Jan Oberhauser * :zap: Simplify auth * :shirt: Fix lint issue Co-authored-by: Michael Kret Co-authored-by: Michael Kret <88898367+michael-radency@users.noreply.github.com> Co-authored-by: Jan Oberhauser Co-authored-by: Iván Ovejero Co-authored-by: Jan Oberhauser Co-authored-by: Alex Grozav Co-authored-by: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> Co-authored-by: ricardo Co-authored-by: Milorad FIlipović Co-authored-by: Jonathan Bennetts --- .../credentials/TodoistApi.credentials.ts | 16 ++ .../nodes/NextCloud/GenericFunctions.ts | 2 +- .../nodes/Todoist/GenericFunctions.ts | 15 +- .../nodes-base/nodes/Todoist/Todoist.node.ts | 138 +++++++++++++++--- 4 files changed, 135 insertions(+), 36 deletions(-) diff --git a/packages/nodes-base/credentials/TodoistApi.credentials.ts b/packages/nodes-base/credentials/TodoistApi.credentials.ts index bbcf09f2ff..45b98d6a59 100644 --- a/packages/nodes-base/credentials/TodoistApi.credentials.ts +++ b/packages/nodes-base/credentials/TodoistApi.credentials.ts @@ -1,4 +1,6 @@ import { + IAuthenticateBearer, + ICredentialTestRequest, ICredentialType, INodeProperties, } from 'n8n-workflow'; @@ -16,4 +18,18 @@ export class TodoistApi implements ICredentialType { default: '', }, ]; + + authenticate = { + type: 'bearer', + properties: { + tokenPropertyName: 'apiKey', + }, + } as IAuthenticateBearer; + + test: ICredentialTestRequest = { + request: { + baseURL: 'https://api.todoist.com/rest/v1', + url: '/labels', + }, + }; } diff --git a/packages/nodes-base/nodes/NextCloud/GenericFunctions.ts b/packages/nodes-base/nodes/NextCloud/GenericFunctions.ts index ff8226e375..443f67cb16 100644 --- a/packages/nodes-base/nodes/NextCloud/GenericFunctions.ts +++ b/packages/nodes-base/nodes/NextCloud/GenericFunctions.ts @@ -34,7 +34,7 @@ export async function nextCloudApiRequest(this: IHookFunctions | IExecuteFunctio credentials = await this.getCredentials('nextCloudOAuth2Api') as { webDavUrl: string }; } - let options: OptionsWithUri = { + const options: OptionsWithUri = { headers, method, body, diff --git a/packages/nodes-base/nodes/Todoist/GenericFunctions.ts b/packages/nodes-base/nodes/Todoist/GenericFunctions.ts index aba3afa25d..37644ca2ce 100644 --- a/packages/nodes-base/nodes/Todoist/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Todoist/GenericFunctions.ts @@ -28,12 +28,11 @@ export async function todoistApiRequest( body: any = {}, // tslint:disable-line:no-any qs: IDataObject = {}, ): Promise { // tslint:disable-line:no-any - const authentication = this.getNodeParameter('authentication', 0, 'apiKey'); + const authentication = this.getNodeParameter('authentication', 0) as string; const endpoint = 'api.todoist.com/rest/v1'; const options: OptionsWithUri = { - headers: {}, method, qs, uri: `https://${endpoint}${resource}`, @@ -45,16 +44,8 @@ export async function todoistApiRequest( } try { - if (authentication === 'apiKey') { - const credentials = await this.getCredentials('todoistApi'); - - //@ts-ignore - options.headers['Authorization'] = `Bearer ${credentials.apiKey}`; - return this.helpers.request!(options); - } else { - //@ts-ignore - return await this.helpers.requestOAuth2.call(this, 'todoistOAuth2Api', options); - } + const credentialType = authentication === 'apiKey' ? 'todoistApi' : 'todoistOAuth2Api'; + return await this.helpers.requestWithAuthentication.call(this, credentialType, options); } catch (error) { throw new NodeApiError(this.getNode(), error); diff --git a/packages/nodes-base/nodes/Todoist/Todoist.node.ts b/packages/nodes-base/nodes/Todoist/Todoist.node.ts index d7a5fcd55a..3f167d3bf9 100644 --- a/packages/nodes-base/nodes/Todoist/Todoist.node.ts +++ b/packages/nodes-base/nodes/Todoist/Todoist.node.ts @@ -21,7 +21,7 @@ interface IBodyCreateTask { description?: string; project_id?: number; section_id?: number; - parent?: number; + parent_id?: number; order?: number; label_ids?: number[]; priority?: number; @@ -116,16 +116,16 @@ export class Todoist implements INodeType { }, }, options: [ - { - name: 'Create', - value: 'create', - description: 'Create a new task', - }, { name: 'Close', value: 'close', description: 'Close a task', }, + { + name: 'Create', + value: 'create', + description: 'Create a new task', + }, { name: 'Delete', value: 'delete', @@ -192,6 +192,7 @@ export class Todoist implements INodeType { }, }, default: [], + description: 'Optional labels that will be assigned to a created task', }, { displayName: 'Content', @@ -266,6 +267,13 @@ export class Todoist implements INodeType { default: '', description: 'Specific date and time in RFC3339 format in UTC', }, + { + displayName: 'Due String Locale', + name: 'dueLang', + type: 'string', + default: '', + description: '2-letter code specifying language in case due_string is not written in English', + }, { displayName: 'Due String', name: 'dueString', @@ -274,11 +282,18 @@ export class Todoist implements INodeType { description: 'Human defined task due date (ex.: “next Monday”, “Tomorrow”). Value is set using local (not UTC) time.', }, { - displayName: 'Due String Locale', - name: 'dueLang', - type: 'string', - default: '', - description: '2-letter code specifying language in case due_string is not written in English', + displayName: 'Parent ID', + name: 'parentId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getItems', + loadOptionsDependsOn: [ + 'project', + 'options.section', + ], + }, + default: {}, + description: 'The parent task you want to operate on', }, { displayName: 'Priority', @@ -292,7 +307,7 @@ export class Todoist implements INodeType { description: 'Task priority from 1 (normal) to 4 (urgent)', }, { - displayName: 'Section', + displayName: 'Section ID', name: 'section', type: 'options', typeOptions: { @@ -344,7 +359,7 @@ export class Todoist implements INodeType { minValue: 1, maxValue: 500, }, - default: 100, + default: 50, description: 'Max number of results to return', }, { @@ -395,6 +410,20 @@ export class Todoist implements INodeType { default: '', description: 'IETF language tag defining what language filter is written in, if differs from default English', }, + { + displayName: 'Parent ID', + name: 'parentId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getItems', + loadOptionsDependsOn: [ + 'filters.projectId', + 'filters.sectionId', + ], + }, + default: '', + description: 'Filter tasks by parent task ID', + }, { displayName: 'Project ID', name: 'projectId', @@ -405,6 +434,19 @@ export class Todoist implements INodeType { default: '', description: 'Filter tasks by project ID', }, + { + displayName: 'Section ID', + name: 'sectionId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getSections', + loadOptionsDependsOn: [ + 'filters.projectId', + ], + }, + default: '', + description: 'Filter tasks by section ID', + }, ], }, { @@ -445,13 +487,6 @@ export class Todoist implements INodeType { default: '', description: 'Specific date and time in RFC3339 format in UTC', }, - { - displayName: 'Due String', - name: 'dueString', - type: 'string', - default: '', - description: 'Human defined task due date (ex.: “next Monday”, “Tomorrow”). Value is set using local (not UTC) time.', - }, { displayName: 'Due String Locale', name: 'dueLang', @@ -459,6 +494,13 @@ export class Todoist implements INodeType { default: '', description: '2-letter code specifying language in case due_string is not written in English', }, + { + displayName: 'Due String', + name: 'dueString', + type: 'string', + default: '', + description: 'Human defined task due date (ex.: “next Monday”, “Tomorrow”). Value is set using local (not UTC) time.', + }, { displayName: 'Labels', name: 'labels', @@ -509,7 +551,13 @@ export class Todoist implements INodeType { async getSections(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; - const projectId = this.getCurrentNodeParameter('project') as number; + const options = Object.assign({}, + this.getNodeParameter('options', {}), + this.getNodeParameter('filters', {}), + ) as IDataObject; + + const projectId = options.projectId as number ?? + this.getCurrentNodeParameter('project') as number; if (projectId) { const qs: IDataObject = {project_id: projectId}; const sections = await todoistApiRequest.call(this, 'GET', '/sections', {}, qs); @@ -527,6 +575,41 @@ export class Todoist implements INodeType { return returnData; }, + // Get all the available parents in the selected project and section, + // to display them to user so that they can select one easily + async getItems(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + + const options = Object.assign({}, + this.getNodeParameter('options', {}), + this.getNodeParameter('filters', {}), + ) as IDataObject; + + const projectId = options.projectId as number ?? + this.getCurrentNodeParameter('project') as number; + + const sectionId = options.sectionId as number || options.section as number || + this.getCurrentNodeParameter('sectionId') as number; + + if (projectId) { + const qs: IDataObject = sectionId ? + {project_id: projectId, section_id: sectionId} : {project_id: projectId}; + + const items = await todoistApiRequest.call(this, 'GET', '/tasks', {}, qs); + for (const item of items) { + const itemContent = item.content; + const itemId = item.id; + + returnData.push({ + name: itemContent, + value: itemId, + }); + } + } + + return returnData; + }, + // Get all the available labels to display them to user so that he can // select them easily async getLabels(this: ILoadOptionsFunctions): Promise { @@ -592,13 +675,16 @@ export class Todoist implements INodeType { } if (labels !== undefined && labels.length !== 0) { - body.label_ids = labels; + body.label_ids = labels as number[]; } if (options.section) { body.section_id = options.section as number; } + if (options.parentId) { + body.parent_id = options.parentId as number; + } responseData = await todoistApiRequest.call(this, 'POST', '/tasks', body); } if (operation === 'close') { @@ -628,10 +714,16 @@ export class Todoist implements INodeType { if (operation === 'getAll') { //https://developer.todoist.com/rest/v1/#get-active-tasks const returnAll = this.getNodeParameter('returnAll', i) as boolean; - const filters = this.getNodeParameter('filters', i) as IDataObject; + const filters = this.getNodeParameter('filters', i, {}) as IDataObject; if (filters.projectId) { qs.project_id = filters.projectId as string; } + if (filters.sectionId) { + qs.section_id = filters.sectionId as string; + } + if (filters.parentId) { + qs.parent_id = filters.parentId as string; + } if (filters.labelId) { qs.label_id = filters.labelId as string; }