feat: Only show workflows shared with you in the overview page (#14773)

This commit is contained in:
Ricardo Espinoza
2025-04-28 13:42:05 -04:00
committed by GitHub
parent cdd3ce4cd4
commit eb465763cf
4 changed files with 91 additions and 32 deletions

View File

@@ -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);
}
}

View File

@@ -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(

View File

@@ -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',

View File

@@ -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,