Skip to content

Commit 5617702

Browse files
authoredJun 2, 2023
feat: access-marketplace permission (RocketChat#29203)
1 parent 29aab43 commit 5617702

File tree

14 files changed

+245
-179
lines changed

14 files changed

+245
-179
lines changed
 

‎.changeset/curvy-hounds-sniff.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@rocket.chat/meteor': minor
3+
---
4+
5+
feat: access-marketplace permission

‎apps/meteor/app/authorization/server/constant/permissions.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// 2. admin, moderator, and user roles should not be deleted as they are referenced in the code.
55
export const permissions = [
66
{ _id: 'access-permissions', roles: ['admin'] },
7+
{ _id: 'access-marketplace', roles: ['admin', 'user'] },
78
{ _id: 'access-setting-permissions', roles: ['admin'] },
89
{ _id: 'add-oauth-service', roles: ['admin'] },
910
{ _id: 'add-user-to-joined-room', roles: ['admin', 'owner', 'moderator'] },

‎apps/meteor/client/components/AdministrationList/AdministrationList.spec.tsx

-84
This file was deleted.

‎apps/meteor/client/components/AdministrationList/AdministrationList.tsx

+3-58
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,15 @@
11
import { OptionDivider } from '@rocket.chat/fuselage';
2-
import { useAtLeastOnePermission, usePermission } from '@rocket.chat/ui-contexts';
32
import type { ReactElement } from 'react';
43
import React, { Fragment } from 'react';
54

6-
import type { AccountBoxItem, IAppAccountBoxItem } from '../../../app/ui-utils/client/lib/AccountBox';
7-
import { isAppAccountBoxItem } from '../../../app/ui-utils/client/lib/AccountBox';
8-
import { useHasLicenseModule } from '../../../ee/client/hooks/useHasLicenseModule';
9-
import AdministrationModelList from './AdministrationModelList';
10-
import AppsModelList from './AppsModelList';
11-
import AuditModelList from './AuditModelList';
12-
135
type AdministrationListProps = {
14-
accountBoxItems: (IAppAccountBoxItem | AccountBoxItem)[];
15-
onDismiss: () => void;
6+
optionsList: (false | JSX.Element)[];
167
};
178

18-
const ADMIN_PERMISSIONS = [
19-
'view-statistics',
20-
'run-import',
21-
'view-user-administration',
22-
'view-room-administration',
23-
'create-invite-links',
24-
'manage-cloud',
25-
'view-logs',
26-
'manage-sounds',
27-
'view-federation-data',
28-
'manage-email-inbox',
29-
'manage-emoji',
30-
'manage-outgoing-integrations',
31-
'manage-own-outgoing-integrations',
32-
'manage-incoming-integrations',
33-
'manage-own-incoming-integrations',
34-
'manage-oauth-apps',
35-
'access-mailer',
36-
'manage-user-status',
37-
'access-permissions',
38-
'access-setting-permissions',
39-
'view-privileged-setting',
40-
'edit-privileged-setting',
41-
'manage-selected-settings',
42-
'view-engagement-dashboard',
43-
'view-moderation-console',
44-
];
45-
46-
const AdministrationList = ({ accountBoxItems, onDismiss }: AdministrationListProps): ReactElement => {
47-
const hasAuditLicense = useHasLicenseModule('auditing') === true;
48-
const hasAdminPermission = useAtLeastOnePermission(ADMIN_PERMISSIONS);
49-
const hasManageAppsPermission = usePermission('manage-apps');
50-
const hasAuditPermission = usePermission('can-audit') && hasAuditLicense;
51-
const hasAuditLogPermission = usePermission('can-audit-log') && hasAuditLicense;
52-
53-
const appBoxItems = accountBoxItems.filter((item): item is IAppAccountBoxItem => isAppAccountBoxItem(item));
54-
const adminBoxItems = accountBoxItems.filter((item): item is AccountBoxItem => !isAppAccountBoxItem(item));
55-
const showAudit = hasAuditPermission || hasAuditLogPermission;
56-
const showAdmin = hasAdminPermission || !!adminBoxItems.length;
57-
const showWorkspace = hasAdminPermission;
58-
59-
const list = [
60-
showAdmin && <AdministrationModelList showWorkspace={showWorkspace} accountBoxItems={adminBoxItems} onDismiss={onDismiss} />,
61-
<AppsModelList appBoxItems={appBoxItems} onDismiss={onDismiss} appsManagementAllowed={hasManageAppsPermission} />,
62-
showAudit && <AuditModelList showAudit={hasAuditPermission} showAuditLog={hasAuditLogPermission} onDismiss={onDismiss} />,
63-
];
64-
9+
const AdministrationList = ({ optionsList }: AdministrationListProps): ReactElement => {
6510
return (
6611
<>
67-
{list.filter(Boolean).map((item, index) => (
12+
{optionsList.map((item, index) => (
6813
<Fragment key={index}>
6914
{index > 0 && <OptionDivider />}
7015
{item}

‎apps/meteor/client/components/AdministrationList/AppsModelList.spec.tsx

+20-4
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ describe('AppsModelList', () => {
3434
it('should render all apps options when a user has manage apps permission', async () => {
3535
const AppsModelList = loadMock();
3636

37-
render(<AppsModelList onDismiss={() => null} appBoxItems={[]} appsManagementAllowed />);
37+
render(<AppsModelList onDismiss={() => null} appBoxItems={[]} appsManagementAllowed showMarketplace />);
3838

3939
expect(screen.getByText('Apps')).to.exist;
4040
expect(screen.getByText('Marketplace')).to.exist;
@@ -50,14 +50,30 @@ describe('AppsModelList', () => {
5050
},
5151
});
5252

53-
render(<AppsModelList onDismiss={() => null} appBoxItems={[]} appsManagementAllowed={false} />);
53+
render(<AppsModelList onDismiss={() => null} appBoxItems={[]} appsManagementAllowed={false} showMarketplace />);
5454

5555
expect(screen.getByText('Apps')).to.exist;
5656
expect(screen.getByText('Marketplace')).to.exist;
5757
expect(screen.getByText('Installed')).to.exist;
5858
expect(screen.queryByText('Requested')).to.not.exist;
5959
});
6060

61+
it('should not render marketplace and installed options when user does not have access-marketplace permission', async () => {
62+
const AppsModelList = loadMock({
63+
'@rocket.chat/ui-contexts': {
64+
'useAtLeastOnePermission': (): boolean => false,
65+
'@noCallThru': false,
66+
},
67+
});
68+
69+
render(<AppsModelList onDismiss={() => null} appBoxItems={[]} appsManagementAllowed={false} />);
70+
71+
expect(screen.getByText('Apps')).to.exist;
72+
expect(screen.queryByText('Marketplace')).to.not.exist;
73+
expect(screen.queryByText('Installed')).to.not.exist;
74+
expect(screen.queryByText('Requested')).to.not.exist;
75+
});
76+
6177
context('when clicked', () => {
6278
const pushRoute = spy();
6379
const handleDismiss = spy();
@@ -69,7 +85,7 @@ describe('AppsModelList', () => {
6985
it('should go to marketplace', async () => {
7086
const AppsModelList = loadMock();
7187

72-
render(<AppsModelList onDismiss={handleDismiss} appBoxItems={[]} />, { wrapper: ProvidersMock });
88+
render(<AppsModelList onDismiss={handleDismiss} appBoxItems={[]} showMarketplace />, { wrapper: ProvidersMock });
7389

7490
const button = screen.getByText('Marketplace');
7591
userEvent.click(button);
@@ -80,7 +96,7 @@ describe('AppsModelList', () => {
8096
it('should go to installed', async () => {
8197
const AppsModelList = loadMock();
8298

83-
render(<AppsModelList onDismiss={handleDismiss} appBoxItems={[]} />, { wrapper: ProvidersMock });
99+
render(<AppsModelList onDismiss={handleDismiss} appBoxItems={[]} showMarketplace />, { wrapper: ProvidersMock });
84100

85101
const button = screen.getByText('Installed');
86102

‎apps/meteor/client/components/AdministrationList/AppsModelList.tsx

+24-19
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ type AppsModelListProps = {
1212
appBoxItems: IAppAccountBoxItem[];
1313
appsManagementAllowed?: boolean;
1414
onDismiss: () => void;
15+
showMarketplace?: boolean;
1516
};
1617

17-
const AppsModelList = ({ appBoxItems, appsManagementAllowed, onDismiss }: AppsModelListProps): ReactElement => {
18+
const AppsModelList = ({ appBoxItems, appsManagementAllowed, showMarketplace, onDismiss }: AppsModelListProps): ReactElement => {
1819
const t = useTranslation();
1920
const marketplaceRoute = useRoute('marketplace');
2021
const page = 'list';
@@ -26,24 +27,28 @@ const AppsModelList = ({ appBoxItems, appsManagementAllowed, onDismiss }: AppsMo
2627
<OptionTitle>{t('Apps')}</OptionTitle>
2728
<ul>
2829
<>
29-
<ListItem
30-
role='listitem'
31-
icon='store'
32-
text={t('Marketplace')}
33-
onClick={() => {
34-
marketplaceRoute.push({ context: 'explore', page });
35-
onDismiss();
36-
}}
37-
/>
38-
<ListItem
39-
role='listitem'
40-
icon='circle-arrow-down'
41-
text={t('Installed')}
42-
onClick={() => {
43-
marketplaceRoute.push({ context: 'installed', page });
44-
onDismiss();
45-
}}
46-
/>
30+
{showMarketplace && (
31+
<>
32+
<ListItem
33+
role='listitem'
34+
icon='store'
35+
text={t('Marketplace')}
36+
onClick={() => {
37+
marketplaceRoute.push({ context: 'explore', page });
38+
onDismiss();
39+
}}
40+
/>
41+
<ListItem
42+
role='listitem'
43+
icon='circle-arrow-down'
44+
text={t('Installed')}
45+
onClick={() => {
46+
marketplaceRoute.push({ context: 'installed', page });
47+
onDismiss();
48+
}}
49+
/>
50+
</>
51+
)}
4752

4853
{appsManagementAllowed && (
4954
<>

‎apps/meteor/client/sidebar/header/actions/Administration.tsx

+70-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,49 @@
11
import { Sidebar, Dropdown } from '@rocket.chat/fuselage';
22
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
3+
import { usePermission, useAtLeastOnePermission } from '@rocket.chat/ui-contexts';
34
import type { HTMLAttributes, VFC } from 'react';
4-
import React, { useRef } from 'react';
5+
import React, { useCallback, useRef } from 'react';
56
import { createPortal } from 'react-dom';
67

78
import { AccountBox } from '../../../../app/ui-utils/client';
9+
import type { IAppAccountBoxItem, AccountBoxItem } from '../../../../app/ui-utils/client/lib/AccountBox';
10+
import { isAppAccountBoxItem } from '../../../../app/ui-utils/client/lib/AccountBox';
11+
import { useHasLicenseModule } from '../../../../ee/client/hooks/useHasLicenseModule';
812
import AdministrationList from '../../../components/AdministrationList/AdministrationList';
13+
import AdministrationModelList from '../../../components/AdministrationList/AdministrationModelList';
14+
import AppsModelList from '../../../components/AdministrationList/AppsModelList';
15+
import AuditModelList from '../../../components/AdministrationList/AuditModelList';
916
import { useReactiveValue } from '../../../hooks/useReactiveValue';
1017
import { useDropdownVisibility } from '../hooks/useDropdownVisibility';
1118

19+
const ADMIN_PERMISSIONS = [
20+
'view-statistics',
21+
'run-import',
22+
'view-user-administration',
23+
'view-room-administration',
24+
'create-invite-links',
25+
'manage-cloud',
26+
'view-logs',
27+
'manage-sounds',
28+
'view-federation-data',
29+
'manage-email-inbox',
30+
'manage-emoji',
31+
'manage-outgoing-integrations',
32+
'manage-own-outgoing-integrations',
33+
'manage-incoming-integrations',
34+
'manage-own-incoming-integrations',
35+
'manage-oauth-apps',
36+
'access-mailer',
37+
'manage-user-status',
38+
'access-permissions',
39+
'access-setting-permissions',
40+
'view-privileged-setting',
41+
'edit-privileged-setting',
42+
'manage-selected-settings',
43+
'view-engagement-dashboard',
44+
'view-moderation-console',
45+
];
46+
1247
const Administration: VFC<Omit<HTMLAttributes<HTMLElement>, 'is'>> = (props) => {
1348
const reference = useRef(null);
1449
const target = useRef(null);
@@ -18,13 +53,46 @@ const Administration: VFC<Omit<HTMLAttributes<HTMLElement>, 'is'>> = (props) =>
1853
const getAccountBoxItems = useMutableCallback(() => AccountBox.getItems());
1954
const accountBoxItems = useReactiveValue(getAccountBoxItems);
2055

56+
const hasAuditLicense = useHasLicenseModule('auditing') === true;
57+
const hasManageAppsPermission = usePermission('manage-apps');
58+
const hasAccessMarketplacePermission = usePermission('access-marketplace');
59+
const hasAdminPermission = useAtLeastOnePermission(ADMIN_PERMISSIONS);
60+
const hasAuditPermission = usePermission('can-audit') && hasAuditLicense;
61+
const hasAuditLogPermission = usePermission('can-audit-log') && hasAuditLicense;
62+
63+
const appBoxItems = accountBoxItems.filter((item): item is IAppAccountBoxItem => isAppAccountBoxItem(item));
64+
const adminBoxItems = accountBoxItems.filter((item): item is AccountBoxItem => !isAppAccountBoxItem(item));
65+
const showAdmin = hasAdminPermission || !!adminBoxItems.length;
66+
const showAudit = hasAuditPermission || hasAuditLogPermission;
67+
const showWorkspace = hasAdminPermission;
68+
const showApps = hasAccessMarketplacePermission || hasManageAppsPermission || !!appBoxItems.length;
69+
70+
const onDismiss = useCallback((): void => toggle(false), [toggle]);
71+
72+
const optionsList = [
73+
showAdmin && <AdministrationModelList showWorkspace={showWorkspace} accountBoxItems={adminBoxItems} onDismiss={onDismiss} />,
74+
showApps && (
75+
<AppsModelList
76+
appBoxItems={appBoxItems}
77+
onDismiss={onDismiss}
78+
appsManagementAllowed={hasManageAppsPermission}
79+
showMarketplace={hasAccessMarketplacePermission || hasManageAppsPermission}
80+
/>
81+
),
82+
showAudit && <AuditModelList showAudit={hasAuditPermission} showAuditLog={hasAuditLogPermission} onDismiss={onDismiss} />,
83+
].filter(Boolean);
84+
85+
if (!optionsList || optionsList.length === 0) {
86+
return null;
87+
}
88+
2189
return (
2290
<>
2391
<Sidebar.TopBar.Action icon='menu' onClick={(): void => toggle()} {...props} ref={reference} />
2492
{isVisible &&
2593
createPortal(
2694
<Dropdown reference={reference} ref={target}>
27-
<AdministrationList accountBoxItems={accountBoxItems} onDismiss={(): void => toggle(false)} />
95+
<AdministrationList optionsList={optionsList} />
2896
</Dropdown>,
2997
document.body,
3098
)}

0 commit comments

Comments
 (0)
Please sign in to comment.