mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-14 00:24:07 -08:00
filter by http node url
This commit is contained in:
parent
97109b0069
commit
7a072ac729
|
@ -103,6 +103,9 @@ export class WorkflowEntity extends WithTimestampsAndStringId implements IWorkfl
|
||||||
|
|
||||||
@Column({ type: 'simple-array', default: '' })
|
@Column({ type: 'simple-array', default: '' })
|
||||||
webhookURLs: string[];
|
webhookURLs: string[];
|
||||||
|
|
||||||
|
@Column({ type: 'simple-array', default: '' })
|
||||||
|
httpNodeURLs: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,6 +7,7 @@ export class AddWorkflowFilterColumns1730286483664 implements ReversibleMigratio
|
||||||
column('nodeTypes').text,
|
column('nodeTypes').text,
|
||||||
column('nodeNames').text,
|
column('nodeNames').text,
|
||||||
column('webhookURLs').text,
|
column('webhookURLs').text,
|
||||||
|
column('httpNodeURLs').text,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +17,7 @@ export class AddWorkflowFilterColumns1730286483664 implements ReversibleMigratio
|
||||||
'nodeTypes',
|
'nodeTypes',
|
||||||
'nodeNames',
|
'nodeNames',
|
||||||
'webhookURLs',
|
'webhookURLs',
|
||||||
|
'httpNodeURLs',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,6 +105,7 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
|
||||||
nodeTypes: string[] = [],
|
nodeTypes: string[] = [],
|
||||||
nodeName = '',
|
nodeName = '',
|
||||||
webhookURL = '',
|
webhookURL = '',
|
||||||
|
httpNodeURL = '',
|
||||||
) {
|
) {
|
||||||
if (sharedWorkflowIds.length === 0) return { workflows: [], count: 0 };
|
if (sharedWorkflowIds.length === 0) return { workflows: [], count: 0 };
|
||||||
|
|
||||||
|
@ -173,6 +174,10 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
|
||||||
where.webhookURLs = Like(`%${escapeCommas(webhookURL)}%`);
|
where.webhookURLs = Like(`%${escapeCommas(webhookURL)}%`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (httpNodeURL) {
|
||||||
|
where.httpNodeURLs = Like(`%${escapeCommas(httpNodeURL)}%`);
|
||||||
|
}
|
||||||
|
|
||||||
const findManyOptions: FindManyOptions<WorkflowEntity> = {
|
const findManyOptions: FindManyOptions<WorkflowEntity> = {
|
||||||
select: { ...select, id: true },
|
select: { ...select, id: true },
|
||||||
where,
|
where,
|
||||||
|
|
|
@ -61,3 +61,19 @@ export function getEscapedWebhookURLs(workflow: WorkflowEntity): string[] {
|
||||||
|
|
||||||
return webhookURLs;
|
return webhookURLs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getEscapedHttpNodeURLs(workflow: WorkflowEntity): string[] {
|
||||||
|
const webhookURLs: string[] = [];
|
||||||
|
|
||||||
|
for (const node of workflow.nodes) {
|
||||||
|
if (
|
||||||
|
node.type === 'n8n-nodes-base.httpRequest' &&
|
||||||
|
typeof node.parameters.url === 'string' &&
|
||||||
|
node.parameters.url.length > 0
|
||||||
|
) {
|
||||||
|
webhookURLs.push(escapeCommas(node.parameters.url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return webhookURLs;
|
||||||
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ export declare namespace WorkflowRequest {
|
||||||
nodeTypes?: string;
|
nodeTypes?: string;
|
||||||
nodeName?: string;
|
nodeName?: string;
|
||||||
webhookURL?: string;
|
webhookURL?: string;
|
||||||
|
httpNodeURL?: string;
|
||||||
}
|
}
|
||||||
> & {
|
> & {
|
||||||
listQueryOptions: ListQuery.Options;
|
listQueryOptions: ListQuery.Options;
|
||||||
|
|
|
@ -36,6 +36,7 @@ import * as WorkflowHelpers from '@/workflow-helpers';
|
||||||
import {
|
import {
|
||||||
getEncodedCredentialIds,
|
getEncodedCredentialIds,
|
||||||
getEncodedNodeTypes,
|
getEncodedNodeTypes,
|
||||||
|
getEscapedHttpNodeURLs,
|
||||||
getEscapedNodeNames,
|
getEscapedNodeNames,
|
||||||
getEscapedWebhookURLs,
|
getEscapedWebhookURLs,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
@ -71,6 +72,7 @@ export class WorkflowService {
|
||||||
nodeTypes: string[] = [],
|
nodeTypes: string[] = [],
|
||||||
nodeName = '',
|
nodeName = '',
|
||||||
webhookURL = '',
|
webhookURL = '',
|
||||||
|
httpNodeURL = '',
|
||||||
) {
|
) {
|
||||||
const sharedWorkflowIds = await this.workflowSharingService.getSharedWorkflowIds(user, {
|
const sharedWorkflowIds = await this.workflowSharingService.getSharedWorkflowIds(user, {
|
||||||
scopes: ['workflow:read'],
|
scopes: ['workflow:read'],
|
||||||
|
@ -84,6 +86,7 @@ export class WorkflowService {
|
||||||
nodeTypes,
|
nodeTypes,
|
||||||
nodeName,
|
nodeName,
|
||||||
webhookURL,
|
webhookURL,
|
||||||
|
httpNodeURL,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasSharing(workflows)) {
|
if (hasSharing(workflows)) {
|
||||||
|
@ -212,6 +215,7 @@ export class WorkflowService {
|
||||||
nodeTypes: getEncodedNodeTypes(workflow),
|
nodeTypes: getEncodedNodeTypes(workflow),
|
||||||
nodeNames: getEscapedNodeNames(workflow),
|
nodeNames: getEscapedNodeNames(workflow),
|
||||||
webhookURLs: getEscapedWebhookURLs(workflow),
|
webhookURLs: getEscapedWebhookURLs(workflow),
|
||||||
|
httpNodeURLs: getEscapedHttpNodeURLs(workflow),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (tagIds && !config.getEnv('workflowTagsDisabled')) {
|
if (tagIds && !config.getEnv('workflowTagsDisabled')) {
|
||||||
|
|
|
@ -41,6 +41,7 @@ import * as WorkflowHelpers from '@/workflow-helpers';
|
||||||
import {
|
import {
|
||||||
getEncodedCredentialIds,
|
getEncodedCredentialIds,
|
||||||
getEncodedNodeTypes,
|
getEncodedNodeTypes,
|
||||||
|
getEscapedHttpNodeURLs,
|
||||||
getEscapedNodeNames,
|
getEscapedNodeNames,
|
||||||
getEscapedWebhookURLs,
|
getEscapedWebhookURLs,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
@ -125,6 +126,7 @@ export class WorkflowsController {
|
||||||
newWorkflow.nodeTypes = getEncodedNodeTypes(newWorkflow);
|
newWorkflow.nodeTypes = getEncodedNodeTypes(newWorkflow);
|
||||||
newWorkflow.nodeNames = getEscapedNodeNames(newWorkflow);
|
newWorkflow.nodeNames = getEscapedNodeNames(newWorkflow);
|
||||||
newWorkflow.webhookURLs = getEscapedWebhookURLs(newWorkflow);
|
newWorkflow.webhookURLs = getEscapedWebhookURLs(newWorkflow);
|
||||||
|
newWorkflow.httpNodeURLs = getEscapedHttpNodeURLs(newWorkflow);
|
||||||
|
|
||||||
let project: Project | null;
|
let project: Project | null;
|
||||||
const savedWorkflow = await Db.transaction(async (transactionManager) => {
|
const savedWorkflow = await Db.transaction(async (transactionManager) => {
|
||||||
|
@ -213,6 +215,7 @@ export class WorkflowsController {
|
||||||
req.query.nodeTypes?.split(','),
|
req.query.nodeTypes?.split(','),
|
||||||
req.query.nodeName,
|
req.query.nodeName,
|
||||||
req.query.webhookURL,
|
req.query.webhookURL,
|
||||||
|
req.query.httpNodeURL,
|
||||||
);
|
);
|
||||||
|
|
||||||
res.json({ count, data });
|
res.json({ count, data });
|
||||||
|
|
|
@ -111,7 +111,7 @@ describe('POST /workflows', () => {
|
||||||
expect(fromBase64(savedWorkflow.credentialIds[0])).toEqual(credential.id);
|
expect(fromBase64(savedWorkflow.credentialIds[0])).toEqual(credential.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should populate `nodeTypes`', async () => {
|
test('should populate `nodeNames`', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const workflow = makeWorkflow();
|
const workflow = makeWorkflow();
|
||||||
workflow.nodes[0].name = 'Cron,with,commas';
|
workflow.nodes[0].name = 'Cron,with,commas';
|
||||||
|
@ -127,7 +127,7 @@ describe('POST /workflows', () => {
|
||||||
expect(savedWorkflow.nodeNames[0]).toEqual(escapeCommas(workflow.nodes[0].name));
|
expect(savedWorkflow.nodeNames[0]).toEqual(escapeCommas(workflow.nodes[0].name));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should populate `nodeTypes`', async () => {
|
test('should populate `webhookURLs`', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const workflow = makeWorkflow();
|
const workflow = makeWorkflow();
|
||||||
workflow.nodes[0].type = 'n8n-nodes-base.webhook';
|
workflow.nodes[0].type = 'n8n-nodes-base.webhook';
|
||||||
|
@ -144,6 +144,23 @@ describe('POST /workflows', () => {
|
||||||
expect(savedWorkflow.webhookURLs[0]).toEqual(escapeCommas(workflow.nodes[0].parameters.path));
|
expect(savedWorkflow.webhookURLs[0]).toEqual(escapeCommas(workflow.nodes[0].parameters.path));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should populate `httpNodeURLs`', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const workflow = makeWorkflow();
|
||||||
|
workflow.nodes[0].type = 'n8n-nodes-base.httpRequest';
|
||||||
|
workflow.nodes[0].parameters.url = 'foobar';
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await authOwnerAgent.post('/workflows').send(workflow).expect(200);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const savedWorkflow = await getWorkflowById(workflow.id);
|
||||||
|
a.ok(savedWorkflow);
|
||||||
|
|
||||||
|
expect(savedWorkflow.httpNodeURLs).toHaveLength(1);
|
||||||
|
expect(savedWorkflow.httpNodeURLs[0]).toEqual(escapeCommas(workflow.nodes[0].parameters.url));
|
||||||
|
});
|
||||||
|
|
||||||
test('should return scopes on created workflow', async () => {
|
test('should return scopes on created workflow', async () => {
|
||||||
const payload = {
|
const payload = {
|
||||||
name: 'testing',
|
name: 'testing',
|
||||||
|
@ -747,7 +764,7 @@ describe('GET /workflows', () => {
|
||||||
expect(found.usedCredentials).toBeUndefined();
|
expect(found.usedCredentials).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return workflows filtered by node name', async () => {
|
test('should return workflows filtered by webhook url', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const node1 = {
|
const node1 = {
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
|
@ -762,15 +779,18 @@ describe('GET /workflows', () => {
|
||||||
owner,
|
owner,
|
||||||
);
|
);
|
||||||
|
|
||||||
const node2: INode = {
|
const node2 = {
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
name: 'Action Network',
|
name: 'Action Network',
|
||||||
type: 'n8n-nodes-base.webhook',
|
type: 'n8n-nodes-base.webhook',
|
||||||
parameters: { path: 'bar foo' },
|
parameters: { path: 'bar foo' },
|
||||||
typeVersion: 1,
|
typeVersion: 1,
|
||||||
position: [0, 0],
|
position: [0, 0],
|
||||||
};
|
} satisfies INode;
|
||||||
await createWorkflow({ name: 'Second', nodes: [node2] }, owner);
|
await createWorkflow(
|
||||||
|
{ name: 'Second', nodes: [node2], webhookURLs: [escapeCommas(node2.parameters.path)] },
|
||||||
|
owner,
|
||||||
|
);
|
||||||
|
|
||||||
await createWorkflow({ name: 'Third' }, owner);
|
await createWorkflow({ name: 'Third' }, owner);
|
||||||
|
|
||||||
|
@ -811,6 +831,73 @@ describe('GET /workflows', () => {
|
||||||
expect(found.usedCredentials).toBeUndefined();
|
expect(found.usedCredentials).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should return workflows filtered by http node url', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const node1 = {
|
||||||
|
id: uuid(),
|
||||||
|
name: 'Action Network',
|
||||||
|
type: 'n8n-nodes-base.httpRequest',
|
||||||
|
parameters: { url: 'foobar' },
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [0, 0],
|
||||||
|
} satisfies INode;
|
||||||
|
const workflow1 = await createWorkflow(
|
||||||
|
{ name: 'First', nodes: [node1], httpNodeURLs: [escapeCommas(node1.parameters.url)] },
|
||||||
|
owner,
|
||||||
|
);
|
||||||
|
|
||||||
|
const node2 = {
|
||||||
|
id: uuid(),
|
||||||
|
name: 'Action Network',
|
||||||
|
type: 'n8n-nodes-base.httpRequest',
|
||||||
|
parameters: { url: 'bar foo' },
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [0, 0],
|
||||||
|
} satisfies INode;
|
||||||
|
await createWorkflow(
|
||||||
|
{ name: 'Second', nodes: [node2], httpNodeURLs: [escapeCommas(node2.parameters.url)] },
|
||||||
|
owner,
|
||||||
|
);
|
||||||
|
|
||||||
|
await createWorkflow({ name: 'Third' }, owner);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
const response = await authOwnerAgent
|
||||||
|
.get('/workflows')
|
||||||
|
.query(`httpNodeURL=${node1.parameters.url}`)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
const ownerPersonalProject = await projectRepository.getPersonalProjectForUserOrFail(owner.id);
|
||||||
|
expect(response.body.count).toBe(1);
|
||||||
|
expect(response.body.data).toHaveLength(1);
|
||||||
|
expect(response.body.data[0]).toEqual(
|
||||||
|
objectContaining({
|
||||||
|
id: workflow1.id,
|
||||||
|
name: 'First',
|
||||||
|
active: workflow1.active,
|
||||||
|
tags: [],
|
||||||
|
createdAt: workflow1.createdAt.toISOString(),
|
||||||
|
updatedAt: workflow1.updatedAt.toISOString(),
|
||||||
|
versionId: workflow1.versionId,
|
||||||
|
homeProject: {
|
||||||
|
id: ownerPersonalProject.id,
|
||||||
|
name: owner.createPersonalProjectName(),
|
||||||
|
type: ownerPersonalProject.type,
|
||||||
|
},
|
||||||
|
sharedWithProjects: [],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const found = response.body.data.find(
|
||||||
|
(w: ListQuery.Workflow.WithOwnership) => w.name === 'First',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(found.nodes).toBeUndefined();
|
||||||
|
expect(found.sharedWithProjects).toHaveLength(0);
|
||||||
|
expect(found.usedCredentials).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
test('should return workflows with scopes when ?includeScopes=true', async () => {
|
test('should return workflows with scopes when ?includeScopes=true', async () => {
|
||||||
const [member1, member2] = await createManyUsers(2, {
|
const [member1, member2] = await createManyUsers(2, {
|
||||||
role: 'global:member',
|
role: 'global:member',
|
||||||
|
|
Loading…
Reference in a new issue