feat(Jira Node): Add Simplify Output option to Issue > Get (#2408)

*  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

*  Improvements

Co-authored-by: Jonathan Bennetts <jonathan.bennetts@gmail.com>
Co-authored-by: ricardo <ricardoespinoza105@gmail.com>
This commit is contained in:
pemontto
2022-03-27 10:49:47 +01:00
committed by GitHub
parent 5ba4c27d8c
commit 016aeaaa79
3 changed files with 98 additions and 41 deletions

View File

@@ -12,6 +12,7 @@ import {
import { import {
ICredentialDataDecryptedObject, ICredentialDataDecryptedObject,
IDataObject, IDataObject,
JsonObject,
NodeApiError, NodeApiError,
NodeOperationError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
@@ -69,7 +70,7 @@ export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecut
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } 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(); 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 = [ export const allEvents = [
'board_created', 'board_created',
'board_updated', 'board_updated',

View File

@@ -530,6 +530,24 @@ export const issueFields: INodeProperties[] = [
default: '', default: '',
description: 'Issue Key', 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', displayName: 'Additional Fields',
name: 'additionalFields', name: 'additionalFields',

View File

@@ -18,12 +18,14 @@ import {
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
JsonObject,
NodeOperationError, NodeOperationError,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
jiraSoftwareCloudApiRequest, jiraSoftwareCloudApiRequest,
jiraSoftwareCloudApiRequestAllItems, jiraSoftwareCloudApiRequestAllItems,
simplifyIssueOutput,
validateJSON, validateJSON,
} from './GenericFunctions'; } from './GenericFunctions';
@@ -178,7 +180,7 @@ export class Jira implements INodeType {
} catch (err) { } catch (err) {
return { return {
status: 'Error', status: 'Error',
message: `Connection details not valid: ${err.message}`, message: `Connection details not valid: ${(err as JsonObject).message}`,
}; };
} }
return { return {
@@ -303,45 +305,29 @@ export class Jira implements INodeType {
// Get all the users to display them to user so that he can // Get all the users to display them to user so that he can
// select them easily // select them easily
async getUsers(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> { async getUsers(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const jiraVersion = this.getCurrentNodeParameter('jiraVersion') as string; const jiraVersion = this.getCurrentNodeParameter('jiraVersion') as string;
const query: IDataObject = {};
let endpoint = '/api/2/users/search';
if (jiraVersion === 'server') { if (jiraVersion === 'server') {
// the interface call must bring username endpoint = '/api/2/user/search';
const users = await jiraSoftwareCloudApiRequest.call(this, '/api/2/user/search', 'GET', {}, query.username = '\'';
{
username: '\'',
},
);
for (const user of users) {
const userName = user.displayName;
const userId = user.name;
returnData.push({
name: userName,
value: userId,
});
}
} else {
const users = await jiraSoftwareCloudApiRequest.call(this, '/api/2/users/search', 'GET');
for (const user of users) {
const userName = user.displayName;
const userId = user.accountId;
returnData.push({
name: userName,
value: userId,
});
}
} }
returnData.sort((a, b) => { const users = await jiraSoftwareCloudApiRequest.call(this, endpoint, 'GET', {}, query);
if (a.name < b.name) { return -1; }
if (a.name > b.name) { return 1; } return users.reduce((activeUsers: INodePropertyOptions[], user: IDataObject) => {
return 0; if (user.active) {
activeUsers.push({
name: user.displayName as string,
value: (user.accountId || user.name) as string,
});
}
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 // 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)) { for (const key of Object.keys(fields)) {
const field = fields[key]; const field = fields[key];
if (field.schema && Object.keys(field.schema).includes('customId')) { if (field.schema && Object.keys(field.schema).includes('customId')) {
returnData.push({ returnData.push({
name: field.name, name: field.name,
value: field.key || field.fieldId, value: field.key || field.fieldId,
}); });
} }
} }
return returnData; return returnData;
@@ -642,6 +628,7 @@ export class Jira implements INodeType {
if (operation === 'get') { if (operation === 'get') {
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
const issueKey = this.getNodeParameter('issueKey', i) as string; const issueKey = this.getNodeParameter('issueKey', i) as string;
const simplifyOutput = this.getNodeParameter('simplifyOutput', i) as boolean;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
if (additionalFields.fields) { if (additionalFields.fields) {
qs.fields = additionalFields.fields as string; qs.fields = additionalFields.fields as string;
@@ -652,6 +639,9 @@ export class Jira implements INodeType {
if (additionalFields.expand) { if (additionalFields.expand) {
qs.expand = additionalFields.expand as string; qs.expand = additionalFields.expand as string;
} }
if (simplifyOutput) {
qs.expand = `${qs.expand || ''},names`;
}
if (additionalFields.properties) { if (additionalFields.properties) {
qs.properties = additionalFields.properties as string; qs.properties = additionalFields.properties as string;
} }
@@ -659,7 +649,12 @@ export class Jira implements INodeType {
qs.updateHistory = additionalFields.updateHistory as string; qs.updateHistory = additionalFields.updateHistory as string;
} }
responseData = await jiraSoftwareCloudApiRequest.call(this, `/api/2/issue/${issueKey}`, 'GET', {}, qs); 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 //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 = await jiraSoftwareCloudApiRequest.call(this, `/api/2/search`, 'POST', body);
responseData = responseData.issues; 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 //https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-issue-issueIdOrKey-changelog-get