From 016aeaaa791205c5ee3d16eef25f856603cf0085 Mon Sep 17 00:00:00 2001 From: pemontto <939704+pemontto@users.noreply.github.com> Date: Sun, 27 Mar 2022 10:49:47 +0100 Subject: [PATCH] feat(Jira Node): Add Simplify Output option to Issue > Get (#2408) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ Add option to use Jira field display names * 🚸 Make mapped fields more deterministic * ♻️ Refactor Jira user loadOptions * Moved and renamed the option as well as only returning the fields to * Tweaked Friendly Fields to make it "Simplify Output" following similar patterns to other nodes * :zap: Improvements Co-authored-by: Jonathan Bennetts Co-authored-by: ricardo --- .../nodes-base/nodes/Jira/GenericFunctions.ts | 46 +++++++++++- .../nodes-base/nodes/Jira/IssueDescription.ts | 18 +++++ packages/nodes-base/nodes/Jira/Jira.node.ts | 71 +++++++++---------- 3 files changed, 96 insertions(+), 39 deletions(-) diff --git a/packages/nodes-base/nodes/Jira/GenericFunctions.ts b/packages/nodes-base/nodes/Jira/GenericFunctions.ts index 4b87f32ed233a..277b566ecda07 100644 --- a/packages/nodes-base/nodes/Jira/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Jira/GenericFunctions.ts @@ -12,6 +12,7 @@ import { import { ICredentialDataDecryptedObject, IDataObject, + JsonObject, NodeApiError, NodeOperationError, } from 'n8n-workflow'; @@ -69,7 +70,7 @@ export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecut try { return await this.helpers.request!(options); } catch (error) { - throw new NodeApiError(this.getNode(), error); + throw new NodeApiError(this.getNode(), error as JsonObject); } } @@ -119,6 +120,49 @@ export function getId(url: string) { return url.split('/').pop(); } +export function simplifyIssueOutput(responseData: { + names: { [key: string]: string }, + fields: IDataObject, + id: string, + key: string, + self: string +}) { + const mappedFields: IDataObject = { + id: responseData.id, + key: responseData.key, + self: responseData.self, + }; + // Sort custom fields last so we map them last + const customField = /^customfield_\d+$/; + const sortedFields: string[] = Object.keys(responseData.fields).sort((a, b) => { + if (customField.test(a) && customField.test(b)) { + return a > b ? 1 : -1; + } + if (customField.test(a)) { + return 1; + } + if (customField.test(b)) { + return -1; + } + return a > b ? 1 : -1; + }); + for (const field of sortedFields) { + if (responseData.names[field] in mappedFields) { + let newField: string = responseData.names[field]; + let counter = 0; + while (newField in mappedFields) { + counter++; + newField = `${responseData.names[field]}_${counter}`; + } + mappedFields[newField] = responseData.fields[field]; + } else { + mappedFields[responseData.names[field] || field] = responseData.fields[field]; + } + } + + return mappedFields; +} + export const allEvents = [ 'board_created', 'board_updated', diff --git a/packages/nodes-base/nodes/Jira/IssueDescription.ts b/packages/nodes-base/nodes/Jira/IssueDescription.ts index ceb925c2d5183..5eace303a86e7 100644 --- a/packages/nodes-base/nodes/Jira/IssueDescription.ts +++ b/packages/nodes-base/nodes/Jira/IssueDescription.ts @@ -530,6 +530,24 @@ export const issueFields: INodeProperties[] = [ default: '', description: 'Issue Key', }, + { + displayName: 'Simplify Output', + name: 'simplifyOutput', + type: 'boolean', + required: false, + displayOptions: { + show: { + resource: [ + 'issue', + ], + operation: [ + 'get', + ], + }, + }, + default: false, + description: `Return a simplified output of the issues fields.`, + }, { displayName: 'Additional Fields', name: 'additionalFields', diff --git a/packages/nodes-base/nodes/Jira/Jira.node.ts b/packages/nodes-base/nodes/Jira/Jira.node.ts index 98a51b1044c61..4eb318aa8b15e 100644 --- a/packages/nodes-base/nodes/Jira/Jira.node.ts +++ b/packages/nodes-base/nodes/Jira/Jira.node.ts @@ -18,12 +18,14 @@ import { INodePropertyOptions, INodeType, INodeTypeDescription, + JsonObject, NodeOperationError, } from 'n8n-workflow'; import { jiraSoftwareCloudApiRequest, jiraSoftwareCloudApiRequestAllItems, + simplifyIssueOutput, validateJSON, } from './GenericFunctions'; @@ -178,7 +180,7 @@ export class Jira implements INodeType { } catch (err) { return { status: 'Error', - message: `Connection details not valid: ${err.message}`, + message: `Connection details not valid: ${(err as JsonObject).message}`, }; } return { @@ -303,45 +305,29 @@ export class Jira implements INodeType { // Get all the users to display them to user so that he can // select them easily async getUsers(this: ILoadOptionsFunctions): Promise { - const returnData: INodePropertyOptions[] = []; const jiraVersion = this.getCurrentNodeParameter('jiraVersion') as string; - if (jiraVersion === 'server') { - // the interface call must bring username - const users = await jiraSoftwareCloudApiRequest.call(this, '/api/2/user/search', 'GET', {}, - { - username: '\'', - }, - ); - for (const user of users) { - const userName = user.displayName; - const userId = user.name; + const query: IDataObject = {}; + let endpoint = '/api/2/users/search'; - returnData.push({ - name: userName, - value: userId, - }); - } - } else { - const users = await jiraSoftwareCloudApiRequest.call(this, '/api/2/users/search', 'GET'); + if (jiraVersion === 'server') { + endpoint = '/api/2/user/search'; + query.username = '\''; + } - for (const user of users) { - const userName = user.displayName; - const userId = user.accountId; + const users = await jiraSoftwareCloudApiRequest.call(this, endpoint, 'GET', {}, query); - returnData.push({ - name: userName, - value: userId, + return users.reduce((activeUsers: INodePropertyOptions[], user: IDataObject) => { + if (user.active) { + activeUsers.push({ + name: user.displayName as string, + value: (user.accountId || user.name) as string, }); } - } - - returnData.sort((a, b) => { - if (a.name < b.name) { return -1; } - if (a.name > b.name) { return 1; } - return 0; + return activeUsers; + }, []).sort((a: INodePropertyOptions, b: INodePropertyOptions) => { + return a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1; }); - return returnData; }, // Get all the groups to display them to user so that he can @@ -418,10 +404,10 @@ export class Jira implements INodeType { 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, - }); + returnData.push({ + name: field.name, + value: field.key || field.fieldId, + }); } } return returnData; @@ -642,6 +628,7 @@ export class Jira implements INodeType { if (operation === 'get') { for (let i = 0; i < length; i++) { const issueKey = this.getNodeParameter('issueKey', i) as string; + const simplifyOutput = this.getNodeParameter('simplifyOutput', i) as boolean; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; if (additionalFields.fields) { qs.fields = additionalFields.fields as string; @@ -652,6 +639,9 @@ export class Jira implements INodeType { if (additionalFields.expand) { qs.expand = additionalFields.expand as string; } + if (simplifyOutput) { + qs.expand = `${qs.expand || ''},names`; + } if (additionalFields.properties) { qs.properties = additionalFields.properties as string; } @@ -659,7 +649,12 @@ export class Jira implements INodeType { qs.updateHistory = additionalFields.updateHistory as string; } responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}`, 'GET', {}, qs); - returnData.push(responseData); + + if (simplifyOutput) { + returnData.push(simplifyIssueOutput(responseData)); + } else { + returnData.push(responseData); + } } } //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-search-post @@ -689,7 +684,7 @@ export class Jira implements INodeType { responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/search`, 'POST', body); responseData = responseData.issues; } - returnData.push.apply(returnData, responseData); + returnData.push(...responseData); } } //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-changelog-get