feat(Github Node): Introduce get issues operation on user resource (#16951)

This commit is contained in:
Ioannis Canellos 2025-10-06 10:56:00 +03:00 committed by GitHub
parent 316dafafc0
commit 065bbcfdc7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 271 additions and 2 deletions

View File

@ -324,6 +324,12 @@ export class Github implements INodeType {
description: 'Returns the repositories of a user',
action: "Get a user's repositories",
},
{
name: 'Get Issues',
value: 'getIssues',
description: 'Returns the issues assigned to the user',
action: "Get a user's issues",
},
{
name: 'Invite',
value: 'invite',
@ -548,7 +554,8 @@ export class Github implements INodeType {
],
displayOptions: {
hide: {
operation: ['invite'],
resource: ['user'],
operation: ['invite', 'getIssues'],
},
},
},
@ -2093,6 +2100,147 @@ export class Github implements INodeType {
default: 50,
description: 'Max number of results to return',
},
// ----------------------------------
// user:getIssues
// ----------------------------------
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
resource: ['user'],
operation: ['getIssues'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
resource: ['user'],
operation: ['getIssues'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
maxValue: 100,
},
default: 50,
description: 'Max number of results to return',
},
{
displayName: 'Filters',
name: 'getUserIssuesFilters',
type: 'collection',
typeOptions: {
multipleValueButtonText: 'Add Filter',
},
displayOptions: {
show: {
resource: ['user'],
operation: ['getIssues'],
},
},
default: {},
options: [
{
displayName: 'Mentioned',
name: 'mentioned',
type: 'string',
default: '',
description: 'Return only issues in which a specific user was mentioned',
},
{
displayName: 'Labels',
name: 'labels',
type: 'string',
default: '',
description:
'Return only issues with the given labels. Multiple labels can be separated by comma.',
},
{
displayName: 'Updated Since',
name: 'since',
type: 'dateTime',
default: '',
description: 'Return only issues updated at or after this time',
},
{
displayName: 'State',
name: 'state',
type: 'options',
options: [
{
name: 'All',
value: 'all',
description: 'Returns issues with any state',
},
{
name: 'Closed',
value: 'closed',
description: 'Return issues with "closed" state',
},
{
name: 'Open',
value: 'open',
description: 'Return issues with "open" state',
},
],
default: 'open',
description: 'The state to set',
},
{
displayName: 'Sort',
name: 'sort',
type: 'options',
options: [
{
name: 'Created',
value: 'created',
description: 'Sort by created date',
},
{
name: 'Updated',
value: 'updated',
description: 'Sort by updated date',
},
{
name: 'Comments',
value: 'comments',
description: 'Sort by comments',
},
],
default: 'created',
description: 'The order the issues should be returned in',
},
{
displayName: 'Direction',
name: 'direction',
type: 'options',
options: [
{
name: 'Ascending',
value: 'asc',
description: 'Sort in ascending order',
},
{
name: 'Descending',
value: 'desc',
description: 'Sort in descending order',
},
],
default: 'desc',
description: 'The sort order',
},
],
},
],
};
@ -2158,6 +2306,7 @@ export class Github implements INodeType {
'repository:listPopularPaths',
'repository:listReferrers',
'user:getRepositories',
'user:getIssues',
'release:getAll',
'review:getAll',
'organization:getRepositories',
@ -2231,7 +2380,7 @@ export class Github implements INodeType {
qs = {};
let owner = '';
if (fullOperation !== 'user:invite') {
if (fullOperation !== 'user:invite' && fullOperation !== 'user:getIssues') {
// Request the parameters which almost all operations need
owner = this.getNodeParameter('owner', i, '', { extractValue: true }) as string;
}
@ -2239,6 +2388,7 @@ export class Github implements INodeType {
let repository = '';
if (
fullOperation !== 'user:getRepositories' &&
fullOperation !== 'user:getIssues' &&
fullOperation !== 'user:invite' &&
fullOperation !== 'organization:getRepositories'
) {
@ -2629,6 +2779,20 @@ export class Github implements INodeType {
returnAll = this.getNodeParameter('returnAll', 0);
if (!returnAll) {
qs.per_page = this.getNodeParameter('limit', 0);
}
} else if (operation === 'getIssues') {
// ----------------------------------
// getIssues
// ----------------------------------
requestMethod = 'GET';
endpoint = '/issues';
qs = this.getNodeParameter('getUserIssuesFilters', i, {}) as IDataObject;
returnAll = this.getNodeParameter('returnAll', 0);
if (!returnAll) {
qs.per_page = this.getNodeParameter('limit', 0);
}

View File

@ -594,4 +594,109 @@ describe('Test Github Node', () => {
);
});
});
describe('User Operations', () => {
let githubNode: Github;
let mockExecutionContext: any;
beforeEach(() => {
githubNode = new Github();
mockExecutionContext = {
getNode: jest.fn().mockReturnValue({ name: 'Github' }),
getNodeParameter: jest.fn(),
getInputData: jest.fn().mockReturnValue([{ json: {} }]),
continueOnFail: jest.fn().mockReturnValue(false),
getCredentials: jest.fn().mockResolvedValue({
server: 'https://api.github.com',
user: 'test',
accessToken: 'test',
}),
helpers: {
returnJsonArray: jest.fn().mockReturnValue([{ json: {} }]),
requestWithAuthentication: jest.fn().mockResolvedValue({}),
constructExecutionMetaData: jest.fn().mockReturnValue([{ json: {} }]),
},
};
});
it('should fetch open issues by default (user:getIssues)', async () => {
mockExecutionContext.getNodeParameter.mockImplementation((parameterName: string) => {
if (parameterName === 'resource') return 'user';
if (parameterName === 'operation') return 'getIssues';
if (parameterName === 'getUserIssuesFilters') return {};
if (parameterName === 'returnAll') return true;
if (parameterName === 'authentication') return 'accessToken';
return '';
});
mockExecutionContext.helpers.requestWithAuthentication.mockResolvedValue({
body: [
{ id: 1, title: 'Issue 1', state: 'open' },
{ id: 2, title: 'Issue 2', state: 'open' },
],
headers: {},
});
await githubNode.execute.call(mockExecutionContext);
expect(mockExecutionContext.helpers.requestWithAuthentication).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
method: 'GET',
uri: 'https://api.github.com/issues',
qs: expect.not.objectContaining({ state: 'closed' }),
}),
);
});
it('should fetch closed issues when state filter is set to closed (user:getIssues)', async () => {
mockExecutionContext.getNodeParameter.mockImplementation((parameterName: string) => {
if (parameterName === 'resource') return 'user';
if (parameterName === 'operation') return 'getIssues';
if (parameterName === 'getUserIssuesFilters') return { state: 'closed' };
if (parameterName === 'returnAll') return true;
if (parameterName === 'authentication') return 'accessToken';
return '';
});
mockExecutionContext.helpers.requestWithAuthentication.mockResolvedValue({
body: [{ id: 3, title: 'Issue 3', state: 'closed' }],
headers: {},
});
await githubNode.execute.call(mockExecutionContext);
expect(mockExecutionContext.helpers.requestWithAuthentication).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
method: 'GET',
uri: 'https://api.github.com/issues',
qs: expect.objectContaining({ state: 'closed' }),
}),
);
});
it('should fetch issues with a specific label (user:getIssues)', async () => {
mockExecutionContext.getNodeParameter.mockImplementation((parameterName: string) => {
if (parameterName === 'resource') return 'user';
if (parameterName === 'operation') return 'getIssues';
if (parameterName === 'getUserIssuesFilters') return { labels: 'bug' };
if (parameterName === 'returnAll') return true;
if (parameterName === 'authentication') return 'accessToken';
return '';
});
mockExecutionContext.helpers.requestWithAuthentication.mockResolvedValue({
body: [{ id: 4, title: 'Issue 4', state: 'open', labels: ['bug'] }],
headers: {},
});
await githubNode.execute.call(mockExecutionContext);
expect(mockExecutionContext.helpers.requestWithAuthentication).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
method: 'GET',
uri: 'https://api.github.com/issues',
qs: expect.objectContaining({ labels: 'bug' }),
}),
);
});
});
});