Skip to content

Commit 84b092e

Browse files
committed
fix: hide course authoring content when enable_course_authoring is off
1 parent 9232178 commit 84b092e

6 files changed

Lines changed: 104 additions & 14 deletions

File tree

src/authz-module/audit-user/index.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import AuthZLayout from '@src/authz-module/components/AuthZLayout';
1515
import { useNavigate, useParams } from 'react-router-dom';
1616
import { useUserAccount, useValidateUserPermissionsNonSuspense } from '@src/data/hooks';
17+
import { CONTENT_COURSE_PERMISSIONS, VIEW_TEAM_PERMISSIONS, courseRolesMetadata } from '@src/authz-module/roles-permissions';
1718
import baseMessages from '@src/authz-module/messages';
1819
import AddRoleButton from '@src/authz-module/components/AddRoleButton';
1920
import {
@@ -32,6 +33,8 @@ import messages from './messages';
3233
import ConfirmDeletionModal from '../components/ConfirmDeletionModal';
3334
import { getCellHeader, getScopeManageActionPermission } from '../utils';
3435

36+
const COURSE_ROLE_KEYS = new Set(courseRolesMetadata.map((r) => r.role));
37+
3538
const AuditUserPage = () => {
3639
const { formatMessage } = useIntl();
3740
const [columnsWithFiltersApplied, setColumnsWithFiltersApplied] = useState<string[]>([]);
@@ -61,6 +64,11 @@ const AuditUserPage = () => {
6164
data: permissionsToManageScope,
6265
} = useValidateUserPermissionsNonSuspense(deletePermissions);
6366

67+
const { data: domainPermissions } = useValidateUserPermissionsNonSuspense(VIEW_TEAM_PERMISSIONS);
68+
const isCourseViewAllowed = domainPermissions
69+
? domainPermissions.some((p) => p.action === CONTENT_COURSE_PERMISSIONS.VIEW_COURSE_TEAM && p.allowed)
70+
: true;
71+
6472
const rowsWithPermissions = useMemo(() => {
6573
if (!permissionsToManageScope) { return userAssignments; }
6674

@@ -75,6 +83,14 @@ const AuditUserPage = () => {
7583
});
7684
}, [userAssignments, permissionsToManageScope]);
7785

86+
const visibleAssignments = useMemo(
87+
() => (
88+
isCourseViewAllowed
89+
? rowsWithPermissions
90+
: rowsWithPermissions.filter((r) => !COURSE_ROLE_KEYS.has(r.role))),
91+
[isCourseViewAllowed, rowsWithPermissions],
92+
);
93+
7894
const fetchData = useMemo(() => handleTableFetch, [handleTableFetch]);
7995

8096
useEffect(() => {
@@ -251,7 +267,7 @@ const AuditUserPage = () => {
251267
isFilterable
252268
isSortable
253269
manualPagination
254-
data={rowsWithPermissions}
270+
data={visibleAssignments}
255271
manualFilters
256272
manualSortBy
257273
fetchData={fetchData}

src/authz-module/components/TableControlBar/RolesFilter.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { useMemo } from 'react';
22
import { useIntl } from '@edx/frontend-platform/i18n';
33
import { Person } from '@openedx/paragon/icons';
44
import { useValidateUserPermissionsNonSuspense } from '@src/data/hooks';
5-
import { CONTENT_COURSE_PERMISSIONS, CONTENT_LIBRARY_PERMISSIONS, MANAGE_TEAM_PERMISSIONS } from '@src/authz-module/roles-permissions';
5+
import {
6+
CONTENT_COURSE_PERMISSIONS, CONTENT_LIBRARY_PERMISSIONS, VIEW_TEAM_PERMISSIONS,
7+
} from '@src/authz-module/roles-permissions';
68
import { CONTEXT_TYPES } from '@src/authz-module/constants';
79
import MultipleChoiceFilter from './MultipleChoiceFilter';
810
import { MultipleChoiceFilterProps } from './types';
@@ -14,7 +16,7 @@ const RolesFilter = ({
1416
filterButtonText, filterValue, setFilter, disabled,
1517
}: RolesFilterProps) => {
1618
const intl = useIntl();
17-
const { data: permissions } = useValidateUserPermissionsNonSuspense(MANAGE_TEAM_PERMISSIONS);
19+
const { data: permissions } = useValidateUserPermissionsNonSuspense(VIEW_TEAM_PERMISSIONS);
1820

1921
// Only show role groups for the domains the user can view. Global roles stay
2022
// hidden until a platform-wide permission is available to gate them on.

src/authz-module/components/TableControlBar/ScopesFilter.test.tsx

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,47 @@
11
import { screen } from '@testing-library/react';
22
import userEvent from '@testing-library/user-event';
33
import { renderWrapper } from '@src/setupTest';
4+
import { useValidateUserPermissionsNonSuspense } from '@src/data/hooks';
5+
import { useScopes } from '@src/authz-module/data/hooks';
6+
import { CONTENT_COURSE_PERMISSIONS, CONTENT_LIBRARY_PERMISSIONS } from '@src/authz-module/roles-permissions';
47
import ScopesFilter from './ScopesFilter';
58

9+
jest.mock('@src/data/hooks', () => ({
10+
useValidateUserPermissionsNonSuspense: jest.fn(),
11+
}));
12+
13+
const mockUsePermissions = useValidateUserPermissionsNonSuspense as jest.Mock;
14+
615
jest.mock('@src/authz-module/data/hooks', () => ({
7-
useScopes: () => ({
16+
useScopes: jest.fn(() => ({
817
data: {
918
pages: [
1019
{
1120
results: [
1221
{
13-
externalKey: 'course:123',
14-
name: 'Test Course',
15-
organization: { name: 'Test Org' },
22+
externalKey: 'course-v1:org+course+run',
23+
displayName: 'Test Course',
24+
org: { shortName: 'TestOrg' },
1625
},
1726
{
18-
externalKey: 'library:456',
19-
name: 'Test Library',
20-
organization: { name: 'Another Org' },
27+
externalKey: 'lib:org:library',
28+
displayName: 'Test Library',
29+
org: { shortName: 'TestOrg' },
2130
},
2231
],
2332
},
2433
],
2534
},
26-
}),
35+
})),
2736
}));
2837

38+
const mockUseScopes = useScopes as jest.Mock;
39+
40+
const permissionsData = ({ library, course }: { library?: boolean; course?: boolean }) => [
41+
{ action: CONTENT_LIBRARY_PERMISSIONS.VIEW_LIBRARY_TEAM, allowed: !!library },
42+
{ action: CONTENT_COURSE_PERMISSIONS.VIEW_COURSE_TEAM, allowed: !!course },
43+
];
44+
2945
describe('ScopesFilter', () => {
3046
const defaultProps = {
3147
filterButtonText: 'Scopes',
@@ -36,6 +52,7 @@ describe('ScopesFilter', () => {
3652

3753
beforeEach(() => {
3854
jest.clearAllMocks();
55+
mockUsePermissions.mockReturnValue({ data: permissionsData({ library: true, course: true }) });
3956
});
4057

4158
it('renders without crashing', () => {
@@ -68,4 +85,27 @@ describe('ScopesFilter', () => {
6885
renderWrapper(<ScopesFilter {...defaultProps} setFilter={mockSetFilter} />);
6986
expect(screen.getByText('Scopes')).toBeInTheDocument();
7087
});
88+
89+
it('fetches all scope types when the user can view courses', () => {
90+
renderWrapper(<ScopesFilter {...defaultProps} />);
91+
expect(mockUseScopes).toHaveBeenCalledWith(
92+
expect.not.objectContaining({ scopeType: 'library' }),
93+
);
94+
});
95+
96+
it('fetches only library scopes when the user cannot view courses', () => {
97+
mockUsePermissions.mockReturnValue({ data: permissionsData({ library: true, course: false }) });
98+
renderWrapper(<ScopesFilter {...defaultProps} />);
99+
expect(mockUseScopes).toHaveBeenCalledWith(
100+
expect.objectContaining({ scopeType: 'library' }),
101+
);
102+
});
103+
104+
it('defaults to showing all scopes while permissions are loading', () => {
105+
mockUsePermissions.mockReturnValue({ data: undefined });
106+
renderWrapper(<ScopesFilter {...defaultProps} />);
107+
expect(mockUseScopes).toHaveBeenCalledWith(
108+
expect.not.objectContaining({ scopeType: 'library' }),
109+
);
110+
});
71111
});

src/authz-module/components/TableControlBar/ScopesFilter.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { useMemo, useState } from 'react';
22
import { useIntl } from '@edx/frontend-platform/i18n';
33
import { LocationOn } from '@openedx/paragon/icons';
4+
import { useValidateUserPermissionsNonSuspense } from '@src/data/hooks';
5+
import { CONTENT_COURSE_PERMISSIONS, VIEW_TEAM_PERMISSIONS } from '@src/authz-module/roles-permissions';
46
import { useScopes } from '@src/authz-module/data/hooks';
57
import { DEFAULT_FILTER_PAGE_SIZE } from '@src/authz-module/constants';
68
import { MultipleChoiceFilterProps } from './types';
@@ -15,7 +17,17 @@ const ScopesFilter = ({
1517
}: ScopesFilterProps) => {
1618
const { formatMessage } = useIntl();
1719
const [searchValue, setSearchValue] = useState<string | undefined>(undefined);
18-
const { data: scopesData } = useScopes({ search: searchValue, pageSize: DEFAULT_FILTER_PAGE_SIZE });
20+
21+
const { data: permissions } = useValidateUserPermissionsNonSuspense(VIEW_TEAM_PERMISSIONS);
22+
const isCourseViewAllowed = permissions
23+
? permissions.some((p) => p.action === CONTENT_COURSE_PERMISSIONS.VIEW_COURSE_TEAM && p.allowed)
24+
: true;
25+
26+
const { data: scopesData } = useScopes({
27+
search: searchValue,
28+
pageSize: DEFAULT_FILTER_PAGE_SIZE,
29+
...(isCourseViewAllowed ? {} : { scopeType: 'library' }),
30+
});
1931

2032
const filterChoices = useMemo(() => (scopesData?.pages?.flatMap((p) => p.results) ?? []).map((scope) => {
2133
const scopeIcon = scope.externalKey?.startsWith('lib') ? RESOURCE_ICONS.LIBRARY : RESOURCE_ICONS.COURSE;

src/authz-module/roles-permissions/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,8 @@ export const MANAGE_TEAM_PERMISSIONS: { action: string }[] = [
2121
{ action: CONTENT_LIBRARY_PERMISSIONS.MANAGE_LIBRARY_TEAM },
2222
{ action: CONTENT_COURSE_PERMISSIONS.MANAGE_COURSE_TEAM },
2323
];
24+
25+
export const VIEW_TEAM_PERMISSIONS: { action: string }[] = [
26+
{ action: CONTENT_LIBRARY_PERMISSIONS.VIEW_LIBRARY_TEAM },
27+
{ action: CONTENT_COURSE_PERMISSIONS.VIEW_COURSE_TEAM },
28+
];

src/authz-module/team-members/TeamMembersTable.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
} from '@openedx/paragon';
88

99
import { useToastManager } from '@src/components/ToastManager/ToastManagerContext';
10+
import { useValidateUserPermissionsNonSuspense } from '@src/data/hooks';
11+
import { CONTENT_COURSE_PERMISSIONS, VIEW_TEAM_PERMISSIONS, courseRolesMetadata } from '@src/authz-module/roles-permissions';
1012
import { useQuerySettings } from '@src/authz-module/hooks/useQuerySettings';
1113
import OrgFilter from '@src/authz-module/components/TableControlBar/OrgFilter';
1214
import RolesFilter from '@src/authz-module/components/TableControlBar/RolesFilter';
@@ -18,9 +20,12 @@ import {
1820
} from '@src/authz-module/components/TableCells';
1921
import { useAllRoleAssignments } from '@src/authz-module/data/hooks';
2022
import { TABLE_DEFAULT_PAGE_SIZE } from '@src/authz-module/constants';
23+
import { UserRole } from '@src/types';
2124
import messages from './messages';
2225
import TableFooter from '../components/TableFooter/TableFooter';
2326

27+
const COURSE_ROLE_KEYS = new Set(courseRolesMetadata.map((r) => r.role));
28+
2429
interface TeamMembersTableProps {
2530
presetScope?: string;
2631
}
@@ -44,12 +49,22 @@ const TeamMembersTable = ({ presetScope }: TeamMembersTableProps) => {
4449
const { querySettings, handleTableFetch } = useQuerySettings(initialQuerySettings);
4550

4651
const {
47-
data: { results: roleAssignments, count } = { results: [], count: 0 },
52+
data: { results: roleAssignments, count } = { results: [] as UserRole[], count: 0 },
4853
isLoading: isLoadingAllRoleAssignments,
4954
error,
5055
refetch,
5156
} = useAllRoleAssignments(querySettings);
5257

58+
const { data: domainPermissions } = useValidateUserPermissionsNonSuspense(VIEW_TEAM_PERMISSIONS);
59+
const isCourseViewAllowed = domainPermissions
60+
? domainPermissions.some((p) => p.action === CONTENT_COURSE_PERMISSIONS.VIEW_COURSE_TEAM && p.allowed)
61+
: true;
62+
63+
const visibleAssignments = useMemo(
64+
() => (isCourseViewAllowed ? roleAssignments : roleAssignments.filter((r) => !COURSE_ROLE_KEYS.has(r.role))),
65+
[isCourseViewAllowed, roleAssignments],
66+
);
67+
5368
const initialFilters = presetScope ? [{ id: 'scope', value: [presetScope] }] : [];
5469

5570
useEffect(() => {
@@ -75,7 +90,7 @@ const TeamMembersTable = ({ presetScope }: TeamMembersTableProps) => {
7590
manualSortBy
7691
numBreakoutFilters={4}
7792
fetchData={fetchData}
78-
data={roleAssignments}
93+
data={visibleAssignments}
7994
itemCount={count}
8095
pageCount={pageCount}
8196
initialState={{ pageSize: TABLE_DEFAULT_PAGE_SIZE, filters: initialFilters }}

0 commit comments

Comments
 (0)