From 237b1d8614ffd19215eaee6a0cb9422a16cf0a5c Mon Sep 17 00:00:00 2001 From: Marcus <56945030+maspio@users.noreply.github.com> Date: Tue, 24 Jan 2023 17:42:38 +0100 Subject: [PATCH] feat(Jira Software Node): Use resource locator component (#5090) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ⚡️Issue -> Create -> parameter Project RLC * 🔥removed unused loadOptions getProjects * ⚡️Issue -> Create -> parameter Issue Type RLC * 🔥removed unused loadOptions getIssueTypes * ⚡️Issue -> Create/Update -> parameter Assignee RLC * ⚡️Issue -> Create/Update -> parameter Reporter RLC * ⚡️Issue -> Create/Update -> parameter Priority RLC * 🔥removed unused loadOptions getPriorities * ⚡️Issue -> Update -> parameter Status RLC * 🔥removed unused loadOptions getTransitions * 🎨 fix typos * ⚡️Issue -> Create/Update -> Custom Fields parameter Field RLC * 🔥removed unused loadOptions getCustomFields * 🥅 throw custom error for "Field priority cannot be set" * 🚨 fix linter error * :zap: removed ts-ignore * :zap: removed ts-ignore Co-authored-by: Michael Kret --- .../nodes-base/nodes/Jira/GenericFunctions.ts | 39 +- .../nodes-base/nodes/Jira/IssueDescription.ts | 464 ++++++++++++++---- packages/nodes-base/nodes/Jira/Jira.node.ts | 425 +++++++++------- .../nodes-base/nodes/Jira/JiraTrigger.node.ts | 1 - 4 files changed, 654 insertions(+), 275 deletions(-) diff --git a/packages/nodes-base/nodes/Jira/GenericFunctions.ts b/packages/nodes-base/nodes/Jira/GenericFunctions.ts index c869e61f6d..e0c7736a6e 100644 --- a/packages/nodes-base/nodes/Jira/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Jira/GenericFunctions.ts @@ -7,13 +7,12 @@ import { ILoadOptionsFunctions, } from 'n8n-core'; -import { IDataObject } from 'n8n-workflow'; +import { IDataObject, INodeListSearchItems, NodeApiError } from 'n8n-workflow'; export async function jiraSoftwareCloudApiRequest( this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, endpoint: string, method: string, - body: any = {}, query?: IDataObject, uri?: string, @@ -56,8 +55,20 @@ export async function jiraSoftwareCloudApiRequest( if (Object.keys(query || {}).length === 0) { delete options.qs; } - - return this.helpers.requestWithAuthentication.call(this, credentialType, options); + try { + return await this.helpers.requestWithAuthentication.call(this, credentialType, options); + } catch (error) { + if ( + error.description?.includes && + error.description.includes("Field 'priority' cannot be set") + ) { + throw new NodeApiError(this.getNode(), error, { + message: + "Field 'priority' cannot be set. You need to add the Priority field to your Jira Project's Issue Types.", + }); + } + throw error; + } } export async function jiraSoftwareCloudApiRequestAllItems( @@ -65,7 +76,6 @@ export async function jiraSoftwareCloudApiRequestAllItems( propertyName: string, endpoint: string, method: string, - body: any = {}, query: IDataObject = {}, ): Promise { @@ -198,3 +208,22 @@ export const allEvents = [ 'worklog_updated', 'worklog_deleted', ]; + +export function filterSortSearchListItems(items: INodeListSearchItems[], filter?: string) { + return items + .filter( + (item) => + !filter || + item.name.toLowerCase().includes(filter.toLowerCase()) || + item.value.toString().toLowerCase().includes(filter.toLowerCase()), + ) + .sort((a, b) => { + if (a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase()) { + return -1; + } + if (a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase()) { + return 1; + } + return 0; + }); +} diff --git a/packages/nodes-base/nodes/Jira/IssueDescription.ts b/packages/nodes-base/nodes/Jira/IssueDescription.ts index 081dc00d04..1da4ab8543 100644 --- a/packages/nodes-base/nodes/Jira/IssueDescription.ts +++ b/packages/nodes-base/nodes/Jira/IssueDescription.ts @@ -71,42 +71,93 @@ export const issueFields: INodeProperties[] = [ /* issue:create */ /* -------------------------------------------------------------------------- */ { - displayName: 'Project Name or ID', + displayName: 'Project', name: 'project', - type: 'options', - description: - 'Choose from the list, or specify an ID using an expression', - default: '', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, required: true, + modes: [ + { + displayName: 'Project', + name: 'list', + type: 'list', + placeholder: 'Select a Project...', + typeOptions: { + searchListMethod: 'getProjects', + // missing searchListDependsOn: ['jiraVersion'], + searchable: true, + }, + }, + { + displayName: 'ID', + name: 'id', + type: 'string', + placeholder: '10000', + validation: [ + { + type: 'regex', + properties: { + regex: '([0-9]{2,})[ \t]*', + errorMessage: 'Not a valid Jira Project ID', + }, + }, + ], + extractValue: { + type: 'regex', + regex: '^([0-9]{2,})', + }, + }, + ], displayOptions: { show: { resource: ['issue'], operation: ['create'], }, }, - typeOptions: { - loadOptionsMethod: 'getProjects', - loadOptionsDependsOn: ['jiraVersion'], - }, }, { - displayName: 'Issue Type Name or ID', + displayName: 'Issue Type', name: 'issueType', - type: 'options', - default: '', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, required: true, + modes: [ + { + displayName: 'Issue Type', + name: 'list', + type: 'list', + placeholder: 'Select an Issue Type...', + typeOptions: { + searchListMethod: 'getIssueTypes', + // missing searchListDependsOn: ['project'], + }, + }, + { + displayName: 'ID', + name: 'id', + type: 'string', + placeholder: '10000', + validation: [ + { + type: 'regex', + properties: { + regex: '([0-9]{2,})[ \t]*', + errorMessage: 'Not a valid Jira Issue Type ID', + }, + }, + ], + extractValue: { + type: 'regex', + regex: '^([0-9]{2,})', + }, + }, + ], displayOptions: { show: { resource: ['issue'], operation: ['create'], }, }, - typeOptions: { - loadOptionsMethod: 'getIssueTypes', - loadOptionsDependsOn: ['project'], - }, - description: - 'Issue Types. Choose from the list, or specify an ID using an expression.', }, { displayName: 'Summary', @@ -135,15 +186,41 @@ export const issueFields: INodeProperties[] = [ }, options: [ { - displayName: 'Assignee Name or ID', + displayName: 'Assignee', name: 'assignee', - type: 'options', - description: - 'Choose from the list, or specify an ID using an expression', - typeOptions: { - loadOptionsMethod: 'getUsers', - }, - default: '', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + modes: [ + { + displayName: 'Assignee', + name: 'list', + type: 'list', + placeholder: 'Select an Assignee...', + typeOptions: { + searchListMethod: 'getUsers', + searchable: true, + }, + }, + { + displayName: 'ID', + name: 'id', + type: 'string', + placeholder: '62971ebae540870069668714', + validation: [ + { + type: 'regex', + properties: { + regex: '([-:a-z0-9]{2,})[ \t]*', + errorMessage: 'Not a valid Jira Assignee ID', + }, + }, + ], + extractValue: { + type: 'regex', + regex: '^([-:a-z0-9]{2,})', + }, + }, + ], }, { displayName: 'Description', @@ -178,16 +255,41 @@ export const issueFields: INodeProperties[] = [ displayName: 'Custom Field', values: [ { - displayName: 'Field Name or ID', + displayName: 'Field', name: 'fieldId', - type: 'options', - typeOptions: { - loadOptionsMethod: 'getCustomFields', - loadOptionsDependsOn: ['project'], - }, - description: - 'ID of the field to set. Choose from the list, or specify an ID using an expression.', - default: '', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + modes: [ + { + displayName: 'Field', + name: 'list', + type: 'list', + placeholder: 'Select a Field...', + typeOptions: { + searchListMethod: 'getCustomFields', + // missing searchListDependsOn: ['project'], + }, + }, + { + displayName: 'ID', + name: 'id', + type: 'string', + placeholder: 'customfield_10035', + validation: [ + { + type: 'regex', + properties: { + regex: '(customfield_[0-9]{2,})[ \t]*', + errorMessage: 'Not a valid Jira Field ID', + }, + }, + ], + extractValue: { + type: 'regex', + regex: '^(customfield_[0-9]{2,})', + }, + }, + ], }, { displayName: 'Field Value', @@ -237,26 +339,77 @@ export const issueFields: INodeProperties[] = [ default: '', }, { - displayName: 'Priority Name or ID', + displayName: 'Priority', name: 'priority', - type: 'options', - description: - 'Choose from the list, or specify an ID using an expression', - typeOptions: { - loadOptionsMethod: 'getPriorities', - }, - default: '', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + modes: [ + { + displayName: 'Priority', + name: 'list', + type: 'list', + placeholder: 'Select a Priority...', + typeOptions: { + searchListMethod: 'getPriorities', + }, + }, + { + displayName: 'ID', + name: 'id', + type: 'string', + placeholder: '1', + validation: [ + { + type: 'regex', + properties: { + regex: '([0-9]{1,})[ \t]*', + errorMessage: 'Not a valid Jira Priority ID', + }, + }, + ], + extractValue: { + type: 'regex', + regex: '^([0-9]{1,})', + }, + }, + ], }, { - displayName: 'Reporter Name or ID', + displayName: 'Reporter', name: 'reporter', - type: 'options', - description: - 'Choose from the list, or specify an ID using an expression', - typeOptions: { - loadOptionsMethod: 'getUsers', - }, - default: '', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + modes: [ + { + displayName: 'Reporter', + name: 'list', + type: 'list', + placeholder: 'Select a Reporter...', + typeOptions: { + searchListMethod: 'getUsers', + searchable: true, + }, + }, + { + displayName: 'ID', + name: 'id', + type: 'string', + placeholder: '62971ebae540870069668714', + validation: [ + { + type: 'regex', + properties: { + regex: '([-:a-z0-9]{2,})[ \t]*', + errorMessage: 'Not a valid Jira Reporter ID', + }, + }, + ], + extractValue: { + type: 'regex', + regex: '^([-:a-z0-9]{2,})', + }, + }, + ], }, { displayName: 'Update History', @@ -299,15 +452,41 @@ export const issueFields: INodeProperties[] = [ }, options: [ { - displayName: 'Assignee Name or ID', + displayName: 'Assignee', name: 'assignee', - type: 'options', - description: - 'Choose from the list, or specify an ID using an expression', - typeOptions: { - loadOptionsMethod: 'getUsers', - }, - default: '', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + modes: [ + { + displayName: 'Assignee', + name: 'list', + type: 'list', + placeholder: 'Select an Assignee...', + typeOptions: { + searchListMethod: 'getUsers', + searchable: true, + }, + }, + { + displayName: 'ID', + name: 'id', + type: 'string', + placeholder: '62971ebae540870069668714', + validation: [ + { + type: 'regex', + properties: { + regex: '([-:a-z0-9]{2,})[ \t]*', + errorMessage: 'Not a valid Jira Assignee ID', + }, + }, + ], + extractValue: { + type: 'regex', + regex: '^([-:a-z0-9]{2,})', + }, + }, + ], }, { displayName: 'Description', @@ -330,16 +509,41 @@ export const issueFields: INodeProperties[] = [ displayName: 'Custom Field', values: [ { - displayName: 'Field Name or ID', + displayName: 'Field', name: 'fieldId', - type: 'options', - typeOptions: { - loadOptionsMethod: 'getCustomFields', - loadOptionsDependsOn: ['issueKey'], - }, - description: - 'ID of the field to set. Choose from the list, or specify an ID using an expression.', - default: '', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + modes: [ + { + displayName: 'Field', + name: 'list', + type: 'list', + placeholder: 'Select a Field...', + typeOptions: { + searchListMethod: 'getCustomFields', + // missing searchListDependsOn: ['issueKey'], + }, + }, + { + displayName: 'ID', + name: 'id', + type: 'string', + placeholder: 'customfield_10035', + validation: [ + { + type: 'regex', + properties: { + regex: '(customfield_[0-9]{2,})[ \t]*', + errorMessage: 'Not a valid Jira Field ID', + }, + }, + ], + extractValue: { + type: 'regex', + regex: '^(customfield_[0-9]{2,})', + }, + }, + ], }, { displayName: 'Field Value', @@ -396,26 +600,77 @@ export const issueFields: INodeProperties[] = [ default: '', }, { - displayName: 'Priority Name or ID', + displayName: 'Priority', name: 'priority', - type: 'options', - description: - 'Choose from the list, or specify an ID using an expression', - typeOptions: { - loadOptionsMethod: 'getPriorities', - }, - default: '', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + modes: [ + { + displayName: 'Priority', + name: 'list', + type: 'list', + placeholder: 'Select a Priority...', + typeOptions: { + searchListMethod: 'getPriorities', + }, + }, + { + displayName: 'ID', + name: 'id', + type: 'string', + placeholder: '1', + validation: [ + { + type: 'regex', + properties: { + regex: '([0-9]{1,})[ \t]*', + errorMessage: 'Not a valid Jira Priority ID', + }, + }, + ], + extractValue: { + type: 'regex', + regex: '^([0-9]{1,})', + }, + }, + ], }, { - displayName: 'Reporter Name or ID', + displayName: 'Reporter', name: 'reporter', - type: 'options', - description: - 'Choose from the list, or specify an ID using an expression', - typeOptions: { - loadOptionsMethod: 'getUsers', - }, - default: '', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + modes: [ + { + displayName: 'Reporter', + name: 'list', + type: 'list', + placeholder: 'Select a Reporter...', + typeOptions: { + searchListMethod: 'getUsers', + searchable: true, + }, + }, + { + displayName: 'ID', + name: 'id', + type: 'string', + placeholder: '62971ebae540870069668714', + validation: [ + { + type: 'regex', + properties: { + regex: '([-:a-z0-9]{2,})[ \t]*', + errorMessage: 'Not a valid Jira Reporter ID', + }, + }, + ], + extractValue: { + type: 'regex', + regex: '^([-:a-z0-9]{2,})', + }, + }, + ], }, { displayName: 'Summary', @@ -424,15 +679,40 @@ export const issueFields: INodeProperties[] = [ default: '', }, { - displayName: 'Status Name or ID', + displayName: 'Status', name: 'statusId', - type: 'options', - typeOptions: { - loadOptionsMethod: 'getTransitions', - }, - default: '', - description: - 'The ID of the issue status. Choose from the list, or specify an ID using an expression.', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + modes: [ + { + displayName: 'Status', + name: 'list', + type: 'list', + placeholder: 'Select a Status...', + typeOptions: { + searchListMethod: 'getTransitions', + }, + }, + { + displayName: 'ID', + name: 'id', + type: 'string', + placeholder: '11', + validation: [ + { + type: 'regex', + properties: { + regex: '([0-9]{1,})[ \t]*', + errorMessage: 'Not a valid Jira Status ID', + }, + }, + ], + extractValue: { + type: 'regex', + regex: '^([0-9]{1,})', + }, + }, + ], }, ], }, diff --git a/packages/nodes-base/nodes/Jira/Jira.node.ts b/packages/nodes-base/nodes/Jira/Jira.node.ts index ace29863c9..63bd7782e8 100644 --- a/packages/nodes-base/nodes/Jira/Jira.node.ts +++ b/packages/nodes-base/nodes/Jira/Jira.node.ts @@ -7,6 +7,8 @@ import { IDataObject, ILoadOptionsFunctions, INodeExecutionData, + INodeListSearchItems, + INodeListSearchResult, INodePropertyOptions, INodeType, INodeTypeDescription, @@ -14,6 +16,7 @@ import { } from 'n8n-workflow'; import { + filterSortSearchListItems, jiraSoftwareCloudApiRequest, jiraSoftwareCloudApiRequestAllItems, simplifyIssueOutput, @@ -129,11 +132,14 @@ export class Jira implements INodeType { }; methods = { - loadOptions: { + listSearch: { // Get all the projects to display them to user so that he can // select them easily - async getProjects(this: ILoadOptionsFunctions): Promise { - const returnData: INodePropertyOptions[] = []; + async getProjects( + this: ILoadOptionsFunctions, + filter?: string, + ): Promise { + const returnData: INodeListSearchItems[] = []; const jiraVersion = this.getCurrentNodeParameter('jiraVersion') as string; let endpoint = ''; let projects; @@ -163,24 +169,14 @@ export class Jira implements INodeType { }); } - returnData.sort((a, b) => { - if (a.name < b.name) { - return -1; - } - if (a.name > b.name) { - return 1; - } - return 0; - }); - - return returnData; + return { results: filterSortSearchListItems(returnData, filter) }; }, // Get all the issue types to display them to user so that he can // select them easily - async getIssueTypes(this: ILoadOptionsFunctions): Promise { - const projectId = this.getCurrentNodeParameter('project'); - const returnData: INodePropertyOptions[] = []; + async getIssueTypes(this: ILoadOptionsFunctions): Promise { + const projectId = this.getCurrentNodeParameter('project', { extractValue: true }); + const returnData: INodeListSearchItems[] = []; const { issueTypes } = await jiraSoftwareCloudApiRequest.call( this, `/api/2/project/${projectId}`, @@ -204,9 +200,146 @@ export class Jira implements INodeType { } return 0; }); - return returnData; + return { results: returnData }; }, + // Get all the users to display them to user so that he can + // select them easily + async getUsers(this: ILoadOptionsFunctions, filter?: string): Promise { + const jiraVersion = this.getCurrentNodeParameter('jiraVersion') as string; + const query: IDataObject = {}; + let endpoint = '/api/2/users/search'; + + if (jiraVersion === 'server') { + endpoint = '/api/2/user/search'; + query.username = "'"; + } + + const users = await jiraSoftwareCloudApiRequest.call(this, endpoint, 'GET', {}, query); + const returnData: INodeListSearchItems[] = users.reduce( + (activeUsers: INodeListSearchItems[], user: IDataObject) => { + if (user.active) { + activeUsers.push({ + name: user.displayName as string, + value: (user.accountId ?? user.name) as string, + }); + } + return activeUsers; + }, + [], + ); + + return { results: filterSortSearchListItems(returnData, filter) }; + }, + + // Get all the priorities to display them to user so that he can + // select them easily + async getPriorities(this: ILoadOptionsFunctions): Promise { + const returnData: INodeListSearchItems[] = []; + + const priorities = await jiraSoftwareCloudApiRequest.call(this, '/api/2/priority', 'GET'); + + for (const priority of priorities) { + const priorityName = priority.name; + const priorityId = priority.id; + + returnData.push({ + name: priorityName, + value: priorityId, + }); + } + + returnData.sort((a, b) => { + if (a.name < b.name) { + return -1; + } + if (a.name > b.name) { + return 1; + } + return 0; + }); + + return { results: returnData }; + }, + + // Get all the transitions (status) to display them to user so that he can + // select them easily + async getTransitions(this: ILoadOptionsFunctions): Promise { + const returnData: INodeListSearchItems[] = []; + + const issueKey = this.getCurrentNodeParameter('issueKey'); + const transitions = await jiraSoftwareCloudApiRequest.call( + this, + `/api/2/issue/${issueKey}/transitions`, + 'GET', + ); + + for (const transition of transitions.transitions) { + returnData.push({ + name: transition.name, + value: transition.id, + }); + } + + returnData.sort((a, b) => { + if (a.name < b.name) { + return -1; + } + if (a.name > b.name) { + return 1; + } + return 0; + }); + + return { results: returnData }; + }, + + // Get all the custom fields to display them to user so that he can + // select them easily + async getCustomFields(this: ILoadOptionsFunctions): Promise { + const returnData: INodeListSearchItems[] = []; + const operation = this.getCurrentNodeParameter('operation') as string; + let projectId: string; + let issueTypeId: string; + if (operation === 'create') { + projectId = this.getCurrentNodeParameter('project', { extractValue: true }) as string; + issueTypeId = this.getCurrentNodeParameter('issueType', { extractValue: true }) as string; + } else { + const issueKey = this.getCurrentNodeParameter('issueKey') as string; + const res = await jiraSoftwareCloudApiRequest.call( + this, + `/api/2/issue/${issueKey}`, + 'GET', + {}, + {}, + ); + projectId = res.fields.project.id; + issueTypeId = res.fields.issuetype.id; + } + + const res = await jiraSoftwareCloudApiRequest.call( + this, + `/api/2/issue/createmeta?projectIds=${projectId}&issueTypeIds=${issueTypeId}&expand=projects.issuetypes.fields`, + 'GET', + ); + + const fields = res.projects + .find((o: any) => o.id === projectId) + .issuetypes.find((o: any) => o.id === issueTypeId).fields; + + for (const key of Object.keys(fields)) { + const field = fields[key]; + if (field.schema && Object.keys(field.schema).includes('customId')) { + returnData.push({ + name: field.name, + value: field.key || field.fieldId, + }); + } + } + return { results: returnData }; + }, + }, + loadOptions: { // Get all the labels to display them to user so that he can // select them easily async getLabels(this: ILoadOptionsFunctions): Promise { @@ -237,36 +370,6 @@ export class Jira implements INodeType { return returnData; }, - // Get all the priorities to display them to user so that he can - // select them easily - async getPriorities(this: ILoadOptionsFunctions): Promise { - const returnData: INodePropertyOptions[] = []; - - const priorities = await jiraSoftwareCloudApiRequest.call(this, '/api/2/priority', 'GET'); - - for (const priority of priorities) { - const priorityName = priority.name; - const priorityId = priority.id; - - returnData.push({ - name: priorityName, - value: priorityId, - }); - } - - returnData.sort((a, b) => { - if (a.name < b.name) { - return -1; - } - if (a.name > b.name) { - return 1; - } - return 0; - }); - - return returnData; - }, - // Get all the users to display them to user so that he can // select them easily async getUsers(this: ILoadOptionsFunctions): Promise { @@ -326,90 +429,12 @@ export class Jira implements INodeType { return returnData; }, - // Get all the groups to display them to user so that he can - // select them easily - async getTransitions(this: ILoadOptionsFunctions): Promise { - const returnData: INodePropertyOptions[] = []; - - const issueKey = this.getCurrentNodeParameter('issueKey'); - const transitions = await jiraSoftwareCloudApiRequest.call( - this, - `/api/2/issue/${issueKey}/transitions`, - 'GET', - ); - - for (const transition of transitions.transitions) { - returnData.push({ - name: transition.name, - value: transition.id, - }); - } - - returnData.sort((a, b) => { - if (a.name < b.name) { - return -1; - } - if (a.name > b.name) { - return 1; - } - return 0; - }); - - return returnData; - }, - - // Get all the custom fields to display them to user so that he can - // select them easily - async getCustomFields(this: ILoadOptionsFunctions): Promise { - const returnData: INodePropertyOptions[] = []; - const operation = this.getCurrentNodeParameter('operation') as string; - let projectId: string; - let issueTypeId: string; - if (operation === 'create') { - projectId = this.getCurrentNodeParameter('project') as string; - issueTypeId = this.getCurrentNodeParameter('issueType') as string; - } else { - const issueKey = this.getCurrentNodeParameter('issueKey') as string; - const res = await jiraSoftwareCloudApiRequest.call( - this, - `/api/2/issue/${issueKey}`, - 'GET', - {}, - {}, - ); - projectId = res.fields.project.id; - issueTypeId = res.fields.issuetype.id; - } - - const res = await jiraSoftwareCloudApiRequest.call( - this, - `/api/2/issue/createmeta?projectIds=${projectId}&issueTypeIds=${issueTypeId}&expand=projects.issuetypes.fields`, - 'GET', - ); - - const fields = res.projects - - .find((o: any) => o.id === projectId) - - .issuetypes.find((o: any) => o.id === issueTypeId).fields; - for (const key of Object.keys(fields)) { - const field = fields[key]; - if (field.schema && Object.keys(field.schema).includes('customId')) { - returnData.push({ - name: field.name, - value: field.key || field.fieldId, - }); - } - } - return returnData; - }, - // Get all the components to display them to user so that he can // select them easily async getProjectComponents(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; - const project = this.getCurrentNodeParameter('project'); + const project = this.getCurrentNodeParameter('project', { extractValue: true }); const { values: components } = await jiraSoftwareCloudApiRequest.call( this, `/api/2/project/${project}/component`, @@ -454,9 +479,29 @@ export class Jira implements INodeType { if (operation === 'create') { for (let i = 0; i < length; i++) { const summary = this.getNodeParameter('summary', i) as string; - const projectId = this.getNodeParameter('project', i) as string; - const issueTypeId = this.getNodeParameter('issueType', i) as string; + const projectId = this.getNodeParameter('project', i, '', { + extractValue: true, + }) as string; + const issueTypeId = this.getNodeParameter('issueType', i, '', { + extractValue: true, + }) as string; const additionalFields = this.getNodeParameter('additionalFields', i); + + const assignee = this.getNodeParameter('additionalFields.assignee', i, '', { + extractValue: true, + }); + if (assignee) additionalFields.assignee = assignee; + + const reporter = this.getNodeParameter('additionalFields.reporter', i, '', { + extractValue: true, + }); + if (reporter) additionalFields.reporter = reporter; + + const priority = this.getNodeParameter('additionalFields.priority', i, '', { + extractValue: true, + }); + if (priority) additionalFields.priority = priority; + const body: IIssue = {}; const fields: IFields = { summary, @@ -513,6 +558,12 @@ export class Jira implements INodeType { const customFields = (additionalFields.customFieldsUi as IDataObject) .customFieldsValues as IDataObject[]; if (customFields) { + // resolve resource locator fieldId value + customFields.forEach((cf) => { + if (typeof cf.fieldId !== 'string') { + cf.fieldId = ((cf.fieldId as IDataObject).value as string).trim(); + } + }); const data = customFields.reduce( (obj, value) => Object.assign(obj, { [`${value.fieldId}`]: value.fieldValue }), {}, @@ -560,6 +611,27 @@ export class Jira implements INodeType { for (let i = 0; i < length; i++) { const issueKey = this.getNodeParameter('issueKey', i) as string; const updateFields = this.getNodeParameter('updateFields', i); + + const assignee = this.getNodeParameter('updateFields.assignee', i, '', { + extractValue: true, + }); + if (assignee) updateFields.assignee = assignee; + + const reporter = this.getNodeParameter('updateFields.reporter', i, '', { + extractValue: true, + }); + if (reporter) updateFields.reporter = reporter; + + const priority = this.getNodeParameter('updateFields.priority', i, '', { + extractValue: true, + }); + if (priority) updateFields.priority = priority; + + const statusId = this.getNodeParameter('updateFields.statusId', i, '', { + extractValue: true, + }); + if (statusId) updateFields.statusId = statusId; + const body: IIssue = {}; const fields: IFields = {}; if (updateFields.summary) { @@ -610,6 +682,12 @@ export class Jira implements INodeType { const customFields = (updateFields.customFieldsUi as IDataObject) .customFieldsValues as IDataObject[]; if (customFields) { + // resolve resource locator fieldId value + customFields.forEach((cf) => { + if (typeof cf.fieldId !== 'string') { + cf.fieldId = ((cf.fieldId as IDataObject).value as string).trim(); + } + }); const data = customFields.reduce( (obj, value) => Object.assign(obj, { [`${value.fieldId}`]: value.fieldValue }), {}, @@ -825,42 +903,39 @@ export class Jira implements INodeType { if (!jsonActive) { const notificationRecipientsValues = ( this.getNodeParameter('notificationRecipientsUi', i) as IDataObject - ).notificationRecipientsValues as IDataObject[]; + ).notificationRecipientsValues as IDataObject; const notificationRecipients: INotificationRecipients = {}; if (notificationRecipientsValues) { - // @ts-ignore if (notificationRecipientsValues.reporter) { - // @ts-ignore notificationRecipients.reporter = notificationRecipientsValues.reporter as boolean; } - // @ts-ignore + if (notificationRecipientsValues.assignee) { - // @ts-ignore notificationRecipients.assignee = notificationRecipientsValues.assignee as boolean; } - // @ts-ignore + if (notificationRecipientsValues.assignee) { - // @ts-ignore notificationRecipients.watchers = notificationRecipientsValues.watchers as boolean; } - // @ts-ignore + if (notificationRecipientsValues.voters) { - // @ts-ignore notificationRecipients.watchers = notificationRecipientsValues.voters as boolean; } - // @ts-ignore - if (notificationRecipientsValues.users.length > 0) { - // @ts-ignore - notificationRecipients.users = notificationRecipientsValues.users.map((user) => { + + if (((notificationRecipientsValues.users as IDataObject[]) || []).length > 0) { + notificationRecipients.users = ( + notificationRecipientsValues.users as IDataObject[] + ).map((user) => { return { accountId: user, }; }); } - // @ts-ignore - if (notificationRecipientsValues.groups.length > 0) { - // @ts-ignore - notificationRecipients.groups = notificationRecipientsValues.groups.map((group) => { + + if (((notificationRecipientsValues.groups as IDataObject[]) || []).length > 0) { + notificationRecipients.groups = ( + notificationRecipientsValues.groups as IDataObject[] + ).map((group) => { return { name: group, }; @@ -870,18 +945,20 @@ export class Jira implements INodeType { body.to = notificationRecipients; const notificationRecipientsRestrictionsValues = ( this.getNodeParameter('notificationRecipientsRestrictionsUi', i) as IDataObject - ).notificationRecipientsRestrictionsValues as IDataObject[]; + ).notificationRecipientsRestrictionsValues as IDataObject; const notificationRecipientsRestrictions: NotificationRecipientsRestrictions = {}; if (notificationRecipientsRestrictionsValues) { - // @ts-ignore - if (notificationRecipientsRestrictionsValues.groups.length > 0) { - notificationRecipientsRestrictions.groups = - // @ts-ignore - notificationRecipientsRestrictionsValues.groups.map((group) => { - return { - name: group, - }; - }); + if ( + ((notificationRecipientsRestrictionsValues.groups as IDataObject[]) || []).length > + 0 + ) { + notificationRecipientsRestrictions.groups = ( + notificationRecipientsRestrictionsValues.groups as IDataObject[] + ).map((group) => { + return { + name: group, + }; + }); } } body.restrict = notificationRecipientsRestrictions; @@ -1075,18 +1152,16 @@ export class Jira implements INodeType { 'GET', {}, {}, - // @ts-ignore - attachment?.json.content, + attachment?.json.content as string, { json: false, encoding: null }, ); - //@ts-ignore - returnData[index].binary[binaryPropertyName] = await this.helpers.prepareBinaryData( - buffer, - // @ts-ignore - attachment.json.filename, - // @ts-ignore - attachment.json.mimeType, - ); + + (returnData[index].binary as IBinaryKeyData)[binaryPropertyName] = + await this.helpers.prepareBinaryData( + buffer, + attachment.json.filename as string, + attachment.json.mimeType as string, + ); } } } @@ -1122,25 +1197,21 @@ export class Jira implements INodeType { const binaryPropertyName = this.getNodeParameter('binaryProperty', 0); for (const [index, attachment] of returnData.entries()) { returnData[index].binary = {}; - //@ts-ignore const buffer = await jiraSoftwareCloudApiRequest.call( this, '', 'GET', {}, {}, - // @ts-ignore - attachment.json.content, + attachment.json.content as string, { json: false, encoding: null }, ); - //@ts-ignore - returnData[index].binary[binaryPropertyName] = await this.helpers.prepareBinaryData( - buffer, - // @ts-ignore - attachment.json.filename, - // @ts-ignore - attachment.json.mimeType, - ); + (returnData[index].binary as IBinaryKeyData)[binaryPropertyName] = + await this.helpers.prepareBinaryData( + buffer, + attachment.json.filename as string, + attachment.json.mimeType as string, + ); } } } diff --git a/packages/nodes-base/nodes/Jira/JiraTrigger.node.ts b/packages/nodes-base/nodes/Jira/JiraTrigger.node.ts index 6fb94cc3b8..1170126fb0 100644 --- a/packages/nodes-base/nodes/Jira/JiraTrigger.node.ts +++ b/packages/nodes-base/nodes/Jira/JiraTrigger.node.ts @@ -359,7 +359,6 @@ export class JiraTrigger implements INodeType { ], }; - // @ts-ignore (because of request) webhookMethods = { default: { async checkExists(this: IHookFunctions): Promise {