mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(Github Trigger Node): Use resource locator component (#5253)
* ⚡️wip * ⚡️RLC Search Function for getUsers and getRepositories * 🐛fix Repository RLC by name url * 🐛 search method getRepositories include forks * 🐛 fix repository name can have a dot * 🐛 fix RLC extractValue without optional * 🎨 fix linting errors * 🎨 using prefix 'e.g.' in RLC placeholders --------- Co-authored-by: Michael Kret <michael.k@radency.com>
This commit is contained in:
parent
0cf45bc4c8
commit
a3d8fac73a
|
@ -2,7 +2,7 @@ import type { OptionsWithUri } from 'request';
|
||||||
|
|
||||||
import type { IExecuteFunctions, IHookFunctions } from 'n8n-core';
|
import type { IExecuteFunctions, IHookFunctions } from 'n8n-core';
|
||||||
|
|
||||||
import type { IDataObject } from 'n8n-workflow';
|
import type { IDataObject, ILoadOptionsFunctions } from 'n8n-workflow';
|
||||||
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
|
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,7 +10,7 @@ import { NodeApiError, NodeOperationError } from 'n8n-workflow';
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export async function githubApiRequest(
|
export async function githubApiRequest(
|
||||||
this: IHookFunctions | IExecuteFunctions,
|
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
|
||||||
method: string,
|
method: string,
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
body: object,
|
body: object,
|
||||||
|
|
|
@ -9,6 +9,7 @@ import type {
|
||||||
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
|
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
|
||||||
|
|
||||||
import { githubApiRequest } from './GenericFunctions';
|
import { githubApiRequest } from './GenericFunctions';
|
||||||
|
import { getRepositories, getUsers } from './SearchFunctions';
|
||||||
|
|
||||||
export class GithubTrigger implements INodeType {
|
export class GithubTrigger implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
|
@ -73,20 +74,111 @@ export class GithubTrigger implements INodeType {
|
||||||
{
|
{
|
||||||
displayName: 'Repository Owner',
|
displayName: 'Repository Owner',
|
||||||
name: 'owner',
|
name: 'owner',
|
||||||
type: 'string',
|
type: 'resourceLocator',
|
||||||
default: '',
|
default: { mode: 'list', value: '' },
|
||||||
required: true,
|
required: true,
|
||||||
placeholder: 'n8n-io',
|
modes: [
|
||||||
description: 'Owner of the repsitory',
|
{
|
||||||
|
displayName: 'Repository Owner',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Select an owner...',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'getUsers',
|
||||||
|
searchable: true,
|
||||||
|
searchFilterRequired: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Link',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. https://github.com/n8n-io',
|
||||||
|
extractValue: {
|
||||||
|
type: 'regex',
|
||||||
|
regex: 'https:\\/\\/github.com\\/([-_0-9a-zA-Z]+)',
|
||||||
|
},
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: 'https:\\/\\/github.com\\/([-_0-9a-zA-Z]+)(?:.*)',
|
||||||
|
errorMessage: 'Not a valid Github URL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'By Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. n8n-io',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '[-_a-zA-Z0-9]+',
|
||||||
|
errorMessage: 'Not a valid Github Owner Name',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
url: '=https://github.com/{{$value}}',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Repository Name',
|
displayName: 'Repository Name',
|
||||||
name: 'repository',
|
name: 'repository',
|
||||||
type: 'string',
|
type: 'resourceLocator',
|
||||||
default: '',
|
default: { mode: 'list', value: '' },
|
||||||
required: true,
|
required: true,
|
||||||
placeholder: 'n8n',
|
modes: [
|
||||||
description: 'The name of the repsitory',
|
{
|
||||||
|
displayName: 'Repository Name',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Select an Repository...',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'getRepositories',
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Link',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. https://github.com/n8n-io/n8n',
|
||||||
|
extractValue: {
|
||||||
|
type: 'regex',
|
||||||
|
regex: 'https:\\/\\/github.com\\/(?:[-_0-9a-zA-Z]+)\\/([-_.0-9a-zA-Z]+)',
|
||||||
|
},
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: 'https:\\/\\/github.com\\/(?:[-_0-9a-zA-Z]+)\\/([-_.0-9a-zA-Z]+)(?:.*)',
|
||||||
|
errorMessage: 'Not a valid Github Repository URL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'By Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. n8n',
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: '[-_.0-9a-zA-Z]+',
|
||||||
|
errorMessage: 'Not a valid Github Repository Name',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
url: '=https://github.com/{{$parameter["owner"]}}/{{$value}}',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Events',
|
displayName: 'Events',
|
||||||
|
@ -343,7 +435,6 @@ export class GithubTrigger implements INodeType {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
// @ts-ignore (because of request)
|
|
||||||
webhookMethods = {
|
webhookMethods = {
|
||||||
default: {
|
default: {
|
||||||
async checkExists(this: IHookFunctions): Promise<boolean> {
|
async checkExists(this: IHookFunctions): Promise<boolean> {
|
||||||
|
@ -355,8 +446,10 @@ export class GithubTrigger implements INodeType {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Webhook got created before so check if it still exists
|
// Webhook got created before so check if it still exists
|
||||||
const owner = this.getNodeParameter('owner') as string;
|
const owner = this.getNodeParameter('owner', '', { extractValue: true }) as string;
|
||||||
const repository = this.getNodeParameter('repository') as string;
|
const repository = this.getNodeParameter('repository', '', {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
const endpoint = `/repos/${owner}/${repository}/hooks/${webhookData.webhookId}`;
|
const endpoint = `/repos/${owner}/${repository}/hooks/${webhookData.webhookId}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -373,7 +466,6 @@ export class GithubTrigger implements INodeType {
|
||||||
// Some error occured
|
// Some error occured
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it did not error then the webhook exists
|
// If it did not error then the webhook exists
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
@ -387,8 +479,10 @@ export class GithubTrigger implements INodeType {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const owner = this.getNodeParameter('owner') as string;
|
const owner = this.getNodeParameter('owner', '', { extractValue: true }) as string;
|
||||||
const repository = this.getNodeParameter('repository') as string;
|
const repository = this.getNodeParameter('repository', '', {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
const events = this.getNodeParameter('events', []);
|
const events = this.getNodeParameter('events', []);
|
||||||
|
|
||||||
const endpoint = `/repos/${owner}/${repository}/hooks`;
|
const endpoint = `/repos/${owner}/${repository}/hooks`;
|
||||||
|
@ -455,8 +549,10 @@ export class GithubTrigger implements INodeType {
|
||||||
const webhookData = this.getWorkflowStaticData('node');
|
const webhookData = this.getWorkflowStaticData('node');
|
||||||
|
|
||||||
if (webhookData.webhookId !== undefined) {
|
if (webhookData.webhookId !== undefined) {
|
||||||
const owner = this.getNodeParameter('owner') as string;
|
const owner = this.getNodeParameter('owner', '', { extractValue: true }) as string;
|
||||||
const repository = this.getNodeParameter('repository') as string;
|
const repository = this.getNodeParameter('repository', '', {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
const endpoint = `/repos/${owner}/${repository}/hooks/${webhookData.webhookId}`;
|
const endpoint = `/repos/${owner}/${repository}/hooks/${webhookData.webhookId}`;
|
||||||
const body = {};
|
const body = {};
|
||||||
|
|
||||||
|
@ -477,6 +573,13 @@ export class GithubTrigger implements INodeType {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
methods = {
|
||||||
|
listSearch: {
|
||||||
|
getUsers,
|
||||||
|
getRepositories,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
||||||
const bodyData = this.getBodyData();
|
const bodyData = this.getBodyData();
|
||||||
|
|
||||||
|
|
87
packages/nodes-base/nodes/Github/SearchFunctions.ts
Normal file
87
packages/nodes-base/nodes/Github/SearchFunctions.ts
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import type {
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
INodeListSearchItems,
|
||||||
|
INodeListSearchResult,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { githubApiRequest } from './GenericFunctions';
|
||||||
|
|
||||||
|
type UserSearchItem = {
|
||||||
|
login: string;
|
||||||
|
html_url: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RepositorySearchItem = {
|
||||||
|
name: string;
|
||||||
|
html_url: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UserSearchResponse = {
|
||||||
|
items: UserSearchItem[];
|
||||||
|
total_count: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RepositorySearchResponse = {
|
||||||
|
items: RepositorySearchItem[];
|
||||||
|
total_count: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getUsers(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
filter?: string,
|
||||||
|
paginationToken?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
const page = paginationToken ? +paginationToken : 1;
|
||||||
|
const per_page = 100;
|
||||||
|
const responseData: UserSearchResponse = await githubApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
'/search/users',
|
||||||
|
{},
|
||||||
|
{ q: filter, page, per_page },
|
||||||
|
);
|
||||||
|
|
||||||
|
const results: INodeListSearchItems[] = responseData.items.map((item: UserSearchItem) => ({
|
||||||
|
name: item.login,
|
||||||
|
value: item.login,
|
||||||
|
url: item.html_url,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const nextPaginationToken = page * per_page < responseData.total_count ? page + 1 : undefined;
|
||||||
|
return { results, paginationToken: nextPaginationToken };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getRepositories(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
filter?: string,
|
||||||
|
paginationToken?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
const owner = this.getCurrentNodeParameter('owner', { extractValue: true });
|
||||||
|
const page = paginationToken ? +paginationToken : 1;
|
||||||
|
const per_page = 100;
|
||||||
|
const q = `${filter ?? ''} user:${owner} fork:true`;
|
||||||
|
let responseData: RepositorySearchResponse = {
|
||||||
|
items: [],
|
||||||
|
total_count: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
responseData = await githubApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
'/search/repositories',
|
||||||
|
{},
|
||||||
|
{ q, page, per_page },
|
||||||
|
);
|
||||||
|
} catch (_error) {
|
||||||
|
// will fail if the owner does not have any repositories
|
||||||
|
}
|
||||||
|
|
||||||
|
const results: INodeListSearchItems[] = responseData.items.map((item: RepositorySearchItem) => ({
|
||||||
|
name: item.name,
|
||||||
|
value: item.name,
|
||||||
|
url: item.html_url,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const nextPaginationToken = page * per_page < responseData.total_count ? page + 1 : undefined;
|
||||||
|
return { results, paginationToken: nextPaginationToken };
|
||||||
|
}
|
Loading…
Reference in a new issue