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:
Marcus 2023-02-15 13:12:28 +01:00 committed by GitHub
parent 0cf45bc4c8
commit a3d8fac73a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 208 additions and 18 deletions

View file

@ -2,7 +2,7 @@ import type { OptionsWithUri } from 'request';
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';
/**
@ -10,7 +10,7 @@ import { NodeApiError, NodeOperationError } from 'n8n-workflow';
*
*/
export async function githubApiRequest(
this: IHookFunctions | IExecuteFunctions,
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
method: string,
endpoint: string,
body: object,

View file

@ -9,6 +9,7 @@ import type {
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
import { githubApiRequest } from './GenericFunctions';
import { getRepositories, getUsers } from './SearchFunctions';
export class GithubTrigger implements INodeType {
description: INodeTypeDescription = {
@ -73,20 +74,111 @@ export class GithubTrigger implements INodeType {
{
displayName: 'Repository Owner',
name: 'owner',
type: 'string',
default: '',
type: 'resourceLocator',
default: { mode: 'list', value: '' },
required: true,
placeholder: 'n8n-io',
description: 'Owner of the repsitory',
modes: [
{
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',
name: 'repository',
type: 'string',
default: '',
type: 'resourceLocator',
default: { mode: 'list', value: '' },
required: true,
placeholder: 'n8n',
description: 'The name of the repsitory',
modes: [
{
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',
@ -343,7 +435,6 @@ export class GithubTrigger implements INodeType {
],
};
// @ts-ignore (because of request)
webhookMethods = {
default: {
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
const owner = this.getNodeParameter('owner') as string;
const repository = this.getNodeParameter('repository') as string;
const owner = this.getNodeParameter('owner', '', { extractValue: true }) as string;
const repository = this.getNodeParameter('repository', '', {
extractValue: true,
}) as string;
const endpoint = `/repos/${owner}/${repository}/hooks/${webhookData.webhookId}`;
try {
@ -373,7 +466,6 @@ export class GithubTrigger implements INodeType {
// Some error occured
throw error;
}
// If it did not error then the webhook exists
return true;
},
@ -387,8 +479,10 @@ export class GithubTrigger implements INodeType {
);
}
const owner = this.getNodeParameter('owner') as string;
const repository = this.getNodeParameter('repository') as string;
const owner = this.getNodeParameter('owner', '', { extractValue: true }) as string;
const repository = this.getNodeParameter('repository', '', {
extractValue: true,
}) as string;
const events = this.getNodeParameter('events', []);
const endpoint = `/repos/${owner}/${repository}/hooks`;
@ -455,8 +549,10 @@ export class GithubTrigger implements INodeType {
const webhookData = this.getWorkflowStaticData('node');
if (webhookData.webhookId !== undefined) {
const owner = this.getNodeParameter('owner') as string;
const repository = this.getNodeParameter('repository') as string;
const owner = this.getNodeParameter('owner', '', { extractValue: true }) as string;
const repository = this.getNodeParameter('repository', '', {
extractValue: true,
}) as string;
const endpoint = `/repos/${owner}/${repository}/hooks/${webhookData.webhookId}`;
const body = {};
@ -477,6 +573,13 @@ export class GithubTrigger implements INodeType {
},
};
methods = {
listSearch: {
getUsers,
getRepositories,
},
};
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
const bodyData = this.getBodyData();

View 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 };
}