diff --git a/packages/nodes-base/nodes/Github/GenericFunctions.ts b/packages/nodes-base/nodes/Github/GenericFunctions.ts index 505eff071a..10d9558b3e 100644 --- a/packages/nodes-base/nodes/Github/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Github/GenericFunctions.ts @@ -18,7 +18,7 @@ import { * @param {object} body * @returns {Promise} */ -export async function githubApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: object, query?: object): Promise { // tslint:disable-line:no-any +export async function githubApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: object, query?: object, option: IDataObject = {}): Promise { // tslint:disable-line:no-any const options: OptionsWithUri = { method, @@ -31,6 +31,10 @@ export async function githubApiRequest(this: IHookFunctions | IExecuteFunctions, json: true, }; + if (Object.keys(option).length !== 0) { + Object.assign(options, option); + } + try { const authenticationMethod = this.getNodeParameter('authentication', 0, 'accessToken') as string; @@ -95,3 +99,22 @@ export async function getFileSha(this: IHookFunctions | IExecuteFunctions, owner } return responseData.sha; } + +export async function githubApiRequestAllItems(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any + + const returnData: IDataObject[] = []; + + let responseData; + + query.per_page = 100; + query.page = 1; + + do { + responseData = await githubApiRequest.call(this, method, endpoint, body, query, { resolveWithFullResponse: true }); + query.page++; + returnData.push.apply(returnData, responseData.body); + } while ( + responseData.headers.link && responseData.headers.link.includes('next') + ); + return returnData; +} diff --git a/packages/nodes-base/nodes/Github/Github.node.ts b/packages/nodes-base/nodes/Github/Github.node.ts index f40ad6b9dc..02ac2d9dc1 100644 --- a/packages/nodes-base/nodes/Github/Github.node.ts +++ b/packages/nodes-base/nodes/Github/Github.node.ts @@ -12,19 +12,25 @@ import { import { getFileSha, githubApiRequest, + githubApiRequestAllItems, } from './GenericFunctions'; +import { + snakeCase, +} from 'change-case'; + export class Github implements INodeType { description: INodeTypeDescription = { displayName: 'GitHub', name: 'github', - icon: 'file:github.png', + icon: 'file:github.svg', group: ['input'], version: 1, - description: 'Retrieve data from GitHub API.', + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume GitHub API.', defaults: { name: 'GitHub', - color: '#665533', + color: '#000000', }, inputs: ['main'], outputs: ['main'], @@ -91,6 +97,10 @@ export class Github implements INodeType { name: 'Release', value: 'release', }, + { + name: 'Review', + value: 'review', + }, { name: 'User', value: 'user', @@ -275,7 +285,42 @@ export class Github implements INodeType { description: 'The operation to perform.', }, - + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'review', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Creates a new review', + }, + { + name: 'Get', + value: 'get', + description: 'Get a review for a pull request', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all reviews for a pull request', + }, + { + name: 'Update', + value: 'update', + description: 'Update a review', + }, + ], + default: 'create', + description: 'The operation to perform.', + }, // ---------------------------------- // shared @@ -1114,6 +1159,248 @@ export class Github implements INodeType { ], }, + + // ---------------------------------- + // rerview + // ---------------------------------- + // ---------------------------------- + // review:getAll + // ---------------------------------- + { + displayName: 'PR Number', + name: 'pullRequestNumber', + type: 'number', + default: 0, + required: true, + displayOptions: { + show: { + operation: [ + 'get', + 'update', + ], + resource: [ + 'review', + ], + }, + }, + description: 'The number of the pull request.', + }, + { + displayName: 'Review ID', + name: 'reviewId', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + operation: [ + 'get', + 'update', + ], + resource: [ + 'review', + ], + }, + }, + description: 'ID of the review', + }, + + // ---------------------------------- + // review:getAll + // ---------------------------------- + { + displayName: 'PR Number', + name: 'pullRequestNumber', + type: 'number', + default: 0, + required: true, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'review', + ], + }, + }, + description: 'The number of the pull request.', + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'review', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'review', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + default: 50, + description: 'How many results to return.', + }, + // ---------------------------------- + // review:create + // ---------------------------------- + { + displayName: 'PR Number', + name: 'pullRequestNumber', + type: 'number', + default: 0, + required: true, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'review', + ], + }, + }, + description: 'The number of the pull request to review.', + }, + { + displayName: 'Event', + name: 'event', + type: 'options', + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'review', + ], + }, + }, + options: [ + { + name: 'Approve', + value: 'approve', + description: 'Approve the pull request', + }, + { + name: 'Request Change', + value: 'requestChanges', + description: 'Request code changes', + }, + { + name: 'Comment', + value: 'comment', + description: 'Add a comment without approval or change requests', + }, + { + name: 'Pending', + value: 'pending', + description: 'You will need to submit the pull request review when you are ready.', + }, + ], + default: 'approve', + description: 'The review action you want to perform.', + }, + { + displayName: 'Body', + name: 'body', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'review', + ], + event: [ + 'requestChanges', + 'comment', + ], + }, + }, + default: '', + description: 'The body of the review (required for events Request Changes or Comment).', + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + placeholder: 'Add Field', + description: 'Additional fields.', + type: 'collection', + default: {}, + displayOptions: { + show: { + operation: [ + 'create', + ], + resource: [ + 'review', + ], + }, + }, + options: [ + { + displayName: 'Commit ID', + name: 'commitId', + type: 'string', + default: '', + description: 'The SHA of the commit that needs a review, if different from the latest', + }, + ], + }, + // ---------------------------------- + // review:update + // ---------------------------------- + { + displayName: 'Body', + name: 'body', + type: 'string', + typeOptions: { + alwaysOpenEditWindow: true, + }, + displayOptions: { + show: { + operation: [ + 'update', + ], + resource: [ + 'review', + ], + }, + }, + default: '', + description: 'The body of the review', + }, ], }; @@ -1122,6 +1409,10 @@ export class Github implements INodeType { const items = this.getInputData(); const returnData: IDataObject[] = []; + let returnAll = false; + + let responseData; + // Operations which overwrite the returned data const overwriteDataOperations = [ 'file:create', @@ -1136,6 +1427,9 @@ export class Github implements INodeType { 'repository:get', 'repository:getLicense', 'repository:getProfile', + 'review:create', + 'review:get', + 'review:update', ]; // Operations which overwrite the returned data and return arrays // and has so to be merged with the data of other items @@ -1144,6 +1438,7 @@ export class Github implements INodeType { 'repository:listPopularPaths', 'repository:listReferrers', 'user:getRepositories', + 'review:getAll', ]; @@ -1386,6 +1681,63 @@ export class Github implements INodeType { endpoint = `/repos/${owner}/${repository}/issues`; } + } else if (resource === 'review') { + if (operation === 'get') { + // ---------------------------------- + // get + // ---------------------------------- + requestMethod = 'GET'; + + const reviewId = this.getNodeParameter('reviewId', i) as string; + + const pullRequestNumber = this.getNodeParameter('pullRequestNumber', i) as string; + + endpoint = `/repos/${owner}/${repository}/pulls/${pullRequestNumber}/reviews/${reviewId}`; + + } else if (operation === 'getAll') { + // ---------------------------------- + // getAll + // ---------------------------------- + requestMethod = 'GET'; + + returnAll = this.getNodeParameter('returnAll', 0) as boolean; + + const pullRequestNumber = this.getNodeParameter('pullRequestNumber', i) as string; + + if (returnAll === false) { + qs.per_page = this.getNodeParameter('limit', 0) as number; + } + + endpoint = `/repos/${owner}/${repository}/pulls/${pullRequestNumber}/reviews`; + } else if (operation === 'create') { + // ---------------------------------- + // create + // ---------------------------------- + requestMethod = 'POST'; + + const pullRequestNumber = this.getNodeParameter('pullRequestNumber', i) as string; + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + Object.assign(body, additionalFields); + + body.event = snakeCase(this.getNodeParameter('event', i) as string).toUpperCase(); + if (body.event === 'REQUEST_CHANGES' || body.event === 'COMMENT') { + body.body = this.getNodeParameter('body', i) as string; + } + + endpoint = `/repos/${owner}/${repository}/pulls/${pullRequestNumber}/reviews`; + } else if (operation === 'update') { + // ---------------------------------- + // update + // ---------------------------------- + requestMethod = 'PUT'; + + const pullRequestNumber = this.getNodeParameter('pullRequestNumber', i) as string; + const reviewId = this.getNodeParameter('reviewId', i) as string; + + body.body = this.getNodeParameter('body', i) as string; + + endpoint = `/repos/${owner}/${repository}/pulls/${pullRequestNumber}/reviews/${reviewId}`; + } } else if (resource === 'user') { if (operation === 'getRepositories') { // ---------------------------------- @@ -1400,7 +1752,11 @@ export class Github implements INodeType { throw new Error(`The resource "${resource}" is not known!`); } - const responseData = await githubApiRequest.call(this, requestMethod, endpoint, body, qs); + if (returnAll === true) { + responseData = await githubApiRequestAllItems.call(this, requestMethod, endpoint, body, qs); + } else { + responseData = await githubApiRequest.call(this, requestMethod, endpoint, body, qs); + } if (fullOperation === 'file:get') { const asBinaryProperty = this.getNodeParameter('asBinaryProperty', i); @@ -1443,6 +1799,5 @@ export class Github implements INodeType { // For all other ones simply return the unchanged items return this.prepareOutputData(items); } - } } diff --git a/packages/nodes-base/nodes/Github/GithubTrigger.node.ts b/packages/nodes-base/nodes/Github/GithubTrigger.node.ts index 6992da2700..3d38dc5a55 100644 --- a/packages/nodes-base/nodes/Github/GithubTrigger.node.ts +++ b/packages/nodes-base/nodes/Github/GithubTrigger.node.ts @@ -19,14 +19,14 @@ export class GithubTrigger implements INodeType { description: INodeTypeDescription = { displayName: 'Github Trigger', name: 'githubTrigger', - icon: 'file:github.png', + icon: 'file:github.svg', group: ['trigger'], version: 1, subtitle: '={{$parameter["owner"] + "/" + $parameter["repository"] + ": " + $parameter["events"].join(", ")}}', description: 'Starts the workflow when a Github events occurs.', defaults: { name: 'Github Trigger', - color: '#885577', + color: '#000000', }, inputs: [], outputs: ['main'], diff --git a/packages/nodes-base/nodes/Github/github.png b/packages/nodes-base/nodes/Github/github.png deleted file mode 100644 index 0c6eee3a81..0000000000 Binary files a/packages/nodes-base/nodes/Github/github.png and /dev/null differ diff --git a/packages/nodes-base/nodes/Github/github.svg b/packages/nodes-base/nodes/Github/github.svg new file mode 100644 index 0000000000..933c133ff3 --- /dev/null +++ b/packages/nodes-base/nodes/Github/github.svg @@ -0,0 +1 @@ + \ No newline at end of file