first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,333 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
IHookFunctions,
|
||||
IHttpRequestMethods,
|
||||
ILoadOptionsFunctions,
|
||||
INodeListSearchItems,
|
||||
INodePropertyOptions,
|
||||
IRequestOptions,
|
||||
JsonObject,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeApiError } from 'n8n-workflow';
|
||||
|
||||
import type { JiraServerInfo, JiraWebhook } from './types';
|
||||
|
||||
export async function jiraSoftwareCloudApiRequest(
|
||||
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
|
||||
endpoint: string,
|
||||
method: IHttpRequestMethods,
|
||||
body: any = {},
|
||||
query?: IDataObject,
|
||||
uri?: string,
|
||||
option: IDataObject = {},
|
||||
): Promise<any> {
|
||||
const jiraVersion = this.getNodeParameter('jiraVersion', 0) as string;
|
||||
|
||||
let domain = '';
|
||||
let credentialType: string;
|
||||
|
||||
if (jiraVersion === 'server') {
|
||||
domain = (await this.getCredentials('jiraSoftwareServerApi')).domain as string;
|
||||
credentialType = 'jiraSoftwareServerApi';
|
||||
} else if (jiraVersion === 'serverPat') {
|
||||
domain = (await this.getCredentials('jiraSoftwareServerPatApi')).domain as string;
|
||||
credentialType = 'jiraSoftwareServerPatApi';
|
||||
} else {
|
||||
domain = (await this.getCredentials('jiraSoftwareCloudApi')).domain as string;
|
||||
credentialType = 'jiraSoftwareCloudApi';
|
||||
}
|
||||
|
||||
const options: IRequestOptions = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'X-Atlassian-Token': 'no-check',
|
||||
},
|
||||
method,
|
||||
qs: query,
|
||||
uri: uri || `${domain}/rest${endpoint}`,
|
||||
body,
|
||||
json: true,
|
||||
};
|
||||
|
||||
if (Object.keys(option).length !== 0) {
|
||||
Object.assign(options, option);
|
||||
}
|
||||
|
||||
if (Object.keys(body as IDataObject).length === 0) {
|
||||
delete options.body;
|
||||
}
|
||||
|
||||
if (Object.keys(query || {}).length === 0) {
|
||||
delete options.qs;
|
||||
}
|
||||
try {
|
||||
return await this.helpers.requestWithAuthentication.call(this, credentialType, options);
|
||||
} catch (error) {
|
||||
if (error.description?.includes?.("Field 'priority' cannot be set")) {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject, {
|
||||
message:
|
||||
"Field 'priority' cannot be set. You need to add the Priority field to your Jira Project's Issue Types.",
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function handlePagination(
|
||||
method: IHttpRequestMethods,
|
||||
body: any,
|
||||
query: IDataObject,
|
||||
paginationType: 'offset' | 'token',
|
||||
responseData?: any,
|
||||
): boolean {
|
||||
if (!responseData) {
|
||||
if (paginationType === 'offset') {
|
||||
if (method === 'GET') {
|
||||
// Example: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-search/#api-rest-api-2-search-get
|
||||
query.startAt = 0;
|
||||
query.maxResults = 100;
|
||||
} else {
|
||||
// Example: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-search/#api-rest-api-2-search-post
|
||||
body.startAt = 0;
|
||||
body.maxResults = 100;
|
||||
}
|
||||
} else {
|
||||
if (method === 'GET') {
|
||||
// Example: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-search/#api-rest-api-2-search-jql-get
|
||||
query.maxResults = 100;
|
||||
} else {
|
||||
// Example: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-search/#api-rest-api-2-search-jql-post
|
||||
body.maxResults = 100;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (paginationType === 'offset') {
|
||||
const nextStartAt = (responseData.startAt as number) + (responseData.maxResults as number);
|
||||
if (method === 'GET') {
|
||||
query.startAt = nextStartAt;
|
||||
} else {
|
||||
body.startAt = nextStartAt;
|
||||
}
|
||||
|
||||
return nextStartAt < responseData.total;
|
||||
} else {
|
||||
if (method === 'GET') {
|
||||
query.nextPageToken = responseData.nextPageToken as string;
|
||||
} else {
|
||||
body.nextPageToken = responseData.nextPageToken as string;
|
||||
}
|
||||
|
||||
return !!responseData.nextPageToken;
|
||||
}
|
||||
}
|
||||
|
||||
export async function jiraSoftwareCloudApiRequestAllItems(
|
||||
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
|
||||
propertyName: string,
|
||||
endpoint: string,
|
||||
method: IHttpRequestMethods,
|
||||
body: any = {},
|
||||
query: IDataObject = {},
|
||||
paginationType: 'offset' | 'token' = 'offset',
|
||||
): Promise<any> {
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
let hasNextPage = handlePagination(method, body, query, paginationType);
|
||||
do {
|
||||
responseData = await jiraSoftwareCloudApiRequest.call(this, endpoint, method, body, query);
|
||||
returnData.push.apply(returnData, responseData[propertyName] as IDataObject[]);
|
||||
hasNextPage = handlePagination(method, body, query, paginationType, responseData);
|
||||
} while (hasNextPage);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export function validateJSON(json: string | undefined): any {
|
||||
let result;
|
||||
try {
|
||||
result = JSON.parse(json!);
|
||||
} catch (exception) {
|
||||
result = '';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function eventExists(currentEvents: string[], webhookEvents: string[]) {
|
||||
for (const currentEvent of currentEvents) {
|
||||
if (!webhookEvents.includes(currentEvent)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function getWebhookId(webhook: JiraWebhook) {
|
||||
if (webhook.id) return webhook.id.toString();
|
||||
return webhook.self?.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',
|
||||
'board_deleted',
|
||||
'board_configuration_changed',
|
||||
'comment_created',
|
||||
'comment_updated',
|
||||
'comment_deleted',
|
||||
'jira:issue_created',
|
||||
'jira:issue_updated',
|
||||
'jira:issue_deleted',
|
||||
'option_voting_changed',
|
||||
'option_watching_changed',
|
||||
'option_unassigned_issues_changed',
|
||||
'option_subtasks_changed',
|
||||
'option_attachments_changed',
|
||||
'option_issuelinks_changed',
|
||||
'option_timetracking_changed',
|
||||
'project_created',
|
||||
'project_updated',
|
||||
'project_deleted',
|
||||
'sprint_created',
|
||||
'sprint_deleted',
|
||||
'sprint_updated',
|
||||
'sprint_started',
|
||||
'sprint_closed',
|
||||
'user_created',
|
||||
'user_updated',
|
||||
'user_deleted',
|
||||
'jira:version_released',
|
||||
'jira:version_unreleased',
|
||||
'jira:version_created',
|
||||
'jira:version_moved',
|
||||
'jira:version_updated',
|
||||
'jira:version_deleted',
|
||||
'issuelink_created',
|
||||
'issuelink_deleted',
|
||||
'worklog_created',
|
||||
'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;
|
||||
});
|
||||
}
|
||||
|
||||
export async function getUsers(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const jiraVersion = this.getCurrentNodeParameter('jiraVersion') as string;
|
||||
const maxResults = 1000;
|
||||
const query: IDataObject = { maxResults };
|
||||
let endpoint = '/api/2/users/search';
|
||||
|
||||
if (jiraVersion === 'server' || jiraVersion === 'serverPat') {
|
||||
endpoint = '/api/2/user/search';
|
||||
query.username = "'";
|
||||
}
|
||||
|
||||
const users = [];
|
||||
let hasNextPage: boolean;
|
||||
|
||||
do {
|
||||
const usersPage = (await jiraSoftwareCloudApiRequest.call(
|
||||
this,
|
||||
endpoint,
|
||||
'GET',
|
||||
{},
|
||||
{ ...query, startAt: users.length },
|
||||
)) as IDataObject[];
|
||||
users.push(...usersPage);
|
||||
hasNextPage = usersPage.length === maxResults;
|
||||
} while (hasNextPage);
|
||||
|
||||
return users
|
||||
.filter((user) => user.active)
|
||||
.map((user) => ({
|
||||
name: user.displayName as string,
|
||||
value: (user.accountId ?? user.name) as string,
|
||||
}))
|
||||
.sort((a: INodePropertyOptions, b: INodePropertyOptions) => {
|
||||
return a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1;
|
||||
});
|
||||
}
|
||||
|
||||
export async function getServerInfo(this: IHookFunctions) {
|
||||
return await (jiraSoftwareCloudApiRequest.call(
|
||||
this,
|
||||
'/api/2/serverInfo',
|
||||
'GET',
|
||||
) as Promise<JiraServerInfo>);
|
||||
}
|
||||
|
||||
export async function getWebhookEndpoint(this: IHookFunctions) {
|
||||
const serverInfo = await getServerInfo.call(this).catch(() => null);
|
||||
|
||||
if (!serverInfo || serverInfo.deploymentType === 'Cloud') return '/webhooks/1.0/webhook';
|
||||
|
||||
// Assume old version when versionNumbers is not set
|
||||
const majorVersion = serverInfo.versionNumbers?.[0] ?? 1;
|
||||
|
||||
return majorVersion >= 10 ? '/jira-webhook/1.0/webhooks' : '/webhooks/1.0/webhook';
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const issueAttachmentOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueAttachment'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Add',
|
||||
value: 'add',
|
||||
description: 'Add attachment to issue',
|
||||
action: 'Add an attachment to an issue',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get an attachment',
|
||||
action: 'Get an attachment from an issue',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Get many attachments',
|
||||
action: 'Get many issue attachments',
|
||||
},
|
||||
{
|
||||
name: 'Remove',
|
||||
value: 'remove',
|
||||
description: 'Remove an attachment',
|
||||
action: 'Remove an attachment from an issue',
|
||||
},
|
||||
],
|
||||
default: 'add',
|
||||
},
|
||||
];
|
||||
|
||||
export const issueAttachmentFields: INodeProperties[] = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* issueAttachment:add */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Issue Key',
|
||||
name: 'issueKey',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueAttachment'],
|
||||
operation: ['add'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Input Binary Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueAttachment'],
|
||||
operation: ['add'],
|
||||
},
|
||||
},
|
||||
name: 'binaryPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
hint: 'The name of the input binary field containing the file to be written',
|
||||
required: true,
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* issueAttachment:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Attachment ID',
|
||||
name: 'attachmentId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueAttachment'],
|
||||
operation: ['get'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The ID of the attachment',
|
||||
},
|
||||
{
|
||||
displayName: 'Download',
|
||||
name: 'download',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueAttachment'],
|
||||
operation: ['get'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Put Output File in Field',
|
||||
name: 'binaryProperty',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueAttachment'],
|
||||
operation: ['get'],
|
||||
download: [true],
|
||||
},
|
||||
},
|
||||
hint: 'The name of the output binary field to put the file in',
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* issueAttachment:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Issue Key',
|
||||
name: 'issueKey',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueAttachment'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueAttachment'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueAttachment'],
|
||||
operation: ['getAll'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Download',
|
||||
name: 'download',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueAttachment'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Put Output File in Field',
|
||||
name: 'binaryProperty',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueAttachment'],
|
||||
operation: ['getAll'],
|
||||
download: [true],
|
||||
},
|
||||
},
|
||||
hint: 'The name of the output binary field to put the file in',
|
||||
required: true,
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* issueAttachment:remove */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Attachment ID',
|
||||
name: 'attachmentId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueAttachment'],
|
||||
operation: ['remove'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The ID of the attachment',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,455 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const issueCommentOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Add',
|
||||
value: 'add',
|
||||
description: 'Add comment to issue',
|
||||
action: 'Add a comment',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a comment',
|
||||
action: 'Get a comment',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Get many comments',
|
||||
action: 'Get many comments',
|
||||
},
|
||||
{
|
||||
name: 'Remove',
|
||||
value: 'remove',
|
||||
description: 'Remove a comment',
|
||||
action: 'Remove a comment',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a comment',
|
||||
action: 'Update a comment',
|
||||
},
|
||||
],
|
||||
default: 'add',
|
||||
},
|
||||
];
|
||||
|
||||
export const issueCommentFields: INodeProperties[] = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* issueComment:add */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Issue Key',
|
||||
name: 'issueKey',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['add'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-lowercase-first-char
|
||||
description: 'issueComment Key',
|
||||
},
|
||||
{
|
||||
displayName: 'JSON Parameters',
|
||||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['add'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Comment',
|
||||
name: 'comment',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['add'],
|
||||
jsonParameters: [false],
|
||||
},
|
||||
},
|
||||
description: "Comment's text",
|
||||
},
|
||||
{
|
||||
displayName: 'Document Format (JSON)',
|
||||
name: 'commentJson',
|
||||
type: 'json',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['add'],
|
||||
jsonParameters: [true],
|
||||
},
|
||||
},
|
||||
description:
|
||||
'The Atlassian Document Format (ADF). Online builder can be found <a href="https://developer.atlassian.com/cloud/jira/platform/apis/document/playground/">here</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['add'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Expand',
|
||||
name: 'expand',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Rendered Body',
|
||||
value: 'renderedBody',
|
||||
},
|
||||
],
|
||||
default: [],
|
||||
description:
|
||||
'Use expand to include additional information about comments in the response. This parameter accepts Rendered Body, which returns the comment body rendered in HTML.',
|
||||
},
|
||||
{
|
||||
displayName: 'Use Wiki Markup',
|
||||
name: 'wikiMarkup',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/jiraVersion': ['cloud'],
|
||||
},
|
||||
},
|
||||
description:
|
||||
'Whether to enable parsing of wikiformatting for this comment. Default is false.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* issueComment:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Issue Key',
|
||||
name: 'issueKey',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['get'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The ID or key of the issue',
|
||||
},
|
||||
{
|
||||
displayName: 'Comment ID',
|
||||
name: 'commentId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['get'],
|
||||
},
|
||||
},
|
||||
description: 'The ID of the comment',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['get'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Expand',
|
||||
name: 'expand',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Rendered Body',
|
||||
value: 'renderedBody',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description:
|
||||
'Use expand to include additional information about comments in the response. This parameter accepts Rendered Body, which returns the comment body rendered in HTML.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* issueComment:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Issue Key',
|
||||
name: 'issueKey',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The ID or key of the issue',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['getAll'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Expand',
|
||||
name: 'expand',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Rendered Body',
|
||||
value: 'renderedBody',
|
||||
},
|
||||
],
|
||||
default: 'renderedBody',
|
||||
description:
|
||||
'Use expand to include additional information about comments in the response. This parameter accepts Rendered Body, which returns the comment body rendered in HTML.',
|
||||
},
|
||||
{
|
||||
displayName: 'Order By',
|
||||
name: 'orderBy',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Created Ascending',
|
||||
value: '+created',
|
||||
},
|
||||
{
|
||||
name: 'Created Descending',
|
||||
value: '-created',
|
||||
},
|
||||
],
|
||||
default: '+created',
|
||||
description: 'Order comments by the created date',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* issueComment:remove */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Issue Key',
|
||||
name: 'issueKey',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['remove'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The ID or key of the issue',
|
||||
},
|
||||
{
|
||||
displayName: 'Comment ID',
|
||||
name: 'commentId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['remove'],
|
||||
},
|
||||
},
|
||||
description: 'The ID of the comment',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* issueComment:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Issue Key',
|
||||
name: 'issueKey',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['update'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The Issue Comment key',
|
||||
},
|
||||
{
|
||||
displayName: 'Comment ID',
|
||||
name: 'commentId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['update'],
|
||||
},
|
||||
},
|
||||
description: 'The ID of the comment',
|
||||
},
|
||||
{
|
||||
displayName: 'JSON Parameters',
|
||||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['update'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Comment',
|
||||
name: 'comment',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['update'],
|
||||
jsonParameters: [false],
|
||||
},
|
||||
},
|
||||
description: "Comment's text",
|
||||
},
|
||||
{
|
||||
displayName: 'Document Format (JSON)',
|
||||
name: 'commentJson',
|
||||
type: 'json',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['update'],
|
||||
jsonParameters: [true],
|
||||
},
|
||||
},
|
||||
description:
|
||||
'The Atlassian Document Format (ADF). Online builder can be found <a href="https://developer.atlassian.com/cloud/jira/platform/apis/document/playground/">here</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['issueComment'],
|
||||
operation: ['update'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Expand',
|
||||
name: 'expand',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Rendered Body',
|
||||
value: 'renderedBody',
|
||||
},
|
||||
],
|
||||
default: 'renderedBody',
|
||||
description:
|
||||
'Use expand to include additional information about comments in the response. This parameter accepts Rendered Body, which returns the comment body rendered in HTML.',
|
||||
},
|
||||
{
|
||||
displayName: 'Use Wiki Markup',
|
||||
name: 'wikiMarkup',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/jiraVersion': ['cloud'],
|
||||
},
|
||||
},
|
||||
description:
|
||||
'Whether to enable parsing of wikiformatting for this comment. Default is false.',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
import type { IDataObject } from 'n8n-workflow';
|
||||
|
||||
export interface IFields {
|
||||
assignee?: IDataObject;
|
||||
description?: string;
|
||||
issuetype?: IDataObject;
|
||||
labels?: string[];
|
||||
parent?: IDataObject;
|
||||
priority?: IDataObject;
|
||||
project?: IDataObject;
|
||||
summary?: string;
|
||||
reporter?: IDataObject;
|
||||
components?: IDataObject[];
|
||||
}
|
||||
|
||||
export interface IIssue {
|
||||
fields?: IFields;
|
||||
transition?: IDataObject;
|
||||
}
|
||||
|
||||
export interface INotify {
|
||||
subject?: string;
|
||||
textBody?: string;
|
||||
htmlBody?: string;
|
||||
to?: INotificationRecipients;
|
||||
restrict?: NotificationRecipientsRestrictions;
|
||||
}
|
||||
|
||||
export interface INotificationRecipients {
|
||||
reporter?: boolean;
|
||||
assignee?: boolean;
|
||||
watchers?: boolean;
|
||||
voters?: boolean;
|
||||
users?: IDataObject[];
|
||||
groups?: IDataObject[];
|
||||
}
|
||||
|
||||
export interface NotificationRecipientsRestrictions {
|
||||
groups?: IDataObject[];
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.jira",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Development", "Productivity"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/credentials/jira/"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.jira/"
|
||||
}
|
||||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "5 workflow automations for Mattermost that we love at n8n",
|
||||
"icon": "🤖",
|
||||
"url": "https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "How to automate every step of an incident response workflow",
|
||||
"url": "https://n8n.io/blog/creating-custom-incident-response-workflows-with-n8n/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.jiraTrigger",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Development", "Productivity"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/credentials/jira/"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.jiratrigger/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,606 @@
|
||||
import type {
|
||||
ICredentialDataDecryptedObject,
|
||||
IDataObject,
|
||||
IHookFunctions,
|
||||
IWebhookFunctions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
IWebhookResponseData,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
allEvents,
|
||||
eventExists,
|
||||
getWebhookId,
|
||||
getWebhookEndpoint,
|
||||
jiraSoftwareCloudApiRequest,
|
||||
} from './GenericFunctions';
|
||||
import type { JiraWebhook } from './types';
|
||||
|
||||
export class JiraTrigger implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Jira Trigger',
|
||||
name: 'jiraTrigger',
|
||||
icon: 'file:jira.svg',
|
||||
group: ['trigger'],
|
||||
version: [1, 1.1],
|
||||
description: 'Starts the workflow when Jira events occur',
|
||||
defaults: {
|
||||
name: 'Jira Trigger',
|
||||
},
|
||||
inputs: [],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
credentials: [
|
||||
{
|
||||
displayName: 'Credentials to Connect to Jira',
|
||||
name: 'jiraSoftwareCloudApi',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
jiraVersion: ['cloud'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Credentials to Connect to Jira',
|
||||
name: 'jiraSoftwareServerApi',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
jiraVersion: ['server'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Credentials to Connect to Jira',
|
||||
name: 'jiraSoftwareServerPatApi',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
jiraVersion: ['serverPat'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-class-description-credentials-name-unsuffixed
|
||||
name: 'httpQueryAuth',
|
||||
displayName: 'Credentials to Authenticate Webhook',
|
||||
displayOptions: {
|
||||
show: {
|
||||
authenticateWebhook: [true],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'httpQueryAuth',
|
||||
displayName: 'Credentials to Authenticate Webhook',
|
||||
displayOptions: {
|
||||
show: {
|
||||
incomingAuthentication: ['queryAuth'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
webhooks: [
|
||||
{
|
||||
name: 'default',
|
||||
httpMethod: 'POST',
|
||||
responseMode: 'onReceived',
|
||||
path: 'webhook',
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Jira Version',
|
||||
name: 'jiraVersion',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Cloud',
|
||||
value: 'cloud',
|
||||
},
|
||||
{
|
||||
name: 'Server (Self Hosted)',
|
||||
value: 'server',
|
||||
},
|
||||
{
|
||||
name: 'Server (Pat) (Self Hosted)',
|
||||
value: 'serverPat',
|
||||
},
|
||||
],
|
||||
default: 'cloud',
|
||||
},
|
||||
{
|
||||
displayName: 'Authenticate Incoming Webhook',
|
||||
name: 'authenticateWebhook',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether authentication should be activated for the incoming webhooks (makes it more secure)',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'@version': [{ _cnd: { gte: 1.1 } }],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Authenticate Webhook With',
|
||||
name: 'incomingAuthentication',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Query Auth',
|
||||
value: 'queryAuth',
|
||||
},
|
||||
{
|
||||
name: 'None',
|
||||
value: 'none',
|
||||
},
|
||||
],
|
||||
default: 'none',
|
||||
description: 'If authentication should be activated for the webhook (makes it more secure)',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'@version': [1],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Events',
|
||||
name: 'events',
|
||||
type: 'multiOptions',
|
||||
options: [
|
||||
{
|
||||
name: '*',
|
||||
value: '*',
|
||||
},
|
||||
{
|
||||
name: 'Board Configuration Changed',
|
||||
value: 'board_configuration_changed',
|
||||
},
|
||||
{
|
||||
name: 'Board Created',
|
||||
value: 'board_created',
|
||||
},
|
||||
{
|
||||
name: 'Board Deleted',
|
||||
value: 'board_deleted',
|
||||
},
|
||||
{
|
||||
name: 'Board Updated',
|
||||
value: 'board_updated',
|
||||
},
|
||||
{
|
||||
name: 'Comment Created',
|
||||
value: 'comment_created',
|
||||
},
|
||||
{
|
||||
name: 'Comment Deleted',
|
||||
value: 'comment_deleted',
|
||||
},
|
||||
{
|
||||
name: 'Comment Updated',
|
||||
value: 'comment_updated',
|
||||
},
|
||||
{
|
||||
name: 'Issue Created',
|
||||
value: 'jira:issue_created',
|
||||
},
|
||||
{
|
||||
name: 'Issue Deleted',
|
||||
value: 'jira:issue_deleted',
|
||||
},
|
||||
{
|
||||
name: 'Issue Link Created',
|
||||
value: 'issuelink_created',
|
||||
},
|
||||
{
|
||||
name: 'Issue Link Deleted',
|
||||
value: 'issuelink_deleted',
|
||||
},
|
||||
{
|
||||
name: 'Issue Updated',
|
||||
value: 'jira:issue_updated',
|
||||
},
|
||||
{
|
||||
name: 'Option Attachments Changed',
|
||||
value: 'option_attachments_changed',
|
||||
},
|
||||
{
|
||||
name: 'Option Issue Links Changed',
|
||||
value: 'option_issuelinks_changed',
|
||||
},
|
||||
{
|
||||
name: 'Option Subtasks Changed',
|
||||
value: 'option_subtasks_changed',
|
||||
},
|
||||
{
|
||||
name: 'Option Timetracking Changed',
|
||||
value: 'option_timetracking_changed',
|
||||
},
|
||||
{
|
||||
name: 'Option Unassigned Issues Changed',
|
||||
value: 'option_unassigned_issues_changed',
|
||||
},
|
||||
{
|
||||
name: 'Option Voting Changed',
|
||||
value: 'option_voting_changed',
|
||||
},
|
||||
{
|
||||
name: 'Option Watching Changed',
|
||||
value: 'option_watching_changed',
|
||||
},
|
||||
{
|
||||
name: 'Project Created',
|
||||
value: 'project_created',
|
||||
},
|
||||
{
|
||||
name: 'Project Deleted',
|
||||
value: 'project_deleted',
|
||||
},
|
||||
{
|
||||
name: 'Project Updated',
|
||||
value: 'project_updated',
|
||||
},
|
||||
{
|
||||
name: 'Sprint Closed',
|
||||
value: 'sprint_closed',
|
||||
},
|
||||
{
|
||||
name: 'Sprint Created',
|
||||
value: 'sprint_created',
|
||||
},
|
||||
{
|
||||
name: 'Sprint Deleted',
|
||||
value: 'sprint_deleted',
|
||||
},
|
||||
{
|
||||
name: 'Sprint Started',
|
||||
value: 'sprint_started',
|
||||
},
|
||||
{
|
||||
name: 'Sprint Updated',
|
||||
value: 'sprint_updated',
|
||||
},
|
||||
{
|
||||
name: 'User Created',
|
||||
value: 'user_created',
|
||||
},
|
||||
{
|
||||
name: 'User Deleted',
|
||||
value: 'user_deleted',
|
||||
},
|
||||
{
|
||||
name: 'User Updated',
|
||||
value: 'user_updated',
|
||||
},
|
||||
{
|
||||
name: 'Version Created',
|
||||
value: 'jira:version_created',
|
||||
},
|
||||
{
|
||||
name: 'Version Deleted',
|
||||
value: 'jira:version_deleted',
|
||||
},
|
||||
{
|
||||
name: 'Version Moved',
|
||||
value: 'jira:version_moved',
|
||||
},
|
||||
{
|
||||
name: 'Version Released',
|
||||
value: 'jira:version_released',
|
||||
},
|
||||
{
|
||||
name: 'Version Unreleased',
|
||||
value: 'jira:version_unreleased',
|
||||
},
|
||||
{
|
||||
name: 'Version Updated',
|
||||
value: 'jira:version_updated',
|
||||
},
|
||||
{
|
||||
name: 'Worklog Created',
|
||||
value: 'worklog_created',
|
||||
},
|
||||
{
|
||||
name: 'Worklog Deleted',
|
||||
value: 'worklog_deleted',
|
||||
},
|
||||
{
|
||||
name: 'Worklog Updated',
|
||||
value: 'worklog_updated',
|
||||
},
|
||||
],
|
||||
required: true,
|
||||
default: [],
|
||||
description: 'The events to listen to',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Exclude Body',
|
||||
name: 'excludeBody',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether a request with empty body will be sent to the URL. Leave unchecked if you want to receive JSON.',
|
||||
},
|
||||
{
|
||||
displayName: 'Filter',
|
||||
name: 'filter',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'Project = JRA AND resolution = Fixed',
|
||||
description:
|
||||
'You can specify a JQL query to send only events triggered by matching issues. The JQL filter only applies to events under the Issue and Comment columns.',
|
||||
},
|
||||
{
|
||||
displayName: 'Include Fields',
|
||||
name: 'includeFields',
|
||||
type: 'multiOptions',
|
||||
options: [
|
||||
{
|
||||
name: 'Attachment ID',
|
||||
value: 'attachment.id',
|
||||
},
|
||||
{
|
||||
name: 'Board ID',
|
||||
value: 'board.id',
|
||||
},
|
||||
{
|
||||
name: 'Comment ID',
|
||||
value: 'comment.id',
|
||||
},
|
||||
{
|
||||
name: 'Issue ID',
|
||||
value: 'issue.id',
|
||||
},
|
||||
{
|
||||
name: 'Merge Version ID',
|
||||
value: 'mergeVersion.id',
|
||||
},
|
||||
{
|
||||
name: 'Modified User Account ID',
|
||||
value: 'modifiedUser.accountId',
|
||||
},
|
||||
{
|
||||
name: 'Modified User Key',
|
||||
value: 'modifiedUser.key',
|
||||
},
|
||||
{
|
||||
name: 'Modified User Name',
|
||||
value: 'modifiedUser.name',
|
||||
},
|
||||
{
|
||||
name: 'Project ID',
|
||||
value: 'project.id',
|
||||
},
|
||||
{
|
||||
name: 'Project Key',
|
||||
value: 'project.key',
|
||||
},
|
||||
{
|
||||
name: 'Propery Key',
|
||||
value: 'property.key',
|
||||
},
|
||||
{
|
||||
name: 'Sprint ID',
|
||||
value: 'sprint.id',
|
||||
},
|
||||
{
|
||||
name: 'Version ID',
|
||||
value: 'version.id',
|
||||
},
|
||||
{
|
||||
name: 'Worklog ID',
|
||||
value: 'worklog.id',
|
||||
},
|
||||
],
|
||||
default: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
webhookMethods = {
|
||||
default: {
|
||||
async checkExists(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookUrl = this.getNodeWebhookUrl('default') as string;
|
||||
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
|
||||
const events = this.getNodeParameter('events') as string[];
|
||||
|
||||
const endpoint = await getWebhookEndpoint.call(this);
|
||||
webhookData.endpoint = endpoint;
|
||||
|
||||
const webhooks: JiraWebhook[] = await jiraSoftwareCloudApiRequest.call(
|
||||
this,
|
||||
endpoint,
|
||||
'GET',
|
||||
{},
|
||||
);
|
||||
|
||||
for (const webhook of webhooks) {
|
||||
if (webhook.url === webhookUrl && eventExists(events, webhook.events)) {
|
||||
webhookData.webhookId = getWebhookId(webhook);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
async create(this: IHookFunctions): Promise<boolean> {
|
||||
const nodeVersion = this.getNode().typeVersion;
|
||||
const webhookUrl = this.getNodeWebhookUrl('default') as string;
|
||||
let events = this.getNodeParameter('events', []) as string[];
|
||||
const additionalFields = this.getNodeParameter('additionalFields') as IDataObject;
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
const endpoint = webhookData.endpoint as string;
|
||||
|
||||
let authenticateWebhook = false;
|
||||
|
||||
if (nodeVersion === 1) {
|
||||
const incomingAuthentication = this.getNodeParameter('incomingAuthentication') as string;
|
||||
|
||||
if (incomingAuthentication === 'queryAuth') {
|
||||
authenticateWebhook = true;
|
||||
}
|
||||
} else {
|
||||
authenticateWebhook = this.getNodeParameter('authenticateWebhook') as boolean;
|
||||
}
|
||||
|
||||
if (events.includes('*')) {
|
||||
events = allEvents;
|
||||
}
|
||||
|
||||
const body = {
|
||||
name: `n8n-webhook:${webhookUrl}`,
|
||||
url: webhookUrl,
|
||||
events,
|
||||
filters: {},
|
||||
excludeBody: false,
|
||||
};
|
||||
|
||||
if (additionalFields.filter) {
|
||||
body.filters = {
|
||||
'issue-related-events-section': additionalFields.filter,
|
||||
};
|
||||
}
|
||||
|
||||
if (additionalFields.excludeBody) {
|
||||
body.excludeBody = additionalFields.excludeBody as boolean;
|
||||
}
|
||||
|
||||
const parameters: Record<string, string> = {};
|
||||
|
||||
if (authenticateWebhook) {
|
||||
let httpQueryAuth;
|
||||
try {
|
||||
httpQueryAuth = await this.getCredentials('httpQueryAuth');
|
||||
} catch (e) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
new Error('Could not retrieve HTTP Query Auth credentials', { cause: e }),
|
||||
);
|
||||
}
|
||||
if (!httpQueryAuth.name && !httpQueryAuth.value) {
|
||||
throw new NodeOperationError(this.getNode(), 'HTTP Query Auth credentials are empty');
|
||||
}
|
||||
parameters[encodeURIComponent(httpQueryAuth.name as string)] = Buffer.from(
|
||||
httpQueryAuth.value as string,
|
||||
).toString('base64');
|
||||
}
|
||||
|
||||
if (additionalFields.includeFields) {
|
||||
for (const field of additionalFields.includeFields as string[]) {
|
||||
// eslint-disable-next-line n8n-local-rules/no-interpolation-in-regular-string
|
||||
parameters[field] = '${' + field + '}';
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(parameters as IDataObject).length) {
|
||||
const params = new URLSearchParams(parameters).toString();
|
||||
body.url = `${body.url}?${decodeURIComponent(params)}`;
|
||||
}
|
||||
|
||||
const responseData: JiraWebhook = await jiraSoftwareCloudApiRequest.call(
|
||||
this,
|
||||
endpoint,
|
||||
'POST',
|
||||
body,
|
||||
);
|
||||
|
||||
webhookData.webhookId = getWebhookId(responseData);
|
||||
|
||||
return true;
|
||||
},
|
||||
async delete(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
|
||||
if (webhookData.webhookId !== undefined) {
|
||||
const baseUrl = webhookData.endpoint as string;
|
||||
const webhookId = webhookData.webhookId as string;
|
||||
const endpoint = `${baseUrl}/${webhookId}`;
|
||||
const body = {};
|
||||
|
||||
try {
|
||||
await jiraSoftwareCloudApiRequest.call(this, endpoint, 'DELETE', body);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
// Remove from the static workflow data so that it is clear
|
||||
// that no webhooks are registered anymore
|
||||
delete webhookData.webhookId;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
||||
const nodeVersion = this.getNode().typeVersion;
|
||||
const bodyData = this.getBodyData();
|
||||
const queryData = this.getQueryData() as IDataObject;
|
||||
const response = this.getResponseObject();
|
||||
|
||||
let authenticateWebhook = false;
|
||||
|
||||
if (nodeVersion === 1) {
|
||||
const incomingAuthentication = this.getNodeParameter('incomingAuthentication') as string;
|
||||
|
||||
if (incomingAuthentication === 'queryAuth') {
|
||||
authenticateWebhook = true;
|
||||
}
|
||||
} else {
|
||||
authenticateWebhook = this.getNodeParameter('authenticateWebhook') as boolean;
|
||||
}
|
||||
|
||||
if (authenticateWebhook) {
|
||||
let httpQueryAuth: ICredentialDataDecryptedObject | undefined;
|
||||
|
||||
try {
|
||||
httpQueryAuth = await this.getCredentials<ICredentialDataDecryptedObject>('httpQueryAuth');
|
||||
} catch (error) {}
|
||||
|
||||
if (httpQueryAuth === undefined || !httpQueryAuth.name || !httpQueryAuth.value) {
|
||||
response
|
||||
.status(403)
|
||||
.json({ message: 'Auth settings are not valid, some data are missing' });
|
||||
|
||||
return {
|
||||
noWebhookResponse: true,
|
||||
};
|
||||
}
|
||||
|
||||
const paramName = httpQueryAuth.name as string;
|
||||
const paramValue = Buffer.from(httpQueryAuth.value as string).toString('base64');
|
||||
|
||||
if (!queryData.hasOwnProperty(paramName) || queryData[paramName] !== paramValue) {
|
||||
response.status(403).json({ message: 'Provided authentication data is not valid' });
|
||||
|
||||
return {
|
||||
noWebhookResponse: true,
|
||||
};
|
||||
}
|
||||
|
||||
delete queryData[paramName];
|
||||
|
||||
Object.assign(bodyData, queryData);
|
||||
} else {
|
||||
Object.assign(bodyData, queryData);
|
||||
}
|
||||
|
||||
return {
|
||||
workflowData: [this.helpers.returnJsonArray(bodyData)],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const userOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['user'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a new user',
|
||||
action: 'Create a user',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a user',
|
||||
action: 'Delete a user',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Retrieve a user',
|
||||
action: 'Get a user',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
},
|
||||
];
|
||||
|
||||
export const userFields: INodeProperties[] = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* user:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Username',
|
||||
name: 'username',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['user'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Email Address',
|
||||
name: 'emailAddress',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['user'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Display Name',
|
||||
name: 'displayName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['user'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['user'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Password',
|
||||
name: 'password',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'Password for the user. If a password is not set, a random password is generated.',
|
||||
typeOptions: {
|
||||
password: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Notification',
|
||||
name: 'notification',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether to send the user an email confirmation that they have been added to Jira',
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* user:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Account ID',
|
||||
name: 'accountId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Account ID of the user to delete',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['user'],
|
||||
operation: ['delete'],
|
||||
},
|
||||
},
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* user:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Account ID',
|
||||
name: 'accountId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Account ID of the user to retrieve',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['user'],
|
||||
operation: ['get'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['user'],
|
||||
operation: ['get'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Expand',
|
||||
name: 'expand',
|
||||
type: 'multiOptions',
|
||||
default: [],
|
||||
description: 'Include more information about the user',
|
||||
options: [
|
||||
{
|
||||
name: 'Groups',
|
||||
value: 'groups',
|
||||
description: 'Include all groups to which the user belongs',
|
||||
},
|
||||
{
|
||||
name: 'Application Roles',
|
||||
value: 'applicationRoles',
|
||||
description: 'Include details of all the applications the user can access',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"author": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accountId": {
|
||||
"type": "string"
|
||||
},
|
||||
"accountType": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"avatarUrls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"16x16": {
|
||||
"type": "string"
|
||||
},
|
||||
"24x24": {
|
||||
"type": "string"
|
||||
},
|
||||
"32x32": {
|
||||
"type": "string"
|
||||
},
|
||||
"48x48": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"created": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"field": {
|
||||
"type": "string"
|
||||
},
|
||||
"fieldId": {
|
||||
"type": "string"
|
||||
},
|
||||
"fieldtype": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,514 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"expand": {
|
||||
"type": "string"
|
||||
},
|
||||
"fields": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"attachment": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"author": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accountId": {
|
||||
"type": "string"
|
||||
},
|
||||
"accountType": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"avatarUrls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"16x16": {
|
||||
"type": "string"
|
||||
},
|
||||
"24x24": {
|
||||
"type": "string"
|
||||
},
|
||||
"32x32": {
|
||||
"type": "string"
|
||||
},
|
||||
"48x48": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"created": {
|
||||
"type": "string"
|
||||
},
|
||||
"filename": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"mimeType": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"comment": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"comments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"author": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accountId": {
|
||||
"type": "string"
|
||||
},
|
||||
"accountType": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"avatarUrls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"16x16": {
|
||||
"type": "string"
|
||||
},
|
||||
"24x24": {
|
||||
"type": "string"
|
||||
},
|
||||
"32x32": {
|
||||
"type": "string"
|
||||
},
|
||||
"48x48": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"type": "string"
|
||||
},
|
||||
"created": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"jsdPublic": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"updateAuthor": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accountId": {
|
||||
"type": "string"
|
||||
},
|
||||
"accountType": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"avatarUrls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"16x16": {
|
||||
"type": "string"
|
||||
},
|
||||
"24x24": {
|
||||
"type": "string"
|
||||
},
|
||||
"32x32": {
|
||||
"type": "string"
|
||||
},
|
||||
"48x48": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"updated": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"maxResults": {
|
||||
"type": "integer"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"startAt": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"created": {
|
||||
"type": "string"
|
||||
},
|
||||
"creator": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accountId": {
|
||||
"type": "string"
|
||||
},
|
||||
"accountType": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"avatarUrls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"16x16": {
|
||||
"type": "string"
|
||||
},
|
||||
"24x24": {
|
||||
"type": "string"
|
||||
},
|
||||
"32x32": {
|
||||
"type": "string"
|
||||
},
|
||||
"48x48": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"issuetype": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatarId": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"entityId": {
|
||||
"type": "string"
|
||||
},
|
||||
"hierarchyLevel": {
|
||||
"type": "integer"
|
||||
},
|
||||
"iconUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"subtask": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"reporter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accountId": {
|
||||
"type": "string"
|
||||
},
|
||||
"accountType": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"avatarUrls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"16x16": {
|
||||
"type": "string"
|
||||
},
|
||||
"24x24": {
|
||||
"type": "string"
|
||||
},
|
||||
"32x32": {
|
||||
"type": "string"
|
||||
},
|
||||
"48x48": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"iconUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"statusCategory": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"colorName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"subtasks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fields": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"issuetype": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatarId": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"entityId": {
|
||||
"type": "string"
|
||||
},
|
||||
"hierarchyLevel": {
|
||||
"type": "integer"
|
||||
},
|
||||
"iconUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"subtask": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"priority": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"iconUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"iconUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"statusCategory": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"colorName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 9
|
||||
}
|
||||
@@ -0,0 +1,649 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"expand": {
|
||||
"type": "string"
|
||||
},
|
||||
"fields": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"aggregateprogress": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"progress": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"created": {
|
||||
"type": "string"
|
||||
},
|
||||
"creator": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accountId": {
|
||||
"type": "string"
|
||||
},
|
||||
"accountType": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"avatarUrls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"16x16": {
|
||||
"type": "string"
|
||||
},
|
||||
"24x24": {
|
||||
"type": "string"
|
||||
},
|
||||
"32x32": {
|
||||
"type": "string"
|
||||
},
|
||||
"48x48": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"customfield_10001": {
|
||||
"type": "null"
|
||||
},
|
||||
"customfield_10019": {
|
||||
"type": "string"
|
||||
},
|
||||
"customfield_10021": {
|
||||
"type": "null"
|
||||
},
|
||||
"fixVersions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"archived": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"released": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"releaseDate": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"issuelinks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"inwardIssue": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fields": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"issuetype": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatarId": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"entityId": {
|
||||
"type": "string"
|
||||
},
|
||||
"hierarchyLevel": {
|
||||
"type": "integer"
|
||||
},
|
||||
"iconUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"subtask": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"priority": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"iconUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"iconUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"statusCategory": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"colorName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"inward": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"outward": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"issuetype": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatarId": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"entityId": {
|
||||
"type": "string"
|
||||
},
|
||||
"hierarchyLevel": {
|
||||
"type": "integer"
|
||||
},
|
||||
"iconUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"subtask": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"priority": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"iconUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"progress": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"progress": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"project": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatarUrls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"16x16": {
|
||||
"type": "string"
|
||||
},
|
||||
"24x24": {
|
||||
"type": "string"
|
||||
},
|
||||
"32x32": {
|
||||
"type": "string"
|
||||
},
|
||||
"48x48": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"projectTypeKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"simplified": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"reporter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accountId": {
|
||||
"type": "string"
|
||||
},
|
||||
"accountType": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"avatarUrls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"16x16": {
|
||||
"type": "string"
|
||||
},
|
||||
"24x24": {
|
||||
"type": "string"
|
||||
},
|
||||
"32x32": {
|
||||
"type": "string"
|
||||
},
|
||||
"48x48": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"type": "null"
|
||||
},
|
||||
"status": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"iconUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"statusCategory": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"colorName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"statusCategory": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"colorName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"statuscategorychangedate": {
|
||||
"type": "string"
|
||||
},
|
||||
"subtasks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"fields": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"issuetype": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatarId": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"entityId": {
|
||||
"type": "string"
|
||||
},
|
||||
"hierarchyLevel": {
|
||||
"type": "integer"
|
||||
},
|
||||
"iconUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"subtask": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"priority": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"iconUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"iconUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"statusCategory": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"colorName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated": {
|
||||
"type": "string"
|
||||
},
|
||||
"votes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hasVoted": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"votes": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"watches": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"isWatching": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"watchCount": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"workratio": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 9
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hasScreen": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"isAvailable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isConditional": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isGlobal": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isInitial": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isLooped": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"to": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"iconUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"statusCategory": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"colorName": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"author": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accountId": {
|
||||
"type": "string"
|
||||
},
|
||||
"accountType": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"avatarUrls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"16x16": {
|
||||
"type": "string"
|
||||
},
|
||||
"24x24": {
|
||||
"type": "string"
|
||||
},
|
||||
"32x32": {
|
||||
"type": "string"
|
||||
},
|
||||
"48x48": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"created": {
|
||||
"type": "string"
|
||||
},
|
||||
"filename": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"mimeType": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"author": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accountId": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"avatarUrls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"16x16": {
|
||||
"type": "string"
|
||||
},
|
||||
"24x24": {
|
||||
"type": "string"
|
||||
},
|
||||
"32x32": {
|
||||
"type": "string"
|
||||
},
|
||||
"48x48": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"created": {
|
||||
"type": "string"
|
||||
},
|
||||
"filename": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"mimeType": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"author": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accountId": {
|
||||
"type": "string"
|
||||
},
|
||||
"accountType": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"avatarUrls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"16x16": {
|
||||
"type": "string"
|
||||
},
|
||||
"24x24": {
|
||||
"type": "string"
|
||||
},
|
||||
"32x32": {
|
||||
"type": "string"
|
||||
},
|
||||
"48x48": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"created": {
|
||||
"type": "string"
|
||||
},
|
||||
"filename": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"mimeType": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"author": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accountId": {
|
||||
"type": "string"
|
||||
},
|
||||
"accountType": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"avatarUrls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"16x16": {
|
||||
"type": "string"
|
||||
},
|
||||
"24x24": {
|
||||
"type": "string"
|
||||
},
|
||||
"32x32": {
|
||||
"type": "string"
|
||||
},
|
||||
"48x48": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"created": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"jsdPublic": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"updateAuthor": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accountId": {
|
||||
"type": "string"
|
||||
},
|
||||
"accountType": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"avatarUrls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"16x16": {
|
||||
"type": "string"
|
||||
},
|
||||
"24x24": {
|
||||
"type": "string"
|
||||
},
|
||||
"32x32": {
|
||||
"type": "string"
|
||||
},
|
||||
"48x48": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"updated": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"author": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accountId": {
|
||||
"type": "string"
|
||||
},
|
||||
"accountType": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"avatarUrls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"16x16": {
|
||||
"type": "string"
|
||||
},
|
||||
"24x24": {
|
||||
"type": "string"
|
||||
},
|
||||
"32x32": {
|
||||
"type": "string"
|
||||
},
|
||||
"48x48": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"attrs": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accessLevel": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"localId": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"created": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"jsdPublic": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"updateAuthor": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accountId": {
|
||||
"type": "string"
|
||||
},
|
||||
"accountType": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"avatarUrls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"16x16": {
|
||||
"type": "string"
|
||||
},
|
||||
"24x24": {
|
||||
"type": "string"
|
||||
},
|
||||
"32x32": {
|
||||
"type": "string"
|
||||
},
|
||||
"48x48": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"updated": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accountId": {
|
||||
"type": "string"
|
||||
},
|
||||
"accountType": {
|
||||
"type": "string"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"applicationRoles": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"avatarUrls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"16x16": {
|
||||
"type": "string"
|
||||
},
|
||||
"24x24": {
|
||||
"type": "string"
|
||||
},
|
||||
"32x32": {
|
||||
"type": "string"
|
||||
},
|
||||
"48x48": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"expand": {
|
||||
"type": "string"
|
||||
},
|
||||
"groups": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"groupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"locale": {
|
||||
"type": "string"
|
||||
},
|
||||
"self": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
import { type DeepMockProxy, mockDeep } from 'jest-mock-extended';
|
||||
import type { IDataObject, IExecuteFunctions } from 'n8n-workflow';
|
||||
|
||||
import { handlePagination, jiraSoftwareCloudApiRequestAllItems } from '../GenericFunctions';
|
||||
|
||||
describe('Jira -> GenericFunctions', () => {
|
||||
describe('jiraSoftwareCloudApiRequestAllItems', () => {
|
||||
let mockExecuteFunctions: DeepMockProxy<IExecuteFunctions>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
|
||||
mockExecuteFunctions.getNodeParameter.mockReturnValue('server');
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({ domain: 'jira.domain.com' });
|
||||
mockExecuteFunctions.helpers.requestWithAuthentication.mockImplementation(
|
||||
async function (_, options) {
|
||||
if (!options.qs?.startAt) {
|
||||
return {
|
||||
issues: [{ id: 1000 }, { id: 1001 }],
|
||||
startAt: 0,
|
||||
maxResults: 2,
|
||||
total: 3,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
issues: [{ id: 1002 }],
|
||||
startAt: 2,
|
||||
maxResults: 2,
|
||||
total: 3,
|
||||
};
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should get all items and not pass the body when the method is GET', async () => {
|
||||
const result = await jiraSoftwareCloudApiRequestAllItems.call(
|
||||
mockExecuteFunctions,
|
||||
'issues',
|
||||
'/api/2/search',
|
||||
'GET',
|
||||
);
|
||||
|
||||
expect(result).toEqual([{ id: 1000 }, { id: 1001 }, { id: 1002 }]);
|
||||
expect(mockExecuteFunctions.helpers.requestWithAuthentication).toBeCalledTimes(2);
|
||||
expect(mockExecuteFunctions.helpers.requestWithAuthentication).toHaveBeenCalledWith(
|
||||
'jiraSoftwareServerApi',
|
||||
expect.not.objectContaining({
|
||||
body: expect.anything(),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handlePagination', () => {
|
||||
it('should initialize offset pagination parameters with GET when responseData is not provided', () => {
|
||||
const body: IDataObject = {};
|
||||
const query: IDataObject = {};
|
||||
|
||||
const result = handlePagination('GET', body, query, 'offset');
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(query.startAt).toBe(0);
|
||||
expect(query.maxResults).toBe(100);
|
||||
expect(body).toEqual({});
|
||||
});
|
||||
|
||||
it('should initialize offset pagination parameters with POST when responseData is not provided', () => {
|
||||
const body: IDataObject = {};
|
||||
const query: IDataObject = {};
|
||||
|
||||
const result = handlePagination('POST', body, query, 'offset');
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(body.startAt).toBe(0);
|
||||
expect(body.maxResults).toBe(100);
|
||||
expect(query).toEqual({});
|
||||
});
|
||||
|
||||
it('should initialize token pagination parameters with GET when responseData is not provided', () => {
|
||||
const body: IDataObject = {};
|
||||
const query: IDataObject = {};
|
||||
|
||||
const result = handlePagination('GET', body, query, 'token');
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(query.maxResults).toBe(100);
|
||||
expect(body).toEqual({});
|
||||
});
|
||||
|
||||
it('should initialize token pagination parameters with POST when responseData is not provided', () => {
|
||||
const body: IDataObject = {};
|
||||
const query: IDataObject = {};
|
||||
|
||||
const result = handlePagination('POST', body, query, 'token');
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(query).toEqual({});
|
||||
expect(body.maxResults).toBe(100);
|
||||
});
|
||||
|
||||
it('should handle offset pagination with GET and more pages available', () => {
|
||||
const body: IDataObject = {};
|
||||
const query: IDataObject = {};
|
||||
const responseData = {
|
||||
startAt: 0,
|
||||
maxResults: 100,
|
||||
total: 250,
|
||||
};
|
||||
|
||||
const result = handlePagination('GET', body, query, 'offset', responseData);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(query.startAt).toBe(100);
|
||||
expect(body).toEqual({});
|
||||
});
|
||||
|
||||
it('should handle offset pagination with POST and more pages available', () => {
|
||||
const body: IDataObject = {};
|
||||
const query: IDataObject = {};
|
||||
const responseData = {
|
||||
startAt: 0,
|
||||
maxResults: 100,
|
||||
total: 250,
|
||||
};
|
||||
|
||||
const result = handlePagination('POST', body, query, 'offset', responseData);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(body.startAt).toBe(100);
|
||||
expect(query).toEqual({});
|
||||
});
|
||||
|
||||
it('should handle offset pagination with GET and no more pages available', () => {
|
||||
const body: IDataObject = {};
|
||||
const query: IDataObject = {};
|
||||
const responseData = {
|
||||
startAt: 200,
|
||||
maxResults: 100,
|
||||
total: 250,
|
||||
};
|
||||
|
||||
const result = handlePagination('GET', body, query, 'offset', responseData);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(query.startAt).toBe(300);
|
||||
expect(body).toEqual({});
|
||||
});
|
||||
|
||||
it('should handle offset pagination with POST and no more pages available', () => {
|
||||
const body: IDataObject = {};
|
||||
const query: IDataObject = {};
|
||||
const responseData = {
|
||||
startAt: 200,
|
||||
maxResults: 100,
|
||||
total: 250,
|
||||
};
|
||||
|
||||
const result = handlePagination('POST', body, query, 'offset', responseData);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(body.startAt).toBe(300);
|
||||
expect(query).toEqual({});
|
||||
});
|
||||
|
||||
it('should handle token pagination with GET and more pages available', () => {
|
||||
const body: IDataObject = {};
|
||||
const query: IDataObject = {};
|
||||
const responseData = {
|
||||
nextPageToken: 'someToken123',
|
||||
};
|
||||
|
||||
const result = handlePagination('GET', body, query, 'token', responseData);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(query.nextPageToken).toBe('someToken123');
|
||||
expect(body).toEqual({});
|
||||
});
|
||||
|
||||
it('should handle token pagination with POST and more pages available', () => {
|
||||
const body: IDataObject = {};
|
||||
const query: IDataObject = {};
|
||||
const responseData = {
|
||||
nextPageToken: 'someToken123',
|
||||
};
|
||||
|
||||
const result = handlePagination('POST', body, query, 'token', responseData);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(body.nextPageToken).toBe('someToken123');
|
||||
expect(query).toEqual({});
|
||||
});
|
||||
|
||||
it('should handle token pagination with GET and no more pages available', () => {
|
||||
const body: IDataObject = {};
|
||||
const query: IDataObject = {};
|
||||
const responseData = {
|
||||
nextPageToken: '',
|
||||
};
|
||||
|
||||
const result = handlePagination('GET', body, query, 'token', responseData);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(query.nextPageToken).toBe('');
|
||||
expect(body).toEqual({});
|
||||
});
|
||||
|
||||
it('should handle token pagination with POST and no more pages available', () => {
|
||||
const body: IDataObject = {};
|
||||
const query: IDataObject = {};
|
||||
const responseData = {
|
||||
nextPageToken: '',
|
||||
};
|
||||
|
||||
const result = handlePagination('POST', body, query, 'token', responseData);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(body.nextPageToken).toBe('');
|
||||
expect(query).toEqual({});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,248 @@
|
||||
import type { DeepMockProxy } from 'jest-mock-extended';
|
||||
import { mockDeep } from 'jest-mock-extended';
|
||||
import type { IExecuteFunctions } from 'n8n-workflow';
|
||||
|
||||
import * as GenericFunctions from '../GenericFunctions';
|
||||
import { Jira } from '../Jira.node';
|
||||
|
||||
jest.mock('../GenericFunctions', () => ({
|
||||
jiraSoftwareCloudApiRequest: jest.fn().mockResolvedValue({ issues: [] }),
|
||||
jiraSoftwareCloudApiRequestAllItems: jest.fn().mockResolvedValue([]),
|
||||
}));
|
||||
|
||||
const jiraSoftwareCloudApiRequestMock = GenericFunctions.jiraSoftwareCloudApiRequest as jest.Mock;
|
||||
const jiraSoftwareCloudApiRequestAllItems =
|
||||
GenericFunctions.jiraSoftwareCloudApiRequestAllItems as jest.Mock;
|
||||
|
||||
describe('Jira Node', () => {
|
||||
let jiraNode: Jira;
|
||||
let executeFunctionsMock: DeepMockProxy<IExecuteFunctions>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jiraNode = new Jira();
|
||||
executeFunctionsMock = mockDeep<IExecuteFunctions>();
|
||||
executeFunctionsMock.getInputData.mockReturnValue([{ json: {} }]);
|
||||
executeFunctionsMock.helpers.returnJsonArray.mockReturnValue([]);
|
||||
executeFunctionsMock.helpers.constructExecutionMetaData.mockReturnValue([]);
|
||||
});
|
||||
|
||||
describe('issue getAll', () => {
|
||||
it('should set default fields to "*navigable" when not provided', async () => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((parameterName: string) => {
|
||||
switch (parameterName) {
|
||||
case 'resource':
|
||||
return 'issue';
|
||||
case 'operation':
|
||||
return 'getAll';
|
||||
case 'jiraVersion':
|
||||
return 'cloud';
|
||||
case 'returnAll':
|
||||
return false;
|
||||
case 'limit':
|
||||
return 10;
|
||||
case 'options':
|
||||
return { fields: undefined };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
await jiraNode.execute.call(executeFunctionsMock);
|
||||
|
||||
expect(jiraSoftwareCloudApiRequestMock).toHaveBeenCalledWith(
|
||||
'/api/2/search/jql',
|
||||
'POST',
|
||||
expect.objectContaining({
|
||||
fields: ['*navigable'],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should set default JQL filter to "created >= 1970-01-01" when not provided', async () => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((parameterName: string) => {
|
||||
switch (parameterName) {
|
||||
case 'resource':
|
||||
return 'issue';
|
||||
case 'operation':
|
||||
return 'getAll';
|
||||
case 'jiraVersion':
|
||||
return 'cloud';
|
||||
case 'returnAll':
|
||||
return false;
|
||||
case 'limit':
|
||||
return 10;
|
||||
case 'options':
|
||||
return { jql: undefined };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
await jiraNode.execute.call(executeFunctionsMock);
|
||||
|
||||
expect(jiraSoftwareCloudApiRequestMock).toHaveBeenCalledWith(
|
||||
'/api/2/search/jql',
|
||||
'POST',
|
||||
expect.objectContaining({
|
||||
jql: 'created >= "1970-01-01"',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should use custom fields when provided', async () => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((parameterName: string) => {
|
||||
switch (parameterName) {
|
||||
case 'resource':
|
||||
return 'issue';
|
||||
case 'operation':
|
||||
return 'getAll';
|
||||
case 'jiraVersion':
|
||||
return 'cloud';
|
||||
case 'returnAll':
|
||||
return false;
|
||||
case 'limit':
|
||||
return 10;
|
||||
case 'options':
|
||||
return { fields: 'summary,description' };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
await jiraNode.execute.call(executeFunctionsMock);
|
||||
|
||||
expect(jiraSoftwareCloudApiRequestMock).toHaveBeenCalledWith(
|
||||
'/api/2/search/jql',
|
||||
'POST',
|
||||
expect.objectContaining({
|
||||
fields: ['summary', 'description'],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should use custom JQL filter when provided', async () => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((parameterName: string) => {
|
||||
switch (parameterName) {
|
||||
case 'resource':
|
||||
return 'issue';
|
||||
case 'operation':
|
||||
return 'getAll';
|
||||
case 'jiraVersion':
|
||||
return 'cloud';
|
||||
case 'returnAll':
|
||||
return false;
|
||||
case 'limit':
|
||||
return 10;
|
||||
case 'options':
|
||||
return { jql: 'project = TEST' };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
await jiraNode.execute.call(executeFunctionsMock);
|
||||
|
||||
expect(jiraSoftwareCloudApiRequestMock).toHaveBeenCalledWith(
|
||||
'/api/2/search/jql',
|
||||
'POST',
|
||||
expect.objectContaining({
|
||||
jql: 'project = TEST',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should call new endpoint for the cloud version with return all = true', async () => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((parameterName: string) => {
|
||||
switch (parameterName) {
|
||||
case 'resource':
|
||||
return 'issue';
|
||||
case 'operation':
|
||||
return 'getAll';
|
||||
case 'jiraVersion':
|
||||
return 'cloud';
|
||||
case 'returnAll':
|
||||
return true;
|
||||
case 'options':
|
||||
return {};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
await jiraNode.execute.call(executeFunctionsMock);
|
||||
|
||||
expect(jiraSoftwareCloudApiRequestAllItems).toHaveBeenCalledWith(
|
||||
'issues',
|
||||
'/api/2/search/jql',
|
||||
'POST',
|
||||
expect.anything(),
|
||||
{},
|
||||
'token',
|
||||
);
|
||||
});
|
||||
|
||||
it.each([['server'], ['serverPat']])(
|
||||
'should call old endpoint for the self-hosted version with return all = false',
|
||||
async (jiraVersion: string) => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((parameterName: string) => {
|
||||
switch (parameterName) {
|
||||
case 'resource':
|
||||
return 'issue';
|
||||
case 'operation':
|
||||
return 'getAll';
|
||||
case 'jiraVersion':
|
||||
return jiraVersion;
|
||||
case 'returnAll':
|
||||
return false;
|
||||
case 'limit':
|
||||
return 10;
|
||||
case 'options':
|
||||
return {};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
await jiraNode.execute.call(executeFunctionsMock);
|
||||
|
||||
expect(jiraSoftwareCloudApiRequestMock).toHaveBeenCalledWith(
|
||||
'/api/2/search',
|
||||
'POST',
|
||||
expect.anything(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([['server'], ['serverPat']])(
|
||||
'should call old endpoint for the self-hosted version with return all = true',
|
||||
async (jiraVersion: string) => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((parameterName: string) => {
|
||||
switch (parameterName) {
|
||||
case 'resource':
|
||||
return 'issue';
|
||||
case 'operation':
|
||||
return 'getAll';
|
||||
case 'jiraVersion':
|
||||
return jiraVersion;
|
||||
case 'returnAll':
|
||||
return true;
|
||||
case 'options':
|
||||
return {};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
await jiraNode.execute.call(executeFunctionsMock);
|
||||
|
||||
expect(jiraSoftwareCloudApiRequestAllItems).toHaveBeenCalledWith(
|
||||
'issues',
|
||||
'/api/2/search',
|
||||
'POST',
|
||||
expect.anything(),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,263 @@
|
||||
import { mock, mockDeep } from 'jest-mock-extended';
|
||||
import type {
|
||||
ICredentialDataDecryptedObject,
|
||||
IDataObject,
|
||||
IHookFunctions,
|
||||
INode,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { testWebhookTriggerNode } from '@test/nodes/TriggerHelpers';
|
||||
|
||||
import { JiraTrigger } from '../JiraTrigger.node';
|
||||
|
||||
describe('JiraTrigger', () => {
|
||||
describe('Webhook lifecycle', () => {
|
||||
let staticData: IDataObject;
|
||||
|
||||
beforeEach(() => {
|
||||
staticData = {};
|
||||
});
|
||||
|
||||
function mockHookFunctions(
|
||||
mockRequest: IHookFunctions['helpers']['requestWithAuthentication'],
|
||||
) {
|
||||
const baseUrl = 'https://jira.local';
|
||||
const credential = {
|
||||
email: 'test@n8n.io',
|
||||
password: 'secret',
|
||||
domain: baseUrl,
|
||||
};
|
||||
|
||||
return mockDeep<IHookFunctions>({
|
||||
getWorkflowStaticData: () => staticData,
|
||||
getNode: jest.fn(() => mock<INode>({ typeVersion: 1 })),
|
||||
getNodeWebhookUrl: jest.fn(() => 'https://n8n.local/webhook/id'),
|
||||
getNodeParameter: jest.fn((param: string) => {
|
||||
if (param === 'events') return ['jira:issue_created'];
|
||||
return {};
|
||||
}),
|
||||
getCredentials: async <T extends object = ICredentialDataDecryptedObject>() =>
|
||||
credential as T,
|
||||
helpers: {
|
||||
requestWithAuthentication: mockRequest,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
test('should register a webhook subscription on Jira 10', async () => {
|
||||
const trigger = new JiraTrigger();
|
||||
|
||||
const mockExistsRequest = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ versionNumbers: [10, 0, 1] })
|
||||
.mockResolvedValueOnce([]);
|
||||
|
||||
const exists = await trigger.webhookMethods.default?.checkExists.call(
|
||||
mockHookFunctions(mockExistsRequest),
|
||||
);
|
||||
|
||||
expect(mockExistsRequest).toHaveBeenCalledTimes(2);
|
||||
expect(mockExistsRequest).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({ uri: 'https://jira.local/rest/api/2/serverInfo' }),
|
||||
);
|
||||
expect(mockExistsRequest).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({ uri: 'https://jira.local/rest/jira-webhook/1.0/webhooks' }),
|
||||
);
|
||||
expect(staticData.endpoint).toBe('/jira-webhook/1.0/webhooks');
|
||||
expect(exists).toBe(false);
|
||||
|
||||
const mockCreateRequest = jest.fn().mockResolvedValueOnce({ id: 1 });
|
||||
|
||||
const created = await trigger.webhookMethods.default?.create.call(
|
||||
mockHookFunctions(mockCreateRequest),
|
||||
);
|
||||
|
||||
expect(mockCreateRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockCreateRequest).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
uri: 'https://jira.local/rest/jira-webhook/1.0/webhooks',
|
||||
body: expect.objectContaining({
|
||||
events: ['jira:issue_created'],
|
||||
excludeBody: false,
|
||||
filters: {},
|
||||
name: 'n8n-webhook:https://n8n.local/webhook/id',
|
||||
url: 'https://n8n.local/webhook/id',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(created).toBe(true);
|
||||
|
||||
const mockDeleteRequest = jest.fn().mockResolvedValueOnce({});
|
||||
const deleted = await trigger.webhookMethods.default?.delete.call(
|
||||
mockHookFunctions(mockDeleteRequest),
|
||||
);
|
||||
|
||||
expect(deleted).toBe(true);
|
||||
expect(mockDeleteRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockDeleteRequest).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
method: 'DELETE',
|
||||
uri: 'https://jira.local/rest/jira-webhook/1.0/webhooks/1',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('should register a webhook subscription on Jira 9', async () => {
|
||||
const trigger = new JiraTrigger();
|
||||
|
||||
const mockExistsRequest = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ versionNumbers: [9, 0, 1] })
|
||||
.mockResolvedValueOnce([]);
|
||||
|
||||
const exists = await trigger.webhookMethods.default?.checkExists.call(
|
||||
mockHookFunctions(mockExistsRequest),
|
||||
);
|
||||
|
||||
expect(mockExistsRequest).toHaveBeenCalledTimes(2);
|
||||
expect(mockExistsRequest).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({ uri: 'https://jira.local/rest/api/2/serverInfo' }),
|
||||
);
|
||||
expect(mockExistsRequest).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({ uri: 'https://jira.local/rest/webhooks/1.0/webhook' }),
|
||||
);
|
||||
expect(staticData.endpoint).toBe('/webhooks/1.0/webhook');
|
||||
expect(exists).toBe(false);
|
||||
|
||||
const mockCreateRequest = jest.fn().mockResolvedValueOnce({ id: 1 });
|
||||
|
||||
const created = await trigger.webhookMethods.default?.create.call(
|
||||
mockHookFunctions(mockCreateRequest),
|
||||
);
|
||||
|
||||
expect(mockCreateRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockCreateRequest).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
uri: 'https://jira.local/rest/webhooks/1.0/webhook',
|
||||
body: expect.objectContaining({
|
||||
events: ['jira:issue_created'],
|
||||
excludeBody: false,
|
||||
filters: {},
|
||||
name: 'n8n-webhook:https://n8n.local/webhook/id',
|
||||
url: 'https://n8n.local/webhook/id',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(created).toBe(true);
|
||||
|
||||
const mockDeleteRequest = jest.fn().mockResolvedValueOnce({});
|
||||
const deleted = await trigger.webhookMethods.default?.delete.call(
|
||||
mockHookFunctions(mockDeleteRequest),
|
||||
);
|
||||
|
||||
expect(deleted).toBe(true);
|
||||
expect(mockDeleteRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockDeleteRequest).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
method: 'DELETE',
|
||||
uri: 'https://jira.local/rest/webhooks/1.0/webhook/1',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('should register a webhook subscription on Jira Cloud', async () => {
|
||||
const trigger = new JiraTrigger();
|
||||
|
||||
const mockExistsRequest = jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ deploymentType: 'Cloud', versionNumbers: [1000, 0, 1] })
|
||||
.mockResolvedValueOnce([]);
|
||||
|
||||
const exists = await trigger.webhookMethods.default?.checkExists.call(
|
||||
mockHookFunctions(mockExistsRequest),
|
||||
);
|
||||
|
||||
expect(mockExistsRequest).toHaveBeenCalledTimes(2);
|
||||
expect(mockExistsRequest).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({ uri: 'https://jira.local/rest/api/2/serverInfo' }),
|
||||
);
|
||||
expect(mockExistsRequest).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({ uri: 'https://jira.local/rest/webhooks/1.0/webhook' }),
|
||||
);
|
||||
expect(staticData.endpoint).toBe('/webhooks/1.0/webhook');
|
||||
expect(exists).toBe(false);
|
||||
|
||||
const mockCreateRequest = jest.fn().mockResolvedValueOnce({ id: 1 });
|
||||
|
||||
const created = await trigger.webhookMethods.default?.create.call(
|
||||
mockHookFunctions(mockCreateRequest),
|
||||
);
|
||||
|
||||
expect(mockCreateRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockCreateRequest).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
uri: 'https://jira.local/rest/webhooks/1.0/webhook',
|
||||
body: expect.objectContaining({
|
||||
events: ['jira:issue_created'],
|
||||
excludeBody: false,
|
||||
filters: {},
|
||||
name: 'n8n-webhook:https://n8n.local/webhook/id',
|
||||
url: 'https://n8n.local/webhook/id',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(created).toBe(true);
|
||||
|
||||
const mockDeleteRequest = jest.fn().mockResolvedValueOnce({});
|
||||
const deleted = await trigger.webhookMethods.default?.delete.call(
|
||||
mockHookFunctions(mockDeleteRequest),
|
||||
);
|
||||
|
||||
expect(deleted).toBe(true);
|
||||
expect(mockDeleteRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockDeleteRequest).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
method: 'DELETE',
|
||||
uri: 'https://jira.local/rest/webhooks/1.0/webhook/1',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Webhook', () => {
|
||||
test('should receive a webhook event', async () => {
|
||||
const event = {
|
||||
timestamp: 1743524005044,
|
||||
webhookEvent: 'jira:issue_created',
|
||||
issue_event_type_name: 'issue_created',
|
||||
user: {
|
||||
self: 'http://localhost:8080/rest/api/2/user?key=JIRAUSER10000',
|
||||
name: 'elias',
|
||||
key: 'JIRAUSER10000',
|
||||
emailAddress: 'elias@meire.dev',
|
||||
displayName: 'Test',
|
||||
},
|
||||
issue: {
|
||||
id: '10018',
|
||||
self: 'http://localhost:8080/rest/api/2/issue/10018',
|
||||
key: 'TEST-19',
|
||||
},
|
||||
};
|
||||
const { responseData } = await testWebhookTriggerNode(JiraTrigger, {
|
||||
bodyData: event,
|
||||
});
|
||||
|
||||
expect(responseData).toEqual({ workflowData: [[{ json: event }]] });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,126 @@
|
||||
import type { MockProxy } from 'jest-mock-extended';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { IHttpRequestMethods, ILoadOptionsFunctions } from 'n8n-workflow';
|
||||
|
||||
import { Jira } from '../Jira.node';
|
||||
|
||||
const ISSUE_KEY = 'KEY-1';
|
||||
|
||||
jest.mock('../GenericFunctions', () => {
|
||||
const originalModule = jest.requireActual('../GenericFunctions');
|
||||
return {
|
||||
...originalModule,
|
||||
jiraSoftwareCloudApiRequest: jest.fn(async function (
|
||||
endpoint: string,
|
||||
method: IHttpRequestMethods,
|
||||
) {
|
||||
if (method === 'GET' && endpoint === `/api/2/issue/${ISSUE_KEY}`) {
|
||||
return {
|
||||
id: 10000,
|
||||
fields: {
|
||||
project: {
|
||||
id: 10001,
|
||||
},
|
||||
issuetype: {
|
||||
id: 10002,
|
||||
},
|
||||
},
|
||||
};
|
||||
} else if (method === 'GET' && endpoint === '/api/2/issue/10000/editmeta') {
|
||||
return {
|
||||
fields: {
|
||||
customfield_123: {
|
||||
name: 'Field 123',
|
||||
},
|
||||
customfield_456: {
|
||||
name: 'Field 456',
|
||||
},
|
||||
},
|
||||
};
|
||||
} else if (
|
||||
method === 'GET' &&
|
||||
endpoint ===
|
||||
'/api/2/issue/createmeta?projectIds=10001&issueTypeIds=10002&expand=projects.issuetypes.fields'
|
||||
) {
|
||||
return {
|
||||
projects: [
|
||||
{
|
||||
id: 10001,
|
||||
issuetypes: [
|
||||
{
|
||||
id: 10002,
|
||||
fields: {
|
||||
customfield_abc: {
|
||||
name: 'Field ABC',
|
||||
schema: { customId: 'customfield_abc' },
|
||||
fieldId: 'customfield_abc',
|
||||
},
|
||||
customfield_def: {
|
||||
name: 'Field DEF',
|
||||
schema: { customId: 'customfield_def' },
|
||||
fieldId: 'customfield_def',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Jira Node, methods', () => {
|
||||
let jira: Jira;
|
||||
let loadOptionsFunctions: MockProxy<ILoadOptionsFunctions>;
|
||||
|
||||
beforeEach(() => {
|
||||
jira = new Jira();
|
||||
loadOptionsFunctions = mock<ILoadOptionsFunctions>();
|
||||
});
|
||||
|
||||
describe('listSearch.getCustomFields', () => {
|
||||
it('should call correct endpoint and return custom fields for server version', async () => {
|
||||
loadOptionsFunctions.getCurrentNodeParameter.mockReturnValueOnce('update');
|
||||
loadOptionsFunctions.getNodeParameter.mockReturnValue('server');
|
||||
loadOptionsFunctions.getCurrentNodeParameter.mockReturnValueOnce(ISSUE_KEY);
|
||||
|
||||
const { results } = await jira.methods.listSearch.getCustomFields.call(
|
||||
loadOptionsFunctions as ILoadOptionsFunctions,
|
||||
);
|
||||
|
||||
expect(results).toEqual([
|
||||
{
|
||||
name: 'Field 123',
|
||||
value: 'customfield_123',
|
||||
},
|
||||
{
|
||||
name: 'Field 456',
|
||||
value: 'customfield_456',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should call correct endpoint and return custom fields for cloud version', async () => {
|
||||
loadOptionsFunctions.getCurrentNodeParameter.mockReturnValueOnce('update');
|
||||
loadOptionsFunctions.getNodeParameter.mockReturnValue('cloud');
|
||||
loadOptionsFunctions.getCurrentNodeParameter.mockReturnValueOnce(ISSUE_KEY);
|
||||
|
||||
const { results } = await jira.methods.listSearch.getCustomFields.call(
|
||||
loadOptionsFunctions as ILoadOptionsFunctions,
|
||||
);
|
||||
|
||||
expect(results).toEqual([
|
||||
{
|
||||
name: 'Field ABC',
|
||||
value: 'customfield_abc',
|
||||
},
|
||||
{
|
||||
name: 'Field DEF',
|
||||
value: 'customfield_def',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#fff" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 68.25 71.25"><use xlink:href="#a" x="3.125" y="3.125"/><defs><linearGradient id="b" x1="91.9%" x2="28.49%" y1="40.22%" y2="81.63%"><stop offset="18%" stop-color="#0052cc"/><stop offset="100%" stop-color="#2684ff"/></linearGradient><linearGradient id="c" x1="8.7%" x2="72.26%" y1="59.17%" y2="17.99%"><stop offset="18%" stop-color="#0052cc"/><stop offset="100%" stop-color="#2684ff"/></linearGradient></defs><symbol id="a" overflow="visible"><g fill-rule="nonzero" stroke="none"><path fill="#2684ff" d="M61.161 30.211 30.95 0 .74 30.211a2.54 2.54 0 0 0 0 3.581l30.211 30.21 30.211-30.21a2.54 2.54 0 0 0 0-3.581zM30.95 41.46l-9.462-9.462 9.462-9.462 9.462 9.462z"/><path fill="url(#b)" d="M30.95 22.599C24.755 16.405 24.724 6.37 30.881.138L10.114 20.774l11.268 11.268z"/><path fill="url(#c)" d="M40.437 31.973 30.95 41.46a15.93 15.93 0 0 1 0 22.536l20.749-20.749z"/></g></symbol></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,25 @@
|
||||
export type JiraWebhook = {
|
||||
id: number;
|
||||
name: string;
|
||||
createdDate: number;
|
||||
updatedDate: number;
|
||||
events: string[];
|
||||
configuration: {};
|
||||
url: string;
|
||||
active: boolean;
|
||||
scopeType: string;
|
||||
sslVerificationRequired: boolean;
|
||||
self?: string; // Only available for version < 10
|
||||
};
|
||||
export type JiraServerInfo = {
|
||||
baseUrl: string;
|
||||
version: string;
|
||||
versionNumbers: number[];
|
||||
deploymentType?: 'Cloud' | 'Server';
|
||||
buildNumber: number;
|
||||
buildDate: string;
|
||||
databaseBuildNumber: number;
|
||||
serverTime: string;
|
||||
scmInfo: string;
|
||||
serverTitle: string;
|
||||
};
|
||||
Reference in New Issue
Block a user