diff --git a/packages/nodes-base/nodes/Github/GenericFunctions.ts b/packages/nodes-base/nodes/Github/GenericFunctions.ts index 901d080343..b5293cd157 100644 --- a/packages/nodes-base/nodes/Github/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Github/GenericFunctions.ts @@ -119,3 +119,13 @@ export function isBase64(content: string) { const base64regex = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/; return base64regex.test(content); } + +export function validateJSON(json: string | undefined): any { + let result; + try { + result = JSON.parse(json!); + } catch (exception) { + result = undefined; + } + return result; +} diff --git a/packages/nodes-base/nodes/Github/Github.node.ts b/packages/nodes-base/nodes/Github/Github.node.ts index ea20ef901d..2e29b467ca 100644 --- a/packages/nodes-base/nodes/Github/Github.node.ts +++ b/packages/nodes-base/nodes/Github/Github.node.ts @@ -14,9 +14,9 @@ import { githubApiRequest, githubApiRequestAllItems, isBase64, + validateJSON, } from './GenericFunctions'; - -import { getRepositories, getUsers } from './SearchFunctions'; +import { getRepositories, getUsers, getWorkflows } from './SearchFunctions'; export class Github implements INodeType { description: INodeTypeDescription = { @@ -103,6 +103,10 @@ export class Github implements INodeType { name: 'User', value: 'user', }, + { + name: 'Workflow', + value: 'workflow', + }, ], default: 'issue', }, @@ -391,6 +395,129 @@ export class Github implements INodeType { default: 'create', }, + // ---------------------------------- + // workflow + // ---------------------------------- + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['workflow'], + }, + }, + options: [ + { + name: 'Disable', + value: 'disable', + description: 'Disable a workflow', + action: 'Disable a workflow', + }, + { + name: 'Dispatch', + value: 'dispatch', + description: 'Dispatch a workflow event', + action: 'Dispatch a workflow event', + }, + { + name: 'Enable', + value: 'enable', + description: 'Enable a workflow', + action: 'Enable a workflow', + }, + { + name: 'Get', + value: 'get', + description: 'Get a workflow', + action: 'Get a workflow', + }, + { + name: 'Get Usage', + value: 'getUsage', + description: 'Get the usage of a workflow', + action: 'Get the usage of a workflow', + }, + { + name: 'List', + value: 'list', + description: 'List workflows', + action: 'List workflows', + }, + ], + default: 'dispatch', + }, + { + displayName: 'Workflow', + name: 'workflowId', + type: 'resourceLocator', + default: { mode: 'list', value: '' }, + required: true, + modes: [ + { + displayName: 'Workflow', + name: 'list', + type: 'list', + placeholder: 'Select a workflow...', + typeOptions: { + searchListMethod: 'getWorkflows', + searchable: true, + searchFilterRequired: true, + }, + }, + { + displayName: 'By ID', + name: 'name', + type: 'string', + placeholder: 'e.g. 12345678', + validation: [ + { + type: 'regex', + properties: { + regex: '\\d+', + errorMessage: 'Not a valid Github Workflow ID', + }, + }, + ], + }, + ], + displayOptions: { + show: { + resource: ['workflow'], + operation: ['disable', 'dispatch', 'get', 'getUsage', 'enable'], + }, + }, + description: 'The workflow to dispatch', + }, + { + displayName: 'Ref', + name: 'ref', + type: 'string', + default: 'main', + required: true, + displayOptions: { + show: { + resource: ['workflow'], + operation: ['dispatch'], + }, + }, + description: 'The git reference for the workflow dispatch (branch, tag, or commit SHA)', + }, + { + displayName: 'Inputs', + name: 'inputs', + type: 'json', + default: '{}', + displayOptions: { + show: { + resource: ['workflow'], + operation: ['dispatch'], + }, + }, + description: 'JSON object with input parameters for the workflow', + }, + // ---------------------------------- // shared // ---------------------------------- @@ -1854,6 +1981,7 @@ export class Github implements INodeType { listSearch: { getUsers, getRepositories, + getWorkflows, }, }; @@ -1886,6 +2014,12 @@ export class Github implements INodeType { 'review:get', 'review:update', 'user:invite', + 'workflow:disable', + 'workflow:dispatch', + 'workflow:enable', + 'workflow:get', + 'workflow:getUsage', + 'workflow:list', ]; // Operations which overwrite the returned data and return arrays // and has so to be merged with the data of other items @@ -2348,6 +2482,77 @@ export class Github implements INodeType { qs.per_page = this.getNodeParameter('limit', 0); } } + } else if (resource === 'workflow') { + if (operation === 'disable') { + // ---------------------------------- + // disable + // ---------------------------------- + + requestMethod = 'PUT'; + + const workflowId = this.getNodeParameter('workflowId', i) as string; + + endpoint = `/repos/${owner}/${repository}/actions/workflows/${workflowId}/disable`; + } else if (operation === 'dispatch') { + // ---------------------------------- + // dispatch + // ---------------------------------- + + requestMethod = 'POST'; + + const workflowId = this.getNodeParameter('workflowId', i) as string; + + endpoint = `/repos/${owner}/${repository}/actions/workflows/${workflowId}/dispatches`; + body.ref = this.getNodeParameter('ref', i) as string; + + const inputs = validateJSON( + this.getNodeParameter('inputs', i) as string, + ) as IDataObject; + if (inputs === undefined) { + throw new NodeOperationError(this.getNode(), 'Inputs: Invalid JSON', { + itemIndex: i, + }); + } + body.inputs = inputs; + } else if (operation === 'enable') { + // ---------------------------------- + // enable + // ---------------------------------- + + requestMethod = 'PUT'; + + const workflowId = this.getNodeParameter('workflowId', i) as string; + + endpoint = `/repos/${owner}/${repository}/actions/workflows/${workflowId}/enable`; + } else if (operation === 'get') { + // ---------------------------------- + // get + // ---------------------------------- + + requestMethod = 'GET'; + + const workflowId = this.getNodeParameter('workflowId', i) as string; + + endpoint = `/repos/${owner}/${repository}/actions/workflows/${workflowId}`; + } else if (operation === 'getUsage') { + // ---------------------------------- + // getUsage + // ---------------------------------- + + requestMethod = 'GET'; + + const workflowId = this.getNodeParameter('workflowId', i) as string; + + endpoint = `/repos/${owner}/${repository}/actions/workflows/${workflowId}/timing`; + } else if (operation === 'list') { + // ---------------------------------- + // list + // ---------------------------------- + + requestMethod = 'GET'; + + endpoint = `/repos/${owner}/${repository}/actions/workflows`; + } } else { throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known!`, { itemIndex: i, diff --git a/packages/nodes-base/nodes/Github/SearchFunctions.ts b/packages/nodes-base/nodes/Github/SearchFunctions.ts index 4d2b34a154..7c2d5fed2a 100644 --- a/packages/nodes-base/nodes/Github/SearchFunctions.ts +++ b/packages/nodes-base/nodes/Github/SearchFunctions.ts @@ -85,3 +85,32 @@ export async function getRepositories( const nextPaginationToken = page * per_page < responseData.total_count ? page + 1 : undefined; return { results, paginationToken: nextPaginationToken }; } + +export async function getWorkflows( + this: ILoadOptionsFunctions, + paginationToken?: string, +): Promise { + const owner = this.getCurrentNodeParameter('owner', { extractValue: true }); + const repository = this.getCurrentNodeParameter('repository', { extractValue: true }); + const page = paginationToken ? +paginationToken : 1; + const per_page = 100; + const endpoint = `/repos/${owner}/${repository}/actions/workflows`; + let responseData: { workflows: Array<{ id: string; name: string }>; total_count: number } = { + workflows: [], + total_count: 0, + }; + + try { + responseData = await githubApiRequest.call(this, 'GET', endpoint, {}, { page, per_page }); + } catch { + // will fail if the repository does not have any workflows + } + + const results: INodeListSearchItems[] = responseData.workflows.map((workflow) => ({ + name: workflow.name, + value: workflow.id, + })); + + const nextPaginationToken = page * per_page < responseData.total_count ? page + 1 : undefined; + return { results, paginationToken: nextPaginationToken }; +}