feat(Jira Software Node): Use resource locator component (#5090)

* ️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

*  removed ts-ignore

*  removed ts-ignore

Co-authored-by: Michael Kret <michael.k@radency.com>
This commit is contained in:
Marcus 2023-01-24 17:42:38 +01:00 committed by GitHub
parent 2b776f39f1
commit 237b1d8614
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 654 additions and 275 deletions

View file

@ -7,13 +7,12 @@ import {
ILoadOptionsFunctions, ILoadOptionsFunctions,
} from 'n8n-core'; } from 'n8n-core';
import { IDataObject } from 'n8n-workflow'; import { IDataObject, INodeListSearchItems, NodeApiError } from 'n8n-workflow';
export async function jiraSoftwareCloudApiRequest( export async function jiraSoftwareCloudApiRequest(
this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
endpoint: string, endpoint: string,
method: string, method: string,
body: any = {}, body: any = {},
query?: IDataObject, query?: IDataObject,
uri?: string, uri?: string,
@ -56,8 +55,20 @@ export async function jiraSoftwareCloudApiRequest(
if (Object.keys(query || {}).length === 0) { if (Object.keys(query || {}).length === 0) {
delete options.qs; delete options.qs;
} }
try {
return this.helpers.requestWithAuthentication.call(this, credentialType, options); 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( export async function jiraSoftwareCloudApiRequestAllItems(
@ -65,7 +76,6 @@ export async function jiraSoftwareCloudApiRequestAllItems(
propertyName: string, propertyName: string,
endpoint: string, endpoint: string,
method: string, method: string,
body: any = {}, body: any = {},
query: IDataObject = {}, query: IDataObject = {},
): Promise<any> { ): Promise<any> {
@ -198,3 +208,22 @@ export const allEvents = [
'worklog_updated', 'worklog_updated',
'worklog_deleted', '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;
});
}

View file

@ -71,42 +71,93 @@ export const issueFields: INodeProperties[] = [
/* issue:create */ /* issue:create */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Project Name or ID', displayName: 'Project',
name: 'project', name: 'project',
type: 'options', type: 'resourceLocator',
description: default: { mode: 'list', value: '' },
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
default: '',
required: true, 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: { displayOptions: {
show: { show: {
resource: ['issue'], resource: ['issue'],
operation: ['create'], operation: ['create'],
}, },
}, },
typeOptions: {
loadOptionsMethod: 'getProjects',
loadOptionsDependsOn: ['jiraVersion'],
},
}, },
{ {
displayName: 'Issue Type Name or ID', displayName: 'Issue Type',
name: 'issueType', name: 'issueType',
type: 'options', type: 'resourceLocator',
default: '', default: { mode: 'list', value: '' },
required: true, 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: { displayOptions: {
show: { show: {
resource: ['issue'], resource: ['issue'],
operation: ['create'], operation: ['create'],
}, },
}, },
typeOptions: {
loadOptionsMethod: 'getIssueTypes',
loadOptionsDependsOn: ['project'],
},
description:
'Issue Types. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
}, },
{ {
displayName: 'Summary', displayName: 'Summary',
@ -135,15 +186,41 @@ export const issueFields: INodeProperties[] = [
}, },
options: [ options: [
{ {
displayName: 'Assignee Name or ID', displayName: 'Assignee',
name: 'assignee', name: 'assignee',
type: 'options', type: 'resourceLocator',
description: default: { mode: 'list', value: '' },
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>', modes: [
typeOptions: { {
loadOptionsMethod: 'getUsers', displayName: 'Assignee',
}, name: 'list',
default: '', 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', displayName: 'Description',
@ -178,16 +255,41 @@ export const issueFields: INodeProperties[] = [
displayName: 'Custom Field', displayName: 'Custom Field',
values: [ values: [
{ {
displayName: 'Field Name or ID', displayName: 'Field',
name: 'fieldId', name: 'fieldId',
type: 'options', type: 'resourceLocator',
typeOptions: { default: { mode: 'list', value: '' },
loadOptionsMethod: 'getCustomFields', modes: [
loadOptionsDependsOn: ['project'], {
}, displayName: 'Field',
description: name: 'list',
'ID of the field to set. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.', type: 'list',
default: '', 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', displayName: 'Field Value',
@ -237,26 +339,77 @@ export const issueFields: INodeProperties[] = [
default: '', default: '',
}, },
{ {
displayName: 'Priority Name or ID', displayName: 'Priority',
name: 'priority', name: 'priority',
type: 'options', type: 'resourceLocator',
description: default: { mode: 'list', value: '' },
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>', modes: [
typeOptions: { {
loadOptionsMethod: 'getPriorities', displayName: 'Priority',
}, name: 'list',
default: '', 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', name: 'reporter',
type: 'options', type: 'resourceLocator',
description: default: { mode: 'list', value: '' },
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>', modes: [
typeOptions: { {
loadOptionsMethod: 'getUsers', displayName: 'Reporter',
}, name: 'list',
default: '', 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', displayName: 'Update History',
@ -299,15 +452,41 @@ export const issueFields: INodeProperties[] = [
}, },
options: [ options: [
{ {
displayName: 'Assignee Name or ID', displayName: 'Assignee',
name: 'assignee', name: 'assignee',
type: 'options', type: 'resourceLocator',
description: default: { mode: 'list', value: '' },
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>', modes: [
typeOptions: { {
loadOptionsMethod: 'getUsers', displayName: 'Assignee',
}, name: 'list',
default: '', 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', displayName: 'Description',
@ -330,16 +509,41 @@ export const issueFields: INodeProperties[] = [
displayName: 'Custom Field', displayName: 'Custom Field',
values: [ values: [
{ {
displayName: 'Field Name or ID', displayName: 'Field',
name: 'fieldId', name: 'fieldId',
type: 'options', type: 'resourceLocator',
typeOptions: { default: { mode: 'list', value: '' },
loadOptionsMethod: 'getCustomFields', modes: [
loadOptionsDependsOn: ['issueKey'], {
}, displayName: 'Field',
description: name: 'list',
'ID of the field to set. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.', type: 'list',
default: '', 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', displayName: 'Field Value',
@ -396,26 +600,77 @@ export const issueFields: INodeProperties[] = [
default: '', default: '',
}, },
{ {
displayName: 'Priority Name or ID', displayName: 'Priority',
name: 'priority', name: 'priority',
type: 'options', type: 'resourceLocator',
description: default: { mode: 'list', value: '' },
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>', modes: [
typeOptions: { {
loadOptionsMethod: 'getPriorities', displayName: 'Priority',
}, name: 'list',
default: '', 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', name: 'reporter',
type: 'options', type: 'resourceLocator',
description: default: { mode: 'list', value: '' },
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>', modes: [
typeOptions: { {
loadOptionsMethod: 'getUsers', displayName: 'Reporter',
}, name: 'list',
default: '', 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', displayName: 'Summary',
@ -424,15 +679,40 @@ export const issueFields: INodeProperties[] = [
default: '', default: '',
}, },
{ {
displayName: 'Status Name or ID', displayName: 'Status',
name: 'statusId', name: 'statusId',
type: 'options', type: 'resourceLocator',
typeOptions: { default: { mode: 'list', value: '' },
loadOptionsMethod: 'getTransitions', modes: [
}, {
default: '', displayName: 'Status',
description: name: 'list',
'The ID of the issue status. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.', 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,})',
},
},
],
}, },
], ],
}, },

View file

@ -7,6 +7,8 @@ import {
IDataObject, IDataObject,
ILoadOptionsFunctions, ILoadOptionsFunctions,
INodeExecutionData, INodeExecutionData,
INodeListSearchItems,
INodeListSearchResult,
INodePropertyOptions, INodePropertyOptions,
INodeType, INodeType,
INodeTypeDescription, INodeTypeDescription,
@ -14,6 +16,7 @@ import {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
filterSortSearchListItems,
jiraSoftwareCloudApiRequest, jiraSoftwareCloudApiRequest,
jiraSoftwareCloudApiRequestAllItems, jiraSoftwareCloudApiRequestAllItems,
simplifyIssueOutput, simplifyIssueOutput,
@ -129,11 +132,14 @@ export class Jira implements INodeType {
}; };
methods = { methods = {
loadOptions: { listSearch: {
// Get all the projects to display them to user so that he can // Get all the projects to display them to user so that he can
// select them easily // select them easily
async getProjects(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> { async getProjects(
const returnData: INodePropertyOptions[] = []; this: ILoadOptionsFunctions,
filter?: string,
): Promise<INodeListSearchResult> {
const returnData: INodeListSearchItems[] = [];
const jiraVersion = this.getCurrentNodeParameter('jiraVersion') as string; const jiraVersion = this.getCurrentNodeParameter('jiraVersion') as string;
let endpoint = ''; let endpoint = '';
let projects; let projects;
@ -163,24 +169,14 @@ export class Jira implements INodeType {
}); });
} }
returnData.sort((a, b) => { return { results: filterSortSearchListItems(returnData, filter) };
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
return 0;
});
return returnData;
}, },
// Get all the issue types to display them to user so that he can // Get all the issue types to display them to user so that he can
// select them easily // select them easily
async getIssueTypes(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> { async getIssueTypes(this: ILoadOptionsFunctions): Promise<INodeListSearchResult> {
const projectId = this.getCurrentNodeParameter('project'); const projectId = this.getCurrentNodeParameter('project', { extractValue: true });
const returnData: INodePropertyOptions[] = []; const returnData: INodeListSearchItems[] = [];
const { issueTypes } = await jiraSoftwareCloudApiRequest.call( const { issueTypes } = await jiraSoftwareCloudApiRequest.call(
this, this,
`/api/2/project/${projectId}`, `/api/2/project/${projectId}`,
@ -204,9 +200,146 @@ export class Jira implements INodeType {
} }
return 0; 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<INodeListSearchResult> {
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<INodeListSearchResult> {
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<INodeListSearchResult> {
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<INodeListSearchResult> {
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 // Get all the labels to display them to user so that he can
// select them easily // select them easily
async getLabels(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> { async getLabels(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
@ -237,36 +370,6 @@ export class Jira implements INodeType {
return returnData; return returnData;
}, },
// Get all the priorities to display them to user so that he can
// select them easily
async getPriorities(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
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 // 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[]> {
@ -326,90 +429,12 @@ export class Jira implements INodeType {
return returnData; return returnData;
}, },
// Get all the groups to display them to user so that he can
// select them easily
async getTransitions(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
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<INodePropertyOptions[]> {
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 // Get all the components to display them to user so that he can
// select them easily // select them easily
async getProjectComponents(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> { async getProjectComponents(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = []; const returnData: INodePropertyOptions[] = [];
const project = this.getCurrentNodeParameter('project'); const project = this.getCurrentNodeParameter('project', { extractValue: true });
const { values: components } = await jiraSoftwareCloudApiRequest.call( const { values: components } = await jiraSoftwareCloudApiRequest.call(
this, this,
`/api/2/project/${project}/component`, `/api/2/project/${project}/component`,
@ -454,9 +479,29 @@ export class Jira implements INodeType {
if (operation === 'create') { if (operation === 'create') {
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
const summary = this.getNodeParameter('summary', i) as string; const summary = this.getNodeParameter('summary', i) as string;
const projectId = this.getNodeParameter('project', i) as string; const projectId = this.getNodeParameter('project', i, '', {
const issueTypeId = this.getNodeParameter('issueType', i) as string; extractValue: true,
}) as string;
const issueTypeId = this.getNodeParameter('issueType', i, '', {
extractValue: true,
}) as string;
const additionalFields = this.getNodeParameter('additionalFields', i); 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 body: IIssue = {};
const fields: IFields = { const fields: IFields = {
summary, summary,
@ -513,6 +558,12 @@ export class Jira implements INodeType {
const customFields = (additionalFields.customFieldsUi as IDataObject) const customFields = (additionalFields.customFieldsUi as IDataObject)
.customFieldsValues as IDataObject[]; .customFieldsValues as IDataObject[];
if (customFields) { 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( const data = customFields.reduce(
(obj, value) => Object.assign(obj, { [`${value.fieldId}`]: value.fieldValue }), (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++) { for (let i = 0; i < length; i++) {
const issueKey = this.getNodeParameter('issueKey', i) as string; const issueKey = this.getNodeParameter('issueKey', i) as string;
const updateFields = this.getNodeParameter('updateFields', i); 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 body: IIssue = {};
const fields: IFields = {}; const fields: IFields = {};
if (updateFields.summary) { if (updateFields.summary) {
@ -610,6 +682,12 @@ export class Jira implements INodeType {
const customFields = (updateFields.customFieldsUi as IDataObject) const customFields = (updateFields.customFieldsUi as IDataObject)
.customFieldsValues as IDataObject[]; .customFieldsValues as IDataObject[];
if (customFields) { 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( const data = customFields.reduce(
(obj, value) => Object.assign(obj, { [`${value.fieldId}`]: value.fieldValue }), (obj, value) => Object.assign(obj, { [`${value.fieldId}`]: value.fieldValue }),
{}, {},
@ -825,42 +903,39 @@ export class Jira implements INodeType {
if (!jsonActive) { if (!jsonActive) {
const notificationRecipientsValues = ( const notificationRecipientsValues = (
this.getNodeParameter('notificationRecipientsUi', i) as IDataObject this.getNodeParameter('notificationRecipientsUi', i) as IDataObject
).notificationRecipientsValues as IDataObject[]; ).notificationRecipientsValues as IDataObject;
const notificationRecipients: INotificationRecipients = {}; const notificationRecipients: INotificationRecipients = {};
if (notificationRecipientsValues) { if (notificationRecipientsValues) {
// @ts-ignore
if (notificationRecipientsValues.reporter) { if (notificationRecipientsValues.reporter) {
// @ts-ignore
notificationRecipients.reporter = notificationRecipientsValues.reporter as boolean; notificationRecipients.reporter = notificationRecipientsValues.reporter as boolean;
} }
// @ts-ignore
if (notificationRecipientsValues.assignee) { if (notificationRecipientsValues.assignee) {
// @ts-ignore
notificationRecipients.assignee = notificationRecipientsValues.assignee as boolean; notificationRecipients.assignee = notificationRecipientsValues.assignee as boolean;
} }
// @ts-ignore
if (notificationRecipientsValues.assignee) { if (notificationRecipientsValues.assignee) {
// @ts-ignore
notificationRecipients.watchers = notificationRecipientsValues.watchers as boolean; notificationRecipients.watchers = notificationRecipientsValues.watchers as boolean;
} }
// @ts-ignore
if (notificationRecipientsValues.voters) { if (notificationRecipientsValues.voters) {
// @ts-ignore
notificationRecipients.watchers = notificationRecipientsValues.voters as boolean; notificationRecipients.watchers = notificationRecipientsValues.voters as boolean;
} }
// @ts-ignore
if (notificationRecipientsValues.users.length > 0) { if (((notificationRecipientsValues.users as IDataObject[]) || []).length > 0) {
// @ts-ignore notificationRecipients.users = (
notificationRecipients.users = notificationRecipientsValues.users.map((user) => { notificationRecipientsValues.users as IDataObject[]
).map((user) => {
return { return {
accountId: user, accountId: user,
}; };
}); });
} }
// @ts-ignore
if (notificationRecipientsValues.groups.length > 0) { if (((notificationRecipientsValues.groups as IDataObject[]) || []).length > 0) {
// @ts-ignore notificationRecipients.groups = (
notificationRecipients.groups = notificationRecipientsValues.groups.map((group) => { notificationRecipientsValues.groups as IDataObject[]
).map((group) => {
return { return {
name: group, name: group,
}; };
@ -870,18 +945,20 @@ export class Jira implements INodeType {
body.to = notificationRecipients; body.to = notificationRecipients;
const notificationRecipientsRestrictionsValues = ( const notificationRecipientsRestrictionsValues = (
this.getNodeParameter('notificationRecipientsRestrictionsUi', i) as IDataObject this.getNodeParameter('notificationRecipientsRestrictionsUi', i) as IDataObject
).notificationRecipientsRestrictionsValues as IDataObject[]; ).notificationRecipientsRestrictionsValues as IDataObject;
const notificationRecipientsRestrictions: NotificationRecipientsRestrictions = {}; const notificationRecipientsRestrictions: NotificationRecipientsRestrictions = {};
if (notificationRecipientsRestrictionsValues) { if (notificationRecipientsRestrictionsValues) {
// @ts-ignore if (
if (notificationRecipientsRestrictionsValues.groups.length > 0) { ((notificationRecipientsRestrictionsValues.groups as IDataObject[]) || []).length >
notificationRecipientsRestrictions.groups = 0
// @ts-ignore ) {
notificationRecipientsRestrictionsValues.groups.map((group) => { notificationRecipientsRestrictions.groups = (
return { notificationRecipientsRestrictionsValues.groups as IDataObject[]
name: group, ).map((group) => {
}; return {
}); name: group,
};
});
} }
} }
body.restrict = notificationRecipientsRestrictions; body.restrict = notificationRecipientsRestrictions;
@ -1075,18 +1152,16 @@ export class Jira implements INodeType {
'GET', 'GET',
{}, {},
{}, {},
// @ts-ignore attachment?.json.content as string,
attachment?.json.content,
{ json: false, encoding: null }, { json: false, encoding: null },
); );
//@ts-ignore
returnData[index].binary[binaryPropertyName] = await this.helpers.prepareBinaryData( (returnData[index].binary as IBinaryKeyData)[binaryPropertyName] =
buffer, await this.helpers.prepareBinaryData(
// @ts-ignore buffer,
attachment.json.filename, attachment.json.filename as string,
// @ts-ignore attachment.json.mimeType as string,
attachment.json.mimeType, );
);
} }
} }
} }
@ -1122,25 +1197,21 @@ export class Jira implements INodeType {
const binaryPropertyName = this.getNodeParameter('binaryProperty', 0); const binaryPropertyName = this.getNodeParameter('binaryProperty', 0);
for (const [index, attachment] of returnData.entries()) { for (const [index, attachment] of returnData.entries()) {
returnData[index].binary = {}; returnData[index].binary = {};
//@ts-ignore
const buffer = await jiraSoftwareCloudApiRequest.call( const buffer = await jiraSoftwareCloudApiRequest.call(
this, this,
'', '',
'GET', 'GET',
{}, {},
{}, {},
// @ts-ignore attachment.json.content as string,
attachment.json.content,
{ json: false, encoding: null }, { json: false, encoding: null },
); );
//@ts-ignore (returnData[index].binary as IBinaryKeyData)[binaryPropertyName] =
returnData[index].binary[binaryPropertyName] = await this.helpers.prepareBinaryData( await this.helpers.prepareBinaryData(
buffer, buffer,
// @ts-ignore attachment.json.filename as string,
attachment.json.filename, attachment.json.mimeType as string,
// @ts-ignore );
attachment.json.mimeType,
);
} }
} }
} }

View file

@ -359,7 +359,6 @@ export class JiraTrigger implements INodeType {
], ],
}; };
// @ts-ignore (because of request)
webhookMethods = { webhookMethods = {
default: { default: {
async checkExists(this: IHookFunctions): Promise<boolean> { async checkExists(this: IHookFunctions): Promise<boolean> {