chore: Custom Roles to manage permissions [ UI ] (#9865)

In admin settings, this Pr will add the UI for managing custom roles (
ref: https://github.com/chatwoot/chatwoot/pull/9995 ). It also handles
the routing logic changes to accommodate fine-tuned permissions.

---------

Co-authored-by: Pranav <pranavrajs@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
Sojan Jose
2024-09-17 11:40:11 -07:00
committed by GitHub
parent fba73c7186
commit 58e78621ba
74 changed files with 2423 additions and 558 deletions

View File

@@ -3,6 +3,7 @@ import {
getCurrentAccount,
getUserPermissions,
hasPermissions,
filterItemsByPermission,
} from '../permissionsHelper';
describe('#getCurrentAccount', () => {
@@ -105,3 +106,113 @@ describe('buildPermissionsFromRouter', () => {
}).toThrow("The route doesn't have the required permissions defined");
});
});
describe('filterItemsByPermission', () => {
const items = {
item1: { name: 'Item 1', permissions: ['agent', 'administrator'] },
item2: {
name: 'Item 2',
permissions: [
'conversation_manage',
'conversation_unassigned_manage',
'conversation_participating_manage',
],
},
item3: { name: 'Item 3', permissions: ['contact_manage'] },
item4: { name: 'Item 4', permissions: ['report_manage'] },
item5: { name: 'Item 5', permissions: ['knowledge_base_manage'] },
item6: {
name: 'Item 6',
permissions: [
'agent',
'administrator',
'conversation_manage',
'conversation_unassigned_manage',
'conversation_participating_manage',
'contact_manage',
'report_manage',
'knowledge_base_manage',
],
},
item7: { name: 'Item 7', permissions: [] },
};
const getPermissions = item => item.permissions;
it('filters items based on user permissions', () => {
const userPermissions = ['agent', 'contact_manage', 'report_manage'];
const result = filterItemsByPermission(
items,
userPermissions,
getPermissions
);
expect(result).toHaveLength(5);
expect(result).toContainEqual(
expect.objectContaining({ key: 'item1', name: 'Item 1' })
);
expect(result).toContainEqual(
expect.objectContaining({ key: 'item3', name: 'Item 3' })
);
expect(result).toContainEqual(
expect.objectContaining({ key: 'item4', name: 'Item 4' })
);
expect(result).toContainEqual(
expect.objectContaining({ key: 'item6', name: 'Item 6' })
);
});
it('includes items with empty permissions', () => {
const userPermissions = [];
const result = filterItemsByPermission(
items,
userPermissions,
getPermissions
);
expect(result).toHaveLength(1);
expect(result).toContainEqual(
expect.objectContaining({ key: 'item7', name: 'Item 7' })
);
});
it('uses custom transform function when provided', () => {
const userPermissions = ['agent', 'contact_manage'];
const customTransform = (key, item) => ({ id: key, title: item.name });
const result = filterItemsByPermission(
items,
userPermissions,
getPermissions,
customTransform
);
expect(result).toHaveLength(4);
expect(result).toContainEqual({ id: 'item1', title: 'Item 1' });
expect(result).toContainEqual({ id: 'item3', title: 'Item 3' });
expect(result).toContainEqual({ id: 'item6', title: 'Item 6' });
});
it('handles empty items object', () => {
const result = filterItemsByPermission({}, ['agent'], getPermissions);
expect(result).toHaveLength(0);
});
it('handles custom getPermissions function', () => {
const customItems = {
item1: { name: 'Item 1', requiredPerms: ['agent', 'administrator'] },
item2: { name: 'Item 2', requiredPerms: ['contact_manage'] },
};
const customGetPermissions = item => item.requiredPerms;
const result = filterItemsByPermission(
customItems,
['agent'],
customGetPermissions
);
expect(result).toHaveLength(1);
expect(result).toContainEqual(
expect.objectContaining({ key: 'item1', name: 'Item 1' })
);
});
});

View File

@@ -1,6 +1,7 @@
import {
getConversationDashboardRoute,
isAConversationRoute,
defaultRedirectPage,
routeIsAccessibleFor,
validateLoggedInRoutes,
isAInboxViewRoute,
@@ -14,6 +15,57 @@ describe('#routeIsAccessibleFor', () => {
});
});
describe('#defaultRedirectPage', () => {
const to = {
params: { accountId: '2' },
fullPath: '/app/accounts/2/dashboard',
name: 'home',
};
it('should return dashboard route for users with conversation permissions', () => {
const permissions = ['conversation_manage', 'agent'];
expect(defaultRedirectPage(to, permissions)).toBe('accounts/2/dashboard');
});
it('should return contacts route for users with contact permissions', () => {
const permissions = ['contact_manage'];
expect(defaultRedirectPage(to, permissions)).toBe('accounts/2/contacts');
});
it('should return reports route for users with report permissions', () => {
const permissions = ['report_manage'];
expect(defaultRedirectPage(to, permissions)).toBe(
'accounts/2/reports/overview'
);
});
it('should return portals route for users with portal permissions', () => {
const permissions = ['knowledge_base_manage'];
expect(defaultRedirectPage(to, permissions)).toBe('accounts/2/portals');
});
it('should return dashboard route as default for users with custom roles', () => {
const permissions = ['custom_role'];
expect(defaultRedirectPage(to, permissions)).toBe('accounts/2/dashboard');
});
it('should return dashboard route for users with administrator role', () => {
const permissions = ['administrator'];
expect(defaultRedirectPage(to, permissions)).toBe('accounts/2/dashboard');
});
it('should return dashboard route for users with multiple permissions', () => {
const permissions = [
'contact_manage',
'custom_role',
'conversation_manage',
'agent',
'administrator',
];
expect(defaultRedirectPage(to, permissions)).toBe('accounts/2/dashboard');
});
});
describe('#validateLoggedInRoutes', () => {
describe('when account access is missing', () => {
it('should return the login route', () => {