mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 05:29:44 +02:00
refactor: Drop called-by column and align trigger view controls
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
839730bba7
commit
d72f7df53b
|
|
@ -1,6 +1,6 @@
|
|||
import { DatabaseConfig } from '@n8n/config';
|
||||
import { Service } from '@n8n/di';
|
||||
import { DataSource, EntityManager, In, IsNull, LessThan, Repository, Not } from '@n8n/typeorm';
|
||||
import { DataSource, EntityManager, IsNull, LessThan, Repository, Not } from '@n8n/typeorm';
|
||||
|
||||
import { WorkflowDependency } from '../entities';
|
||||
|
||||
|
|
@ -159,17 +159,4 @@ export class WorkflowDependencyRepository extends Repository<WorkflowDependency>
|
|||
const { tablePrefix } = this.databaseConfig;
|
||||
return this.manager.connection.driver.escape(`${tablePrefix}${name}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the caller → callee edges of workflows that reference any of the
|
||||
* given workflows via an Execute Workflow node. `workflowId` is the caller,
|
||||
* `dependencyKey` the called workflow's id.
|
||||
*/
|
||||
async findCallersOfWorkflows(workflowIds: string[]) {
|
||||
if (workflowIds.length === 0) return [];
|
||||
return await this.find({
|
||||
select: ['workflowId', 'dependencyKey'],
|
||||
where: { dependencyType: 'workflowCall', dependencyKey: In(workflowIds) },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { WebhookEntity } from '@n8n/db';
|
|||
import type {
|
||||
Project,
|
||||
SharedWorkflowRepository,
|
||||
WorkflowDependencyRepository,
|
||||
WorkflowEntity,
|
||||
WorkflowRepository,
|
||||
} from '@n8n/db';
|
||||
|
|
@ -29,13 +28,11 @@ describe('WebhooksController', () => {
|
|||
const webhookService = mock<WebhookService>();
|
||||
const workflowRepository = mock<WorkflowRepository>();
|
||||
const sharedWorkflowRepository = mock<SharedWorkflowRepository>();
|
||||
const workflowDependencyRepository = mock<WorkflowDependencyRepository>();
|
||||
const nodeTypes = mock<NodeTypes>();
|
||||
const controller = new WebhooksController(
|
||||
webhookService,
|
||||
workflowRepository,
|
||||
sharedWorkflowRepository,
|
||||
workflowDependencyRepository,
|
||||
nodeTypes,
|
||||
);
|
||||
|
||||
|
|
@ -46,7 +43,6 @@ describe('WebhooksController', () => {
|
|||
workflowRepository.findPublishedWithActiveVersionNodes.mockResolvedValue([]);
|
||||
workflowRepository.findUnpublishedWithNodes.mockResolvedValue([]);
|
||||
sharedWorkflowRepository.findOwnerProjectsByWorkflowIds.mockResolvedValue(new Map());
|
||||
workflowDependencyRepository.findCallersOfWorkflows.mockResolvedValue([]);
|
||||
vi.mocked(getTriggerKinds).mockReturnValue(new Map());
|
||||
});
|
||||
|
||||
|
|
@ -106,7 +102,6 @@ describe('WebhooksController', () => {
|
|||
nodeType: 'n8n-nodes-base.webhook',
|
||||
isActive: true,
|
||||
project: { name: 'My Project', type: 'team', icon: { type: 'icon', value: 'folder' } },
|
||||
calledBy: [],
|
||||
},
|
||||
{
|
||||
kind: 'webhook',
|
||||
|
|
@ -118,7 +113,6 @@ describe('WebhooksController', () => {
|
|||
nodeType: undefined,
|
||||
isActive: true,
|
||||
project: undefined,
|
||||
calledBy: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
@ -152,14 +146,6 @@ describe('WebhooksController', () => {
|
|||
['n3', 'persisted'],
|
||||
]),
|
||||
);
|
||||
// Duplicate edges: draft and published versions index separately
|
||||
workflowDependencyRepository.findCallersOfWorkflows.mockResolvedValue([
|
||||
{ workflowId: 'caller1', dependencyKey: 'wf5' },
|
||||
{ workflowId: 'caller1', dependencyKey: 'wf5' },
|
||||
] as never);
|
||||
workflowRepository.findByIds.mockResolvedValue([
|
||||
{ id: 'caller1', name: 'Caller Workflow' } as WorkflowEntity,
|
||||
]);
|
||||
|
||||
const result = await controller.getAll();
|
||||
|
||||
|
|
@ -172,7 +158,6 @@ describe('WebhooksController', () => {
|
|||
nodeType: 'n8n-nodes-base.scheduleTrigger',
|
||||
isActive: true,
|
||||
project: undefined,
|
||||
calledBy: [{ id: 'caller1', name: 'Caller Workflow' }],
|
||||
},
|
||||
{
|
||||
kind: 'trigger',
|
||||
|
|
@ -182,7 +167,6 @@ describe('WebhooksController', () => {
|
|||
nodeType: 'n8n-nodes-base.executeWorkflowTrigger',
|
||||
isActive: true,
|
||||
project: undefined,
|
||||
calledBy: [{ id: 'caller1', name: 'Caller Workflow' }],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
@ -227,7 +211,6 @@ describe('WebhooksController', () => {
|
|||
nodeType: 'n8n-nodes-base.scheduleTrigger',
|
||||
isActive: false,
|
||||
project: undefined,
|
||||
calledBy: [],
|
||||
},
|
||||
{
|
||||
kind: 'webhook',
|
||||
|
|
@ -239,7 +222,6 @@ describe('WebhooksController', () => {
|
|||
nodeType: 'n8n-nodes-base.webhook',
|
||||
isActive: false,
|
||||
project: undefined,
|
||||
calledBy: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
import type { WorkflowEntity } from '@n8n/db';
|
||||
import {
|
||||
SharedWorkflowRepository,
|
||||
WorkflowDependencyRepository,
|
||||
WorkflowRepository,
|
||||
} from '@n8n/db';
|
||||
import { SharedWorkflowRepository, WorkflowRepository } from '@n8n/db';
|
||||
import { Get, GlobalScope, Post, RestController } from '@n8n/decorators';
|
||||
import { Request } from 'express';
|
||||
import get from 'lodash/get';
|
||||
|
|
@ -35,7 +31,6 @@ export class WebhooksController {
|
|||
private readonly webhookService: WebhookService,
|
||||
private readonly workflowRepository: WorkflowRepository,
|
||||
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
|
||||
private readonly workflowDependencyRepository: WorkflowDependencyRepository,
|
||||
private readonly nodeTypes: NodeTypes,
|
||||
) {}
|
||||
|
||||
|
|
@ -52,7 +47,6 @@ export class WebhooksController {
|
|||
const workflowIds = [...new Set(rows.map((row) => row.workflowId))];
|
||||
const ownerProjects =
|
||||
await this.sharedWorkflowRepository.findOwnerProjectsByWorkflowIds(workflowIds);
|
||||
const callersByCallee = await this.getCallersByCallee(workflowIds);
|
||||
|
||||
return rows.map((row) => {
|
||||
const project = ownerProjects.get(row.workflowId);
|
||||
|
|
@ -61,34 +55,10 @@ export class WebhooksController {
|
|||
project: project
|
||||
? { name: project.name, type: project.type, icon: project.icon }
|
||||
: undefined,
|
||||
calledBy: callersByCallee.get(row.workflowId) ?? [],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** Workflows calling each given workflow via an Execute Workflow node. */
|
||||
private async getCallersByCallee(workflowIds: string[]) {
|
||||
const edges = await this.workflowDependencyRepository.findCallersOfWorkflows(workflowIds);
|
||||
const callerIds = [...new Set(edges.map((edge) => edge.workflowId))];
|
||||
const callers = await this.workflowRepository.findByIds(callerIds, { fields: ['id', 'name'] });
|
||||
const callerNames = new Map(callers.map((workflow) => [workflow.id, workflow.name]));
|
||||
|
||||
// Draft and published versions index separately, so dedupe callers per callee
|
||||
const callersByCallee = new Map<string, Map<string, string>>();
|
||||
for (const edge of edges) {
|
||||
const perCallee = callersByCallee.get(edge.dependencyKey) ?? new Map<string, string>();
|
||||
perCallee.set(edge.workflowId, callerNames.get(edge.workflowId) ?? edge.workflowId);
|
||||
callersByCallee.set(edge.dependencyKey, perCallee);
|
||||
}
|
||||
|
||||
return new Map(
|
||||
[...callersByCallee.entries()].map(([calleeId, perCallee]) => [
|
||||
calleeId,
|
||||
[...perCallee.entries()].map(([id, name]) => ({ id, name })),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
private async getActiveWebhooks(): Promise<TriggerRow[]> {
|
||||
const registered = await this.webhookService.findAll();
|
||||
const workflowIds = [...new Set(registered.map((webhook) => webhook.workflowId))];
|
||||
|
|
|
|||
|
|
@ -5585,7 +5585,6 @@
|
|||
"settings.webhooks.column.project": "Project",
|
||||
"settings.webhooks.column.node": "Node",
|
||||
"settings.webhooks.column.calls": "Calls",
|
||||
"settings.webhooks.column.calledBy": "Called by",
|
||||
"settings.webhooks.copy.tooltip": "Copy webhook URL",
|
||||
"settings.webhooks.copy.success": "Webhook URL copied",
|
||||
"settings.webhooks.empty.title": "No triggers",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ export type InstanceWebhook = {
|
|||
type: 'personal' | 'team';
|
||||
icon: { type: 'emoji' | 'icon'; value: string } | null;
|
||||
};
|
||||
calledBy: Array<{ id: string; name: string }>;
|
||||
};
|
||||
|
||||
export const findWebhook = async (
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
N8nInput,
|
||||
N8nOption,
|
||||
N8nSelect,
|
||||
N8nTag,
|
||||
N8nText,
|
||||
} from '@n8n/design-system';
|
||||
import type { IconOrEmoji, TableHeader } from '@n8n/design-system';
|
||||
|
|
@ -71,12 +70,6 @@ const headers = computed<Array<TableHeader<WebhookRow>>>(() => [
|
|||
width: 90,
|
||||
align: 'end',
|
||||
},
|
||||
{
|
||||
title: i18n.baseText('settings.webhooks.column.calledBy'),
|
||||
key: 'calledBy',
|
||||
width: 200,
|
||||
disableSort: true,
|
||||
},
|
||||
]);
|
||||
|
||||
const nodeTypeFilter = ref<string[]>([]);
|
||||
|
|
@ -104,14 +97,9 @@ const filteredItems = computed(() => {
|
|||
}
|
||||
if (term) {
|
||||
items = items.filter((row) =>
|
||||
[
|
||||
row.path ?? '',
|
||||
row.workflowName ?? '',
|
||||
row.node,
|
||||
row.method ?? '',
|
||||
row.projectName,
|
||||
...row.calledBy.map((caller) => caller.name),
|
||||
].some((value) => value.toLowerCase().includes(term)),
|
||||
[row.path ?? '', row.workflowName ?? '', row.node, row.method ?? '', row.projectName].some(
|
||||
(value) => value.toLowerCase().includes(term),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -166,13 +154,6 @@ const getPathAbbreviation = (path: string) => {
|
|||
return shortened === path ? path : `${shortened}…`;
|
||||
};
|
||||
|
||||
const MAX_CALLER_PILLS = 3;
|
||||
const getOverflowCallers = (row: WebhookRow) =>
|
||||
row.calledBy
|
||||
.slice(MAX_CALLER_PILLS)
|
||||
.map((caller) => caller.name)
|
||||
.join(', ');
|
||||
|
||||
const FLASH_DURATION_MS = 2000;
|
||||
const flashedKeys = ref(new Set<string>());
|
||||
const rowKey = (row: { workflowId: string; node: string; method?: string; path?: string }) =>
|
||||
|
|
@ -262,6 +243,7 @@ onMounted(async () => {
|
|||
multiple
|
||||
collapse-tags
|
||||
clearable
|
||||
size="medium"
|
||||
:placeholder="i18n.baseText('settings.webhooks.filter.nodeType')"
|
||||
data-testid="webhooks-node-type-filter"
|
||||
:class="$style.nodeTypeFilter"
|
||||
|
|
@ -275,6 +257,7 @@ onMounted(async () => {
|
|||
</N8nSelect>
|
||||
<N8nInput
|
||||
v-model="search"
|
||||
size="medium"
|
||||
:placeholder="i18n.baseText('settings.webhooks.search.placeholder')"
|
||||
clearable
|
||||
data-testid="webhooks-search"
|
||||
|
|
@ -341,25 +324,6 @@ onMounted(async () => {
|
|||
<N8nText :class="$style.ellipsis">{{ item.node }}</N8nText>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #[`item.calledBy`]="{ item }">
|
||||
<div v-if="item.calledBy.length > 0" :class="$style.pillCell">
|
||||
<RouterLink
|
||||
v-for="caller in item.calledBy.slice(0, MAX_CALLER_PILLS)"
|
||||
:key="caller.id"
|
||||
:to="getWorkflowLink(caller.id)"
|
||||
target="_blank"
|
||||
>
|
||||
<N8nTag :text="caller.name" />
|
||||
</RouterLink>
|
||||
<N8nTag
|
||||
v-if="item.calledBy.length > MAX_CALLER_PILLS"
|
||||
:text="`+${item.calledBy.length - MAX_CALLER_PILLS}`"
|
||||
:clickable="false"
|
||||
:title="getOverflowCallers(item)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</N8nDataTableServer>
|
||||
</section>
|
||||
</template>
|
||||
|
|
@ -392,6 +356,8 @@ onMounted(async () => {
|
|||
.controls {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: var(--spacing--2xs);
|
||||
}
|
||||
|
||||
.search {
|
||||
|
|
@ -426,12 +392,6 @@ onMounted(async () => {
|
|||
gap: var(--spacing--3xs);
|
||||
}
|
||||
|
||||
.pillCell {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing--3xs);
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user