Sentry.io integration (#728)

* 🚧 setup

- Added everything, need to test and add icon

* Add icon

* 🚧 Node colour change

* 🚧 Fixed Descriptions

* ✔️ Tested, fixed up properties

*  Fixed issue of issue

*  Added create option for team & organization

*  Improvements

*  Fixed OAuth2 credentials scope

*  Adjusted descriptions, added loadOptions for organizations/projects, small fixes

*  Added Create Release, interfaces

*  Improvements to SentryIO-Node

Co-authored-by: ricardo <ricardoespinoza105@gmail.com>
Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
Rupenieks 2020-09-07 17:56:14 +02:00 committed by GitHub
parent 1479ce47e6
commit 42bbe3006a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 2374 additions and 1 deletions

View file

@ -0,0 +1,17 @@
import {
ICredentialType,
NodePropertyTypes,
} from 'n8n-workflow';
export class SentryIoApi implements ICredentialType {
name = 'sentryIoApi';
displayName = 'Sentry.io API';
properties = [
{
displayName: 'Token',
name: 'token',
type: 'string' as NodePropertyTypes,
default: '',
},
];
}

View file

@ -0,0 +1,46 @@
import {
ICredentialType,
NodePropertyTypes,
} from 'n8n-workflow';
export class SentryIoOAuth2Api implements ICredentialType {
name = 'sentryIoOAuth2Api';
extends = [
'oAuth2Api',
];
displayName = 'Sentry.io OAuth2 API';
properties = [
{
displayName: 'Authorization URL',
name: 'authUrl',
type: 'hidden' as NodePropertyTypes,
default: 'https://sentry.io/oauth/authorize/',
required: true,
},
{
displayName: 'Access Token URL',
name: 'accessTokenUrl',
type: 'hidden' as NodePropertyTypes,
default: 'https://sentry.io/oauth/token/',
required: true,
},
{
displayName: 'Scope',
name: 'scope',
type: 'hidden' as NodePropertyTypes,
default: 'event:admin event:read org:read project:read project:releases team:read event:write org:admin project:write team:write project:admin team:admin',
},
{
displayName: 'Auth URI Query Parameters',
name: 'authQueryParameters',
type: 'hidden' as NodePropertyTypes,
default: '',
},
{
displayName: 'Authentication',
name: 'authentication',
type: 'hidden' as NodePropertyTypes,
default: 'body',
},
];
}

View file

@ -14,7 +14,6 @@ import {
getFileSha, getFileSha,
} from './GenericFunctions'; } from './GenericFunctions';
export class Github implements INodeType { export class Github implements INodeType {
description: INodeTypeDescription = { description: INodeTypeDescription = {
displayName: 'GitHub', displayName: 'GitHub',

View file

@ -0,0 +1,204 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const eventOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'event',
],
},
},
options: [
{
name: 'Get',
value: 'get',
description: 'Get event by ID',
},
{
name: 'Get All',
value: 'getAll',
description: 'Get all events',
}
],
default: 'get',
description: 'The operation to perform',
},
] as INodeProperties[];
export const eventFields = [
/* -------------------------------------------------------------------------- */
/* event:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Organization Slug',
name: 'organizationSlug',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getOrganizations',
},
default: '',
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'getAll',
],
},
},
required: true,
description: 'The slug of the organization the events belong to',
},
{
displayName: 'Project Slug',
name: 'projectSlug',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getProjects',
loadOptionsDependsOn: [
'organizationSlug',
],
},
default: '',
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'getAll',
],
},
},
required: true,
description: 'The slug of the project the events belong to',
},
{
displayName: 'Full',
name: 'full',
type: 'boolean',
default: true,
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'getAll',
],
},
},
description: 'If this is set to true, then the event payload will include the full event body, including the stack trace',
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'event',
],
},
},
default: false,
description: 'If all results should be returned or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'event',
],
returnAll: [
false,
],
},
},
typeOptions: {
minValue: 1,
maxValue: 500,
},
default: 100,
description: 'How many results to return',
},
/* -------------------------------------------------------------------------- */
/* event:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'Organization Slug',
name: 'organizationSlug',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getOrganizations',
},
default: '',
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'get',
],
},
},
required: true,
description: 'The slug of the organization the events belong to',
},
{
displayName: 'Project Slug',
name: 'projectSlug',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getProjects',
},
default: '',
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'get',
],
},
},
required: true,
description: 'The slug of the project the events belong to',
},
{
displayName: 'Event ID',
name: 'eventId',
type: 'string',
default: '',
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'get',
],
},
},
required: true,
description: 'The id of the event to retrieve (either the numeric primary-key or the hexadecimal id as reported by the raven client).',
},
] as INodeProperties[];

View file

@ -0,0 +1,108 @@
import {
OptionsWithUri
} from 'request';
import {
IExecuteFunctions,
IExecuteSingleFunctions,
IHookFunctions,
ILoadOptionsFunctions,
IWebhookFunctions,
} from 'n8n-core';
import {
IDataObject,
} from 'n8n-workflow';
export async function sentryIoApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const authentication = this.getNodeParameter('authentication', 0);
const options: OptionsWithUri = {
headers: {},
method,
qs,
body,
uri: uri ||`https://sentry.io${resource}`,
json: true
};
if (!Object.keys(body).length) {
delete options.body;
}
if (Object.keys(option).length !== 0) {
Object.assign(options, option);
}
if (options.qs.limit) {
delete options.qs.limit;
}
try {
if (authentication === 'accessToken') {
const credentials = this.getCredentials('sentryIoApi');
options.headers = {
Authorization: `Bearer ${credentials?.token}`,
};
console.log('options');
console.log(options);
//@ts-ignore
return this.helpers.request(options);
} else {
return await this.helpers.requestOAuth2!.call(this, 'sentryIoOAuth2Api', options);
}
} catch (error) {
throw new Error(`Sentry.io Error: ${error}`);
}
}
export async function sentryApiRequestAllItems(this: IHookFunctions | IExecuteFunctions| ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const returnData: IDataObject[] = [];
let responseData;
let link;
let uri: string | undefined;
do {
responseData = await sentryIoApiRequest.call(this, method, resource, body, query, uri, { resolveWithFullResponse: true });
link = responseData.headers.link;
uri = getNext(link);
returnData.push.apply(returnData, responseData.body);
if (query.limit && (query.limit >= returnData.length)) {
return;
}
} while (
hasMore(link)
);
return returnData;
}
function getNext(link: string) {
if (link === undefined) {
return;
}
const next = link.split(',')[1];
if (next.includes('rel="next"')) {
return next.split(';')[0].replace('<', '').replace('>','').trim();
}
}
function hasMore(link: string) {
if (link === undefined) {
return;
}
const next = link.split(',')[1];
if (next.includes('rel="next"')) {
return next.includes('results="true"');
}
}

View file

@ -0,0 +1,20 @@
export interface ICommit {
id: string;
repository?: string;
message?: string;
patch_set?: IPatchSet[];
author_name?: string;
author_email?: string;
timestamp?: Date;
}
export interface IPatchSet {
path: string;
type: string;
}
export interface IRef {
commit: string;
repository: string;
previousCommit?: string;
}

View file

@ -0,0 +1,300 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const issueOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'issue',
],
},
},
options: [
{
name: 'Delete',
value: 'delete',
description: 'Delete an issue',
},
{
name: 'Get',
value: 'get',
description: 'Get issue by ID',
},
{
name: 'Get All',
value: 'getAll',
description: 'Get all issues',
},
{
name: 'Update',
value: 'update',
description: 'Update an issue',
},
],
default: 'get',
description: 'The operation to perform',
},
] as INodeProperties[];
export const issueFields = [
/* -------------------------------------------------------------------------- */
/* issue:get/delete */
/* -------------------------------------------------------------------------- */
{
displayName: 'Issue ID',
name: 'issueId',
type: 'string',
default: '',
placeholder: '1234',
displayOptions: {
show: {
resource: [
'issue',
],
operation: [
'get',
'delete',
],
},
},
required: true,
description: 'ID of issue to get',
},
/* -------------------------------------------------------------------------- */
/* issue:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Organization Slug',
name: 'organizationSlug',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getOrganizations',
},
default: '',
displayOptions: {
show: {
resource: [
'issue',
],
operation: [
'getAll',
],
},
},
required: true,
description: 'The slug of the organization the issues belong to',
},
{
displayName: 'Project Slug',
name: 'projectSlug',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getProjects',
loadOptionsDependsOn: [
'organizationSlug',
],
},
default: '',
displayOptions: {
show: {
resource: [
'issue',
],
operation: [
'getAll',
],
},
},
required: true,
description: 'The slug of the project the issues belong to',
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'issue',
],
},
},
default: false,
description: 'If all results should be returned or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'issue',
],
returnAll: [
false,
],
},
},
typeOptions: {
minValue: 1,
maxValue: 500,
},
default: 100,
description: 'How many results to return',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'issue',
],
operation: [
'getAll',
],
},
},
options: [
{
displayName: 'Stats Period',
name: 'statsPeriod',
type: 'options',
default: '',
description: 'Time period of stats',
options: [
{
name: '14 Days',
value: '14d'
},
{
name: '24 Hours',
value: '24h'
},
]
},
{
displayName: 'Short ID lookup',
name: 'shortIdLookUp',
type: 'boolean',
default: true,
description: 'If this is set to true then short IDs are looked up by this function as well. This can cause the return value of the function to return an event issue of a different project which is why this is an opt-in',
},
]
},
/* -------------------------------------------------------------------------- */
/* issue:update */
/* -------------------------------------------------------------------------- */
{
displayName: 'Issue ID',
name: 'issueId',
type: 'string',
default: '',
placeholder: '1234',
displayOptions: {
show: {
resource: [
'issue',
],
operation: [
'update',
],
},
},
required: true,
description: 'ID of issue to get',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'issue',
],
operation: [
'update',
],
},
},
options: [
{
displayName: 'Assigned to',
name: 'assignedTo',
type: 'string',
default: '',
description: 'The actor id (or username) of the user or team that should be assigned to this issue',
},
{
displayName: 'Has Seen',
name: 'hasSeen',
type: 'boolean',
default: true,
description: 'In case this API call is invoked with a user context this allows changing of the flag that indicates if the user has seen the event',
},
{
displayName: 'Is Bookmarked',
name: 'isBookmarked',
type: 'boolean',
default: true,
description: 'In case this API call is invoked with a user context this allows changing of the bookmark flag',
},
{
displayName: 'Is Public',
name: 'isPublic',
type: 'boolean',
default: true,
description: 'Sets the issue to public or private',
},
{
displayName: 'Is Subscribed',
name: 'isSubscribed',
type: 'boolean',
default: true,
},
{
displayName: 'Status',
name: 'status',
type: 'options',
default: '',
description: 'The new status for the issue',
options: [
{
name: 'Ignored',
value: 'ignored'
},
{
name: 'Resolved',
value: 'resolved'
},
{
name: 'Resolved Next Release',
value: 'resolvedInNextRelease'
},
{
name: 'Unresolved',
value: 'unresolved'
},
]
},
]
},
] as INodeProperties[];

View file

@ -0,0 +1,205 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const organizationOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'organization',
],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create an organization',
},
{
name: 'Get',
value: 'get',
description: 'Get organization by slug',
},
{
name: 'Get All',
value: 'getAll',
description: 'Get all organizations',
}
],
default: 'get',
description: 'The operation to perform',
},
] as INodeProperties[];
export const organizationFields = [
/* -------------------------------------------------------------------------- */
/* organization:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'organization',
],
},
},
default: false,
description: 'If all results should be returned or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'organization',
],
returnAll: [
false,
],
},
},
typeOptions: {
minValue: 1,
maxValue: 500,
},
default: 100,
description: 'How many results to return',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'organization',
],
operation: [
'getAll',
],
},
},
options: [
{
displayName: 'Member',
name: 'member',
type: 'boolean',
default: true,
description: 'Restrict results to organizations which you have membership',
},
{
displayName: 'Owner',
name: 'owner',
type: 'boolean',
default: true,
description: 'Restrict results to organizations which you are the owner',
},
]
},
/* -------------------------------------------------------------------------- */
/* organization:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'Organization Slug',
name: 'organizationSlug',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getOrganizations',
},
default: '',
displayOptions: {
show: {
resource: [
'organization',
],
operation: [
'get',
],
},
},
required: true,
description: 'The slug of the organization the team should be created for',
},
/* -------------------------------------------------------------------------- */
/* organization:create */
/* -------------------------------------------------------------------------- */
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
displayOptions: {
show: {
resource: [
'organization',
],
operation: [
'create',
],
},
},
required: true,
description: 'The slug of the organization the team should be created for',
},
{
displayName: 'Agree to Terms',
name: 'agreeTerms',
type: 'boolean',
default: false,
displayOptions: {
show: {
resource: [
'organization',
],
operation: [
'create',
],
},
},
description: 'Signaling you agree to the applicable terms of service and privacy policy of Sentry.io',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'organization',
],
operation: [
'create',
],
},
},
options: [
{
displayName: 'Slug',
name: 'slug',
type: 'string',
default: '',
description: 'The unique URL slug for this organization. If this is not provided a slug is automatically generated based on the name',
},
]
},
] as INodeProperties[];

View file

@ -0,0 +1,194 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const projectOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'project',
],
},
},
options: [
{
name: 'Get',
value: 'get',
description: 'Get project by ID',
},
{
name: 'Get All',
value: 'getAll',
description: 'Get all projects',
}
],
default: 'get',
description: 'The operation to perform',
},
] as INodeProperties[];
export const projectFields = [
/* -------------------------------------------------------------------------- */
/* project:create/get */
/* -------------------------------------------------------------------------- */
{
displayName: 'Organization Slug',
name: 'organizationSlug',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getOrganizations',
},
default: '',
displayOptions: {
show: {
resource: [
'project',
],
operation: [
'create',
'get',
'update',
'delete',
],
},
},
required: true,
description: 'The slug of the organization the events belong to',
},
{
displayName: 'Project Slug',
name: 'projectSlug',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getProjects',
loadOptionsDependsOn: [
'organizationSlug',
],
},
default: '',
displayOptions: {
show: {
resource: [
'project',
],
operation: [
'get',
],
},
},
required: true,
description: 'The slug of the project to retrieve',
},
{
displayName: 'Team Slug',
name: 'teamSlug',
type: 'string',
default: '',
displayOptions: {
show: {
resource: [
'project',
],
operation: [
'create',
'update',
'delete',
],
},
},
required: true,
description: 'The slug of the team to create a new project for',
},
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
displayOptions: {
show: {
resource: [
'project',
],
operation: [
'create',
],
},
},
required: true,
description: 'The name for the new project',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'project',
],
operation: [
'create',
],
},
},
options: [
{
displayName: 'Slug',
name: 'slug',
type: 'string',
default: '',
description: 'Optionally a slug for the new project. If its not provided a slug is generated from the name',
},
]
},
/* -------------------------------------------------------------------------- */
/* project:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'project',
],
},
},
default: false,
description: 'If all results should be returned or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'project',
],
returnAll: [
false,
],
},
},
typeOptions: {
minValue: 1,
maxValue: 500,
},
default: 100,
description: 'How many results to return',
},
] as INodeProperties[];

View file

@ -0,0 +1,429 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const releaseOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'release',
],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create a release',
},
{
name: 'Get',
value: 'get',
description: 'Get release by version identifier',
},
{
name: 'Get All',
value: 'getAll',
description: 'Get all releases',
},
],
default: 'get',
description: 'The operation to perform',
},
] as INodeProperties[];
export const releaseFields = [
/* -------------------------------------------------------------------------- */
/* release:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Organization Slug',
name: 'organizationSlug',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getOrganizations',
},
default: '',
displayOptions: {
show: {
resource: [
'release',
],
operation: [
'getAll',
],
},
},
required: true,
description: 'The slug of the organization the releases belong to',
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'release',
],
},
},
default: false,
description: 'If all results should be returned or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'release',
],
returnAll: [
false,
],
},
},
typeOptions: {
minValue: 1,
maxValue: 500,
},
default: 100,
description: 'How many results to return',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'release',
],
operation: [
'getAll',
],
},
},
options: [
{
displayName: 'Query',
name: 'query',
type: 'string',
default: '',
description: 'This parameter can be used to create a “starts with” filter for the version',
},
]
},
/* -------------------------------------------------------------------------- */
/* release:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'Organization Slug',
name: 'organizationSlug',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getOrganizations',
},
default: '',
displayOptions: {
show: {
resource: [
'release',
],
operation: [
'get',
],
},
},
required: true,
description: 'The slug of the organization the release belongs to',
},
{
displayName: 'Version',
name: 'version',
type: 'string',
default: '',
displayOptions: {
show: {
resource: [
'release',
],
operation: [
'get',
],
},
},
required: true,
description: 'The version identifier of the release',
},
/* -------------------------------------------------------------------------- */
/* release:create */
/* -------------------------------------------------------------------------- */
{
displayName: 'Organization Slug',
name: 'organizationSlug',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getOrganizations',
},
default: '',
displayOptions: {
show: {
resource: [
'release',
],
operation: [
'create',
],
},
},
required: true,
description: 'The slug of the organization the release belongs to',
},
{
displayName: 'Version',
name: 'version',
type: 'string',
default: '',
displayOptions: {
show: {
resource: [
'release',
],
operation: [
'create',
],
},
},
required: true,
description: ' a version identifier for this release. Can be a version number, a commit hash etc',
},
{
displayName: 'URL',
name: 'url',
type: 'string',
default: '',
displayOptions: {
show: {
resource: [
'release',
],
operation: [
'create',
],
},
},
required: true,
description: 'A URL that points to the release. This can be the path to an online interface to the sourcecode for instance',
},
{
displayName: 'Projects',
name: 'projects',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod: 'getProjects',
},
default: '',
displayOptions: {
show: {
resource: [
'release',
],
operation: [
'create',
],
},
},
required: true,
description: 'A list of project slugs that are involved in this release',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'release',
],
operation: [
'create',
],
},
},
options: [
{
displayName: 'Date released',
name: 'dateReleased',
type: 'dateTime',
default: '',
description: 'an optional date that indicates when the release went live. If not provided the current time is assumed',
},
{
displayName: 'Commits',
name: 'commits',
description: 'an optional list of commit data to be associated with the release',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
options: [
{
name: 'commitProperties',
displayName: 'Commit Properties',
values: [
{
displayName: 'Id',
name: 'id',
type: 'string',
default: '',
description: 'the sha of the commit',
required: true
},
{
displayName: 'Author Email',
name: 'authorEmail',
type: 'string',
default: '',
description: 'Authors email',
},
{
displayName: 'Author Name',
name: 'authorName',
type: 'string',
default: '',
description: 'Name of author',
},
{
displayName: 'Message',
name: 'message',
type: 'string',
default: '',
description: 'Message of commit',
},
{
displayName: 'Patch Set',
name: 'patchSet',
description: 'A list of the files that have been changed in the commit. Specifying the patch_set is necessary to power suspect commits and suggested assignees',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
options: [
{
name: 'patchSetProperties',
displayName: 'Patch Set Properties',
values: [
{
displayName: 'Path',
name: 'path',
type: 'string',
default: '',
description: 'he path to the file. Both forward and backward slashes are supported',
required: true
},
{
displayName: 'Type',
name: 'type',
type: 'options',
default: '',
description: 'he types of changes that happend in that commit',
options: [
{
name: 'Add',
value: 'add'
},
{
name: 'Modify',
value: 'modify'
},
{
name: 'Delete',
value: 'delete'
},
]
},
]
},
],
},
{
displayName: 'Repository',
name: 'repository',
type: 'string',
default: '',
description: 'Repository name',
},
{
displayName: 'Timestamp',
name: 'timestamp',
type: 'dateTime',
default: '',
description: 'Timestamp of commit',
},
]
},
],
},
{
displayName: 'Refs',
name: 'refs',
description: 'an optional way to indicate the start and end commits for each repository included in a release',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
options: [
{
name: 'refProperties',
displayName: 'Ref Properties',
values: [
{
displayName: 'Commit',
name: 'commit',
type: 'string',
default: '',
description: 'the head sha of the commit',
required: true
},
{
displayName: 'Repository',
name: 'repository',
type: 'string',
default: '',
description: 'Repository name',
required: true
},
{
displayName: 'Previous Commit',
name: 'previousCommit',
type: 'string',
default: '',
description: 'the sha of the HEAD of the previous release',
},
]
},
],
},
]
},
] as INodeProperties[];

View file

@ -0,0 +1,558 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeExecutionData,
INodeType,
INodeTypeDescription,
ILoadOptionsFunctions,
INodePropertyOptions,
} from 'n8n-workflow';
import {
eventOperations,
eventFields,
} from './EventDescription';
import {
issueOperations,
issueFields,
} from './IssueDescription';
import {
organizationFields,
organizationOperations,
} from './OrganizationDescription';
import {
projectOperations,
projectFields,
} from './ProjectDescription';
import {
releaseOperations,
releaseFields,
} from './ReleaseDescription';
import {
teamOperations,
teamFields,
} from './TeamDescription';
import {
sentryIoApiRequest,
sentryApiRequestAllItems,
} from './GenericFunctions';
import { ICommit, IPatchSet, IRef } from './Interface';
export class SentryIo implements INodeType {
description: INodeTypeDescription = {
displayName: 'Sentry.io',
name: 'sentryIo',
icon: 'file:sentryio.png',
group: ['output'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Sentry.io API',
defaults: {
name: 'Sentry.io',
color: '#000000',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'sentryIoOAuth2Api',
required: true,
displayOptions: {
show: {
authentication: [
'oAuth2',
],
},
},
},
{
name: 'sentryIoApi',
required: true,
displayOptions: {
show: {
authentication: [
'accessToken',
],
},
},
},
],
properties: [
{
displayName: 'Authentication',
name: 'authentication',
type: 'options',
options: [
{
name: 'Access Token',
value: 'accessToken',
},
{
name: 'OAuth2',
value: 'oAuth2',
},
],
default: 'accessToken',
description: 'The resource to operate on.',
},
{
displayName: 'Resource',
name: 'resource',
type: 'options',
options: [
{
name: 'Event',
value: 'event',
},
{
name: 'Issue',
value: 'issue',
},
{
name: 'Project',
value: 'project',
},
{
name: 'Release',
value: 'release',
},
{
name: 'Organization',
value: 'organization',
},
{
name: 'Team',
value: 'team',
},
],
default: 'event',
description: 'Resource to consume.',
},
// EVENT
...eventOperations,
...eventFields,
// ISSUE
...issueOperations,
...issueFields,
// ORGANIZATION
...organizationOperations,
...organizationFields,
// PROJECT
...projectOperations,
...projectFields,
// RELEASE
...releaseOperations,
...releaseFields,
// TEAM
...teamOperations,
...teamFields
],
};
methods = {
loadOptions: {
// Get all organizations so they can be displayed easily
async getOrganizations(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const organizations = await sentryApiRequestAllItems.call(this, 'GET', `/api/0/organizations/`, {});
for (const organization of organizations) {
returnData.push({
name: organization.slug,
value: organization.slug,
});
}
returnData.sort((a, b) => {
if (a.name < b.name) { return -1; }
if (a.name > b.name) { return 1; }
return 0;
});
return returnData;
},
// Get all projects so can be displayed easily
async getProjects(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const projects = await sentryApiRequestAllItems.call(this, 'GET', `/api/0/projects/`, {});
const organizationSlug = this.getNodeParameter('organizationSlug') as string;
for (const project of projects) {
if (organizationSlug !== project.organization.slug) {
continue;
}
returnData.push({
name: project.slug,
value: project.slug,
});
}
returnData.sort((a, b) => {
if (a.name < b.name) { return -1; }
if (a.name > b.name) { return 1; }
return 0;
});
return returnData;
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: IDataObject[] = [];
const length = items.length as unknown as number;
let responseData;
const qs: IDataObject = {};
const resource = this.getNodeParameter('resource', 0) as string;
const operation = this.getNodeParameter('operation', 0) as string;
for (let i = 0; i < length; i++) {
if (resource === 'event') {
if (operation === 'getAll') {
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
const projectSlug = this.getNodeParameter('projectSlug', i) as string;
const full = this.getNodeParameter('full', i) as boolean;
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
const endpoint = `/api/0/projects/${organizationSlug}/${projectSlug}/events/`;
if (returnAll === false) {
const limit = this.getNodeParameter('limit', i) as number;
qs.limit = limit;
}
qs.full = full;
responseData = await sentryApiRequestAllItems.call(this, 'GET', endpoint, {}, qs);
if (returnAll === false) {
const limit = this.getNodeParameter('limit', i) as number;
responseData = responseData.splice(0, limit);
}
}
if (operation === 'get') {
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
const projectSlug = this.getNodeParameter('projectSlug', i) as string;
const eventId = this.getNodeParameter('eventId', i) as string;
const endpoint = `/api/0/projects/${organizationSlug}/${projectSlug}/events/${eventId}/`;
responseData = await sentryIoApiRequest.call(this, 'GET', endpoint, qs);
}
}
if (resource === 'issue') {
if (operation === 'getAll') {
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
const projectSlug = this.getNodeParameter('projectSlug', i) as string;
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
const endpoint = `/api/0/projects/${organizationSlug}/${projectSlug}/issues/`;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
if (additionalFields.statsPeriod) {
qs.statsPeriod = additionalFields.statsPeriod as string;
}
if (additionalFields.shortIdLookup) {
qs.shortIdLookup = additionalFields.shortIdLookup as boolean;
}
if (additionalFields.query) {
qs.query = additionalFields.query as string;
}
if (returnAll === false) {
const limit = this.getNodeParameter('limit', i) as number;
qs.limit = limit;
}
responseData = await sentryApiRequestAllItems.call(this, 'GET', endpoint, {}, qs);
if (returnAll === false) {
const limit = this.getNodeParameter('limit', i) as number;
responseData = responseData.splice(0, limit);
}
}
if (operation === 'get') {
const issueId = this.getNodeParameter('issueId', i) as string;
const endpoint = `/api/0/issues/${issueId}/`;
responseData = await sentryIoApiRequest.call(this, 'GET', endpoint, qs);
}
if (operation === 'delete') {
const issueId = this.getNodeParameter('issueId', i) as string;
const endpoint = `/api/0/issues/${issueId}/`;
responseData = await sentryIoApiRequest.call(this, 'DELETE', endpoint, qs);
responseData = { success: true };
}
if (operation === 'update') {
const issueId = this.getNodeParameter('issueId', i) as string;
const endpoint = `/api/0/issues/${issueId}/`;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
if (additionalFields.status) {
qs.status = additionalFields.status as string;
}
if (additionalFields.assignedTo) {
qs.assignedTo = additionalFields.assignedTo as string;
}
if (additionalFields.hasSeen) {
qs.hasSeen = additionalFields.hasSeen as boolean;
}
if (additionalFields.isBookmarked) {
qs.isBookmarked = additionalFields.isBookmarked as boolean;
}
if (additionalFields.isSubscribed) {
qs.isSubscribed = additionalFields.isSubscribed as boolean;
}
if (additionalFields.isPublic) {
qs.isPublic = additionalFields.isPublic as boolean;
}
responseData = await sentryIoApiRequest.call(this, 'PUT', endpoint, qs);
}
}
if (resource === 'organization') {
if (operation === 'get') {
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
const endpoint = `/api/0/organizations/${organizationSlug}/`;
responseData = await sentryIoApiRequest.call(this, 'GET', endpoint, qs);
}
if (operation === 'getAll') {
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
const endpoint = `/api/0/organizations/`;
if (additionalFields.member) {
qs.member = additionalFields.member as boolean;
}
if (additionalFields.owner) {
qs.owner = additionalFields.owner as boolean;
}
if (returnAll === false) {
const limit = this.getNodeParameter('limit', i) as number;
qs.limit = limit;
}
responseData = await sentryApiRequestAllItems.call(this, 'GET', endpoint, {}, qs);
if (responseData === undefined) {
responseData = [];
}
if (returnAll === false) {
const limit = this.getNodeParameter('limit', i) as number;
responseData = responseData.splice(0, limit);
}
}
if (operation === 'create') {
const name = this.getNodeParameter('name', i) as string;
const agreeTerms = this.getNodeParameter('agreeTerms', i) as boolean;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
const endpoint = `/api/0/organizations/`;
qs.name = name;
qs.agreeTerms = agreeTerms;
if (additionalFields.slug) {
qs.slug = additionalFields.slug as string;
}
responseData = await sentryIoApiRequest.call(this, 'POST', endpoint, qs);
}
}
if (resource === 'project') {
if (operation === 'get') {
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
const projectSlug = this.getNodeParameter('projectSlug', i) as string;
const endpoint = `/api/0/projects/${organizationSlug}/${projectSlug}/`;
responseData = await sentryIoApiRequest.call(this, 'GET', endpoint, qs);
}
if (operation === 'getAll') {
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
const endpoint = `/api/0/projects/`;
if (returnAll === false) {
const limit = this.getNodeParameter('limit', i) as number;
qs.limit = limit;
}
responseData = await sentryApiRequestAllItems.call(this, 'GET', endpoint, {}, qs);
if (returnAll === false) {
const limit = this.getNodeParameter('limit', i) as number;
responseData = responseData.splice(0, limit);
}
}
}
if (resource === 'release') {
if (operation === 'get') {
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
const version = this.getNodeParameter('version', i) as string;
const endpoint = `/api/0/organizations/${organizationSlug}/releases/${version}/`;
responseData = await sentryIoApiRequest.call(this, 'GET', endpoint, qs);
}
if (operation === 'getAll') {
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
const endpoint = `/api/0/organizations/${organizationSlug}/releases/`;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
if (additionalFields.query) {
qs.query = additionalFields.query as string;
}
if (returnAll === false) {
const limit = this.getNodeParameter('limit', i) as number;
qs.limit = limit;
}
responseData = await sentryApiRequestAllItems.call(this, 'GET', endpoint, {}, qs);
if (returnAll === false) {
const limit = this.getNodeParameter('limit', i) as number;
responseData = responseData.splice(0, limit);
}
}
if (operation === 'create') {
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
const endpoint = `/api/0/organizations/${organizationSlug}/releases/`;
const version = this.getNodeParameter('version', i) as string;
const url = this.getNodeParameter('url', i) as string;
const projects = this.getNodeParameter('projects', i) as string[];
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
if (additionalFields.dateReleased) {
qs.dateReleased = additionalFields.dateReleased as string;
}
qs.version = version;
qs.url = url;
qs.projects = projects;
if (additionalFields.commits) {
const commits: ICommit[] = [];
//@ts-ignore
// tslint:disable-next-line: no-any
additionalFields.commits.commitProperties.map((commit: any) => {
const commitObject: ICommit = { id: commit.id };
if (commit.repository) {
commitObject.repository = commit.repository;
}
if (commit.message) {
commitObject.message = commit.message;
}
if (commit.patchSet && Array.isArray(commit.patchSet)) {
commit.patchSet.patchSetProperties.map((patchSet: IPatchSet) => {
commitObject.patch_set?.push(patchSet);
});
}
if (commit.authorName) {
commitObject.author_name = commit.authorName;
}
if (commit.authorEmail) {
commitObject.author_email = commit.authorEmail;
}
if (commit.timestamp) {
commitObject.timestamp = commit.timestamp;
}
commits.push(commitObject);
});
qs.commits = commits;
}
if (additionalFields.refs) {
const refs: IRef[] = [];
//@ts-ignore
additionalFields.refs.refProperties.map((ref: IRef) => {
refs.push(ref);
});
qs.refs = refs;
}
responseData = await sentryIoApiRequest.call(this, 'POST', endpoint, qs);
}
}
if (resource === 'team') {
if (operation === 'get') {
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
const teamSlug = this.getNodeParameter('teamSlug', i) as string;
const endpoint = `/api/0/teams/${organizationSlug}/${teamSlug}/`;
responseData = await sentryIoApiRequest.call(this, 'GET', endpoint, qs);
}
if (operation === 'getAll') {
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
const endpoint = `/api/0/organizations/${organizationSlug}/teams/`;
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
if (returnAll === false) {
const limit = this.getNodeParameter('limit', i) as number;
qs.limit = limit;
}
responseData = await sentryApiRequestAllItems.call(this, 'GET', endpoint, {}, qs);
if (returnAll === false) {
const limit = this.getNodeParameter('limit', i) as number;
responseData = responseData.splice(0, limit);
}
}
if (operation === 'create') {
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
const name = this.getNodeParameter('name', i) as string;
const endpoint = `/api/0/organizations/${organizationSlug}/teams/`;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
qs.name = name;
if (additionalFields.slug) {
qs.slug = additionalFields.slug;
}
responseData = await sentryIoApiRequest.call(this, 'POST', endpoint, qs);
}
}
if (Array.isArray(responseData)) {
returnData.push.apply(returnData, responseData as IDataObject[]);
} else {
returnData.push(responseData as IDataObject);
}
}
return [this.helpers.returnJsonArray(returnData)];
}
}

View file

@ -0,0 +1,290 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const teamOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'team',
],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create a new team',
},
{
name: 'Get',
value: 'get',
description: 'Get team by slug',
},
{
name: 'Get All',
value: 'getAll',
description: 'Get all teams',
}
],
default: 'get',
description: 'The operation to perform',
},
] as INodeProperties[];
export const teamFields = [
/* -------------------------------------------------------------------------- */
/* team:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Organization Slug',
name: 'organizationSlug',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getOrganizations',
},
default: '',
displayOptions: {
show: {
resource: [
'team',
],
operation: [
'getAll',
],
},
},
required: true,
description: 'The slug of the organization for which the teams should be listed',
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'team',
],
},
},
default: false,
description: 'If all results should be returned or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
operation: [
'getAll',
],
resource: [
'team',
],
returnAll: [
false,
],
},
},
typeOptions: {
minValue: 1,
maxValue: 500,
},
default: 100,
description: 'How many results to return',
},
/* -------------------------------------------------------------------------- */
/* team:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'Organization Slug',
name: 'organizationSlug',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getOrganizations',
},
default: '',
displayOptions: {
show: {
resource: [
'team',
],
operation: [
'get',
],
},
},
required: true,
description: 'The slug of the organization the team belongs to',
},
{
displayName: 'Team Slug',
name: 'teamSlug',
type: 'string',
default: '',
displayOptions: {
show: {
resource: [
'team',
],
operation: [
'get',
],
},
},
required: true,
description: 'The slug of the team to get',
},
/* -------------------------------------------------------------------------- */
/* team:create */
/* -------------------------------------------------------------------------- */
{
displayName: 'Organization Slug',
name: 'organizationSlug',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getOrganizations',
},
default: '',
displayOptions: {
show: {
resource: [
'team',
],
operation: [
'create',
],
},
},
required: true,
description: 'The slug of the organization the team belongs to',
},
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
displayOptions: {
show: {
resource: [
'team',
],
operation: [
'create',
],
},
},
required: true,
description: 'The name of the team',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'team',
],
operation: [
'create',
],
},
},
options: [
{
displayName: 'Slug',
name: 'slug',
type: 'string',
default: '',
description: 'The optional slug for this team. If not provided it will be auto generated from the name',
},
]
},
/* -------------------------------------------------------------------------- */
/* team:update */
/* -------------------------------------------------------------------------- */
{
displayName: 'Organization Slug',
name: 'organizationSlug',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getOrganizations',
},
default: '',
displayOptions: {
show: {
resource: [
'team',
],
operation: [
'update', 'delete'
],
},
},
required: true,
description: 'The slug of the organization the team belongs to',
},
{
displayName: 'Team Slug',
name: 'teamSlug',
type: 'string',
default: '',
displayOptions: {
show: {
resource: [
'team',
],
operation: [
'update', 'delete'
],
},
},
required: true,
description: 'The slug of the team to get',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'team',
],
operation: [
'update',
],
},
},
options: [
{
displayName: 'Slug',
name: 'slug',
type: 'string',
default: '',
description: 'The new slug of the team. Must be unique and available',
},
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
description: 'The new name of the team',
},
]
},
] as INodeProperties[];

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -139,6 +139,8 @@
"dist/credentials/RundeckApi.credentials.js", "dist/credentials/RundeckApi.credentials.js",
"dist/credentials/ShopifyApi.credentials.js", "dist/credentials/ShopifyApi.credentials.js",
"dist/credentials/SalesforceOAuth2Api.credentials.js", "dist/credentials/SalesforceOAuth2Api.credentials.js",
"dist/credentials/SentryIoApi.credentials.js",
"dist/credentials/SentryIoOAuth2Api.credentials.js",
"dist/credentials/SlackApi.credentials.js", "dist/credentials/SlackApi.credentials.js",
"dist/credentials/SlackOAuth2Api.credentials.js", "dist/credentials/SlackOAuth2Api.credentials.js",
"dist/credentials/Sms77Api.credentials.js", "dist/credentials/Sms77Api.credentials.js",
@ -319,6 +321,7 @@
"dist/nodes/S3/S3.node.js", "dist/nodes/S3/S3.node.js",
"dist/nodes/Salesforce/Salesforce.node.js", "dist/nodes/Salesforce/Salesforce.node.js",
"dist/nodes/Set.node.js", "dist/nodes/Set.node.js",
"dist/nodes/SentryIo/SentryIo.node.js",
"dist/nodes/Shopify/Shopify.node.js", "dist/nodes/Shopify/Shopify.node.js",
"dist/nodes/Shopify/ShopifyTrigger.node.js", "dist/nodes/Shopify/ShopifyTrigger.node.js",
"dist/nodes/Signl4/Signl4.node.js", "dist/nodes/Signl4/Signl4.node.js",