mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
feat: Only show workflows shared with you in the overview page (#14773)
This commit is contained in:
@@ -10,6 +10,10 @@ import { ProjectRelationRepository } from '@/databases/repositories/project-rela
|
||||
import { SharedWorkflowRepository } from '@/databases/repositories/shared-workflow.repository';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
|
||||
export type ShareWorkflowOptions =
|
||||
| { scopes: Scope[]; projectId?: string }
|
||||
| { projectRoles: ProjectRole[]; workflowRoles: WorkflowSharingRole[]; projectId?: string };
|
||||
|
||||
@Service()
|
||||
export class WorkflowSharingService {
|
||||
constructor(
|
||||
@@ -26,12 +30,8 @@ export class WorkflowSharingService {
|
||||
*
|
||||
* Returns all IDs if user has the 'workflow:read' global scope.
|
||||
*/
|
||||
async getSharedWorkflowIds(
|
||||
user: User,
|
||||
options:
|
||||
| { scopes: Scope[]; projectId?: string }
|
||||
| { projectRoles: ProjectRole[]; workflowRoles: WorkflowSharingRole[]; projectId?: string },
|
||||
): Promise<string[]> {
|
||||
|
||||
async getSharedWorkflowIds(user: User, options: ShareWorkflowOptions): Promise<string[]> {
|
||||
const { projectId } = options;
|
||||
|
||||
if (user.hasGlobalScope('workflow:read')) {
|
||||
@@ -90,4 +90,20 @@ export class WorkflowSharingService {
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
async getOwnedWorkflowsInPersonalProject(user: User): Promise<string[]> {
|
||||
const sharedWorkflows = await this.sharedWorkflowRepository.find({
|
||||
select: ['workflowId'],
|
||||
where: {
|
||||
role: 'workflow:owner',
|
||||
project: {
|
||||
projectRelations: {
|
||||
userId: user.id,
|
||||
role: 'project:personalOwner',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return sharedWorkflows.map(({ workflowId }) => workflowId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,10 +73,24 @@ export class WorkflowService {
|
||||
let count;
|
||||
let workflows;
|
||||
let workflowsAndFolders: WorkflowFolderUnionFull[] = [];
|
||||
let sharedWorkflowIds: string[] = [];
|
||||
let isPersonalProject = false;
|
||||
|
||||
const sharedWorkflowIds = await this.workflowSharingService.getSharedWorkflowIds(user, {
|
||||
scopes: ['workflow:read'],
|
||||
});
|
||||
if (options?.filter?.projectId) {
|
||||
const projects = await this.projectService.getProjectRelationsForUser(user);
|
||||
isPersonalProject = !!projects.find(
|
||||
(p) => p.project.id === options.filter?.projectId && p.project.type === 'personal',
|
||||
);
|
||||
}
|
||||
|
||||
if (isPersonalProject) {
|
||||
sharedWorkflowIds =
|
||||
await this.workflowSharingService.getOwnedWorkflowsInPersonalProject(user);
|
||||
} else {
|
||||
sharedWorkflowIds = await this.workflowSharingService.getSharedWorkflowIds(user, {
|
||||
scopes: ['workflow:read'],
|
||||
});
|
||||
}
|
||||
|
||||
if (includeFolders) {
|
||||
[workflowsAndFolders, count] = await this.workflowRepository.getWorkflowsAndFoldersWithCount(
|
||||
|
||||
@@ -885,6 +885,56 @@ describe('GET /workflows', () => {
|
||||
expect(response2.body.data).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('should filter by personal project and return only workflows where the user is owner', async () => {
|
||||
const workflow = await createWorkflow({ name: 'First' }, owner);
|
||||
const workflow2 = await createWorkflow({ name: 'Second' }, member);
|
||||
await shareWorkflowWithUsers(workflow2, [owner]);
|
||||
const pp = await Container.get(ProjectRepository).getPersonalProjectForUserOrFail(owner.id);
|
||||
|
||||
const response1 = await authOwnerAgent
|
||||
.get('/workflows')
|
||||
.query(`filter={ "projectId": "${pp.id}" }`)
|
||||
.expect(200);
|
||||
|
||||
expect(response1.body.data).toHaveLength(1);
|
||||
expect(response1.body.data[0].id).toBe(workflow.id);
|
||||
|
||||
const response2 = await authOwnerAgent
|
||||
.get('/workflows')
|
||||
.query('filter={ "projectId": "Non-Existing Project ID" }')
|
||||
.expect(200);
|
||||
|
||||
expect(response2.body.data).toHaveLength(0);
|
||||
|
||||
const response3 = await authOwnerAgent.get('/workflows').query('filter={}').expect(200);
|
||||
expect(response3.body.data).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('should filter by personal project and return only workflows where the user is member', async () => {
|
||||
const workflow = await createWorkflow({ name: 'First' }, member);
|
||||
const workflow2 = await createWorkflow({ name: 'Second' }, owner);
|
||||
await shareWorkflowWithUsers(workflow2, [member]);
|
||||
const pp = await Container.get(ProjectRepository).getPersonalProjectForUserOrFail(member.id);
|
||||
|
||||
const response1 = await authMemberAgent
|
||||
.get('/workflows')
|
||||
.query(`filter={ "projectId": "${pp.id}" }`)
|
||||
.expect(200);
|
||||
|
||||
expect(response1.body.data).toHaveLength(1);
|
||||
expect(response1.body.data[0].id).toBe(workflow.id);
|
||||
|
||||
const response2 = await authMemberAgent
|
||||
.get('/workflows')
|
||||
.query('filter={ "projectId": "Non-Existing Project ID" }')
|
||||
.expect(200);
|
||||
|
||||
expect(response2.body.data).toHaveLength(0);
|
||||
|
||||
const response3 = await authMemberAgent.get('/workflows').query('filter={}').expect(200);
|
||||
expect(response3.body.data).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('should filter workflows by parentFolderId', async () => {
|
||||
const pp = await Container.get(ProjectRepository).getPersonalProjectForUserOrFail(owner.id);
|
||||
|
||||
@@ -911,21 +961,6 @@ describe('GET /workflows', () => {
|
||||
expect(response2.body.data).toHaveLength(1);
|
||||
expect(response2.body.data[0].id).toBe(workflow2.id);
|
||||
});
|
||||
|
||||
test('should return homeProject when filtering workflows by projectId', async () => {
|
||||
const workflow = await createWorkflow({ name: 'First' }, owner);
|
||||
await shareWorkflowWithUsers(workflow, [member]);
|
||||
const pp = await getPersonalProject(member);
|
||||
|
||||
const response = await authMemberAgent
|
||||
.get('/workflows')
|
||||
.query(`filter={ "projectId": "${pp.id}" }`)
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.data).toHaveLength(1);
|
||||
expect(response.body.data[0].id).toBe(workflow.id);
|
||||
expect(response.body.data[0].homeProject).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('select', () => {
|
||||
@@ -1724,8 +1759,7 @@ describe('GET /workflows?includeFolders=true', () => {
|
||||
});
|
||||
|
||||
test('should return homeProject when filtering workflows and folders by projectId', async () => {
|
||||
const workflow = await createWorkflow({ name: 'First' }, owner);
|
||||
await shareWorkflowWithUsers(workflow, [member]);
|
||||
const workflow = await createWorkflow({ name: 'First' }, member);
|
||||
const pp = await getPersonalProject(member);
|
||||
const folder = await createFolder(pp, {
|
||||
name: 'First Folder',
|
||||
|
||||
@@ -136,12 +136,7 @@ const actions = computed(() => {
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
workflowPermissions.value.update &&
|
||||
showFolders.value &&
|
||||
!props.readOnly &&
|
||||
!isSomeoneElsesWorkflow.value
|
||||
) {
|
||||
if (workflowPermissions.value.update && showFolders.value && !props.readOnly) {
|
||||
items.push({
|
||||
label: locale.baseText('folders.actions.moveToFolder'),
|
||||
value: WORKFLOW_LIST_ITEM_ACTIONS.MOVE_TO_FOLDER,
|
||||
|
||||
Reference in New Issue
Block a user