Skip to content
This repository was archived by the owner on May 6, 2026. It is now read-only.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,10 @@ Personal contacts are CRM entities that represent a person. The entity name is `
Projects are used to manage project tasks. The entity name is `project`.

**Operations**
* *Get one** - returns a detailed view of a single project. The entity is specified by its ID.
* **Get one** - returns a detailed view of a single project. The entity is specified by its ID.
* **Get many** - returns a list of projects. You should use easy query id to specify the filter.
* **Search** - searches for projects with filtering options.
- Query - text search query to filter projects

## Time entries

Expand Down Expand Up @@ -289,6 +291,14 @@ Users are Easy Redmine users. The entity name is `user`.

- **Get one** - returns a detailed view of a single user. The entity is specified by its ID.
- **Get many** - returns a list of entities. You should use easy query id to specify the filter.
- **Search** - searches for users with filtering options.
- Email - search by email address (partial match)
- First name - search by first name (partial match)
- Last name - search by last name (partial match)
- Login - search by login username (partial match)
- Status - filter by user status (integer value)
- Last login time from - filter users who logged in after this date
- Last login time to - filter users who logged in before this date
- **Create** - creates a new user
- Login (required)
- First name (required)
Expand Down
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# 0.5.0 (Unreleased)

- Add get and get-many operations for the products entity.
- Add search operation support for issue, project, user entities with query parameter and additional filtering options.

# 0.4.2 (2025-08-29)

Expand Down
55 changes: 46 additions & 9 deletions nodes/EasyRedmine/EasyRedmine.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,32 @@
import {
IDataObject,
IExecuteFunctions,
ILoadOptionsFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
NodeOperationError,
INodeListSearchResult,
} from 'n8n-workflow';
import { EasyNodeOperationType, EasyNodeResourceType } from './Model';
import { processGetManyOperation } from './operations/GetManyOperation';
import { processGetOneOperation } from './operations/GetOneOperation';
import { addCommentOperation } from './operations/AddCommentOperation';
import { updateOperation } from './operations/UpdateOperation';
import {
processGetManyOperation,
processGetOneOperation,
addCommentOperation,
updateOperation,
createOperation,
processSearchOperation,
} from './operations';
import { IssueFields } from './fields/IssueFields';
import { LeadFields } from './fields/LeadFields';
import { OpportunityFields } from './fields/OpportunityFields';
import { AccountFields } from './fields/AccountFields';
import { PersonalContactFields } from './fields/PersonalContactFields';
import { UserFields } from './fields/UserFields';
import { createOperation } from './operations/CreateOperation';
import { TimeEntryFields } from './fields/TimeEntryFields';
import { AttendanceFields } from './fields/AttendanceFields';
import { loadOptions } from './LoadOptions';
import { EasyRedmineClient } from './client';
import { ProjectFields } from './fields/ProjectFields';

/**
Expand All @@ -44,6 +50,7 @@ export class EasyRedmine implements INodeType {
},
inputs: ['main'],
outputs: ['main'],
usableAsTool: true,
credentials: [
{
name: 'easyRedmineApi',
Expand Down Expand Up @@ -114,7 +121,6 @@ export class EasyRedmine implements INodeType {
displayOptions: {
show: {
resource: [
EasyNodeResourceType.issues,
EasyNodeResourceType.leads,
EasyNodeResourceType.opportunities,
EasyNodeResourceType.accounts,
Expand All @@ -135,6 +141,12 @@ export class EasyRedmine implements INodeType {
value: EasyNodeOperationType.getMany,
action: 'Get many',
},
{
name: 'Search',
description: 'Search entities',
value: EasyNodeOperationType.search,
action: 'Search',
},
{
name: 'Add Comment',
description: 'Add a comment to entity',
Expand Down Expand Up @@ -174,7 +186,7 @@ export class EasyRedmine implements INodeType {
description: 'Whether to return all results or only up to a given limit',
displayOptions: {
show: {
operation: [EasyNodeOperationType.getMany],
operation: [EasyNodeOperationType.getMany, EasyNodeOperationType.search],
},
},
},
Expand All @@ -184,9 +196,12 @@ export class EasyRedmine implements INodeType {
type: 'number',
default: 0,
description: 'Result offset',
typeOptions: {
minValue: 0,
},
displayOptions: {
show: {
operation: [EasyNodeOperationType.getMany],
operation: [EasyNodeOperationType.getMany, EasyNodeOperationType.search],
returnAll: [false],
},
},
Expand All @@ -202,7 +217,7 @@ export class EasyRedmine implements INodeType {
description: 'Max number of results to return',
displayOptions: {
show: {
operation: [EasyNodeOperationType.getMany],
operation: [EasyNodeOperationType.getMany, EasyNodeOperationType.search],
returnAll: [false],
},
},
Expand All @@ -224,6 +239,25 @@ export class EasyRedmine implements INodeType {

methods = {
loadOptions,
listSearch: {
getProjects: async function (
this: ILoadOptionsFunctions,
filter?: string,
paginationToken?: string,
): Promise<INodeListSearchResult> {
const client = new EasyRedmineClient(this, this.helpers);
const projects = (await client.listProjects()).sort((p0, p1) =>
p0.name.localeCompare(p1.name),
);
return {
results: projects.map((project) => ({
name: project.name,
value: project.id,
})),
paginationToken: undefined,
};
},
},
};

async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
Expand All @@ -242,6 +276,9 @@ export class EasyRedmine implements INodeType {
case EasyNodeOperationType.getMany:
responseData = await processGetManyOperation.call(this, resource, itemIndex);
break;
case EasyNodeOperationType.search:
responseData = await processSearchOperation.call(this, resource, itemIndex);
break;
case EasyNodeOperationType.getOne:
responseData = await processGetOneOperation.call(this, resource, itemIndex);
break;
Expand Down
13 changes: 0 additions & 13 deletions nodes/EasyRedmine/LoadOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,19 +122,6 @@ export const loadOptions: {
.sort((a, b) => a.name.localeCompare(b.name));
},

getAccessibleProjects: async function (
this: ILoadOptionsFunctions,
): Promise<INodePropertyOptions[]> {
const client = new EasyRedmineClient(this, this.helpers);
const projects = await client.listProjects();
return projects
.map((project) => ({
name: project.name,
value: project.id,
}))
.sort((a, b) => a.name.localeCompare(b.name));
},

getAvailablePriorities: async function (
this: ILoadOptionsFunctions,
): Promise<INodePropertyOptions[]> {
Expand Down
1 change: 1 addition & 0 deletions nodes/EasyRedmine/Model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export enum EasyNodeOperationType {
getMany = 'get-many',
search = 'search',
getOne = 'get-one',
addComment = 'add-comment',
create = 'create',
Expand Down
23 changes: 23 additions & 0 deletions nodes/EasyRedmine/fields/AccountFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,4 +300,27 @@ export const AccountFields: INodeProperties[] = [
},
options: [...BillingAccountOptions],
},

{
displayName: 'Search Options',
name: 'accountSearchOptions',
type: 'collection',
placeholder: 'Add option',
default: {},
displayOptions: {
show: {
operation: [EasyNodeOperationType.search],
resource: [EasyNodeResourceType.accounts],
},
},
options: [
{
displayName: 'Query',
name: 'query',
type: 'string',
default: '',
description: 'Search query for accounts',
},
],
},
];
29 changes: 29 additions & 0 deletions nodes/EasyRedmine/fields/AttendanceFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ export const AttendanceFields: INodeProperties[] = [
value: EasyNodeOperationType.getMany,
action: 'Get many',
},
{
name: 'Search',
description: 'Search attendance entities',
value: EasyNodeOperationType.search,
action: 'Search',
},
{
name: 'Create',
description: 'Create attendance entity',
Expand Down Expand Up @@ -144,4 +150,27 @@ export const AttendanceFields: INodeProperties[] = [
},
options: [...AttendanceUpdateOptions, ...CommonAttendanceOptions],
},

{
displayName: 'Search Options',
name: 'attendanceSearchOptions',
type: 'collection',
placeholder: 'Add option',
default: {},
displayOptions: {
show: {
operation: [EasyNodeOperationType.search],
resource: [EasyNodeResourceType.attendances],
},
},
options: [
{
displayName: 'Query',
name: 'query',
type: 'string',
default: '',
description: 'Search query for attendances',
},
],
},
];
107 changes: 95 additions & 12 deletions nodes/EasyRedmine/fields/IssueFields.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,7 @@
import { EasyNodeOperationType, EasyNodeResourceType } from '../Model';
import { INodeProperties } from 'n8n-workflow';
import { CustomFieldsOption } from './CustomFields';

const ProjectIdField: INodeProperties = {
displayName: 'Project Name or ID',
name: 'projectId',
type: 'options',
description:
'ID of the project. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
default: '',
typeOptions: {
loadOptionsMethod: 'getAccessibleProjects',
},
};
import { ProjectIdField } from './ProjectIdField';

const CommonIssueFields: INodeProperties[] = [
{
Expand Down Expand Up @@ -109,6 +98,56 @@ const CommonIssueFields: INodeProperties[] = [
];

export const IssueFields: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: [EasyNodeResourceType.issues],
},
},
default: 'get-many',
options: [
{
name: 'Get One',
description: 'Get a single entity',
value: EasyNodeOperationType.getOne,
action: 'Get one',
},
{
name: 'Get Many',
description: 'Get multiple entities',
value: EasyNodeOperationType.getMany,
action: 'Get many',
},
{
name: 'Search',
description: 'Search for multiple issues',
value: EasyNodeOperationType.search,
action: 'Search',
},
{
name: 'Add Comment',
description: 'Add a comment to entity',
value: EasyNodeOperationType.addComment,
action: 'Add comment',
},
{
name: 'Create',
description: 'Create entity',
value: EasyNodeOperationType.create,
action: 'Create',
},
{
name: 'Update',
description: 'Update entity',
value: EasyNodeOperationType.update,
action: 'Update',
},
],
},
{
displayName: 'Issue ID',
name: 'id',
Expand Down Expand Up @@ -207,4 +246,48 @@ export const IssueFields: INodeProperties[] = [
CustomFieldsOption,
],
},

{
displayName: 'Search Fields',
name: 'issueSearchOptions',
type: 'collection',
placeholder: 'Add option',
default: {},
displayOptions: {
show: {
operation: [EasyNodeOperationType.search],
resource: [EasyNodeResourceType.issues],
},
},
options: [
{
displayName: 'Query',
name: 'query',
type: 'string',
default: '',
description: 'Query the name of the issue',
},
{
displayName: 'Assigned To ID',
name: 'assignedToId',
type: 'string',
default: '',
description: 'ID of the user the issue is assigned to',
},
{
displayName: 'Due Date From',
name: 'dueDateFrom',
type: 'string',
default: '',
placeholder: '2025-01-01',
},
{
displayName: 'Due Date To',
name: 'dueDateTo',
type: 'string',
default: '',
placeholder: '2025-01-31',
},
],
},
];
Loading