@openedx/frontend-app-admin-console 1.0.0-alpha.6 → 1.0.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/authz-module/authz-home/index.js +20 -1
  2. package/dist/authz-module/authz-home/index.js.map +1 -1
  3. package/dist/authz-module/components/AddRoleButton.js +1 -1
  4. package/dist/authz-module/components/AddRoleButton.js.map +1 -1
  5. package/dist/authz-module/components/AuthZTitle.js +5 -3
  6. package/dist/authz-module/components/AuthZTitle.js.map +1 -1
  7. package/dist/authz-module/components/ExpandableButton.d.ts +17 -0
  8. package/dist/authz-module/components/ExpandableButton.js +16 -0
  9. package/dist/authz-module/components/ExpandableButton.js.map +1 -0
  10. package/dist/authz-module/components/TableCells.d.ts +5 -8
  11. package/dist/authz-module/components/TableCells.js +26 -59
  12. package/dist/authz-module/components/TableCells.js.map +1 -1
  13. package/dist/authz-module/components/TableControlBar/OrgFilter.js +2 -2
  14. package/dist/authz-module/components/TableControlBar/OrgFilter.js.map +1 -1
  15. package/dist/authz-module/components/TableControlBar/TableControlBar.d.ts +4 -1
  16. package/dist/authz-module/components/TableControlBar/TableControlBar.js +24 -24
  17. package/dist/authz-module/components/TableControlBar/TableControlBar.js.map +1 -1
  18. package/dist/authz-module/components/TableFooter/TableFooter.d.ts +14 -1
  19. package/dist/authz-module/components/TableFooter/TableFooter.js +3 -2
  20. package/dist/authz-module/components/TableFooter/TableFooter.js.map +1 -1
  21. package/dist/authz-module/components/constants.d.ts +1 -0
  22. package/dist/authz-module/components/constants.js +2 -1
  23. package/dist/authz-module/components/constants.js.map +1 -1
  24. package/dist/authz-module/components/messages.d.ts +0 -5
  25. package/dist/authz-module/components/messages.js +2 -7
  26. package/dist/authz-module/components/messages.js.map +1 -1
  27. package/dist/authz-module/constants.d.ts +14 -6
  28. package/dist/authz-module/constants.js +23 -6
  29. package/dist/authz-module/constants.js.map +1 -1
  30. package/dist/authz-module/data/api.d.ts +12 -4
  31. package/dist/authz-module/data/api.js +11 -2
  32. package/dist/authz-module/data/api.js.map +1 -1
  33. package/dist/authz-module/data/hooks.d.ts +8 -8
  34. package/dist/authz-module/data/hooks.js +15 -20
  35. package/dist/authz-module/data/hooks.js.map +1 -1
  36. package/dist/authz-module/hooks/useExclusiveRowExpansion.d.ts +7 -0
  37. package/dist/authz-module/hooks/useExclusiveRowExpansion.js +24 -0
  38. package/dist/authz-module/hooks/useExclusiveRowExpansion.js.map +1 -0
  39. package/dist/authz-module/index.scss +120 -2
  40. package/dist/authz-module/messages.d.ts +23 -0
  41. package/dist/authz-module/messages.js +18 -0
  42. package/dist/authz-module/messages.js.map +1 -1
  43. package/dist/authz-module/role-assignation-wizard/hooks/useScopeListData.js.map +1 -1
  44. package/dist/authz-module/team-members/TeamMembersTable.js +55 -17
  45. package/dist/authz-module/team-members/TeamMembersTable.js.map +1 -1
  46. package/dist/authz-module/team-members/components/AssignedRolesCell.d.ts +20 -0
  47. package/dist/authz-module/team-members/components/AssignedRolesCell.js +54 -0
  48. package/dist/authz-module/team-members/components/AssignedRolesCell.js.map +1 -0
  49. package/dist/authz-module/team-members/components/MoreRolesToggle.d.ts +14 -0
  50. package/dist/authz-module/team-members/components/MoreRolesToggle.js +25 -0
  51. package/dist/authz-module/team-members/components/MoreRolesToggle.js.map +1 -0
  52. package/dist/authz-module/team-members/components/TeamMemberCells.d.ts +17 -0
  53. package/dist/authz-module/team-members/components/TeamMemberCells.js +21 -0
  54. package/dist/authz-module/team-members/components/TeamMemberCells.js.map +1 -0
  55. package/dist/authz-module/team-members/components/TeamMemberViewActionCell.d.ts +18 -0
  56. package/dist/authz-module/team-members/components/TeamMemberViewActionCell.js +42 -0
  57. package/dist/authz-module/team-members/components/TeamMemberViewActionCell.js.map +1 -0
  58. package/dist/authz-module/team-members/components/UserAssignmentsSubTable.d.ts +16 -0
  59. package/dist/authz-module/team-members/components/UserAssignmentsSubTable.js +74 -0
  60. package/dist/authz-module/team-members/components/UserAssignmentsSubTable.js.map +1 -0
  61. package/dist/authz-module/team-members/index.d.ts +1 -0
  62. package/dist/authz-module/team-members/index.js +2 -0
  63. package/dist/authz-module/team-members/index.js.map +1 -0
  64. package/dist/authz-module/team-members/messages.d.ts +56 -1
  65. package/dist/authz-module/team-members/messages.js +59 -4
  66. package/dist/authz-module/team-members/messages.js.map +1 -1
  67. package/dist/authz-module/utils.d.ts +5 -0
  68. package/dist/authz-module/utils.js +10 -10
  69. package/dist/authz-module/utils.js.map +1 -1
  70. package/dist/components/LoadingPage.d.ts +3 -0
  71. package/dist/components/LoadingPage.js +9 -1
  72. package/dist/components/LoadingPage.js.map +1 -1
  73. package/dist/components/messages.d.ts +8 -0
  74. package/dist/components/messages.js +10 -0
  75. package/dist/components/messages.js.map +1 -0
  76. package/dist/types.d.ts +29 -20
  77. package/dist/types.js.map +1 -1
  78. package/package.json +1 -1
  79. package/dist/authz-module/components/ViewMoreLink.d.ts +0 -8
  80. package/dist/authz-module/components/ViewMoreLink.js +0 -8
  81. package/dist/authz-module/components/ViewMoreLink.js.map +0 -1
@@ -9,10 +9,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { useInfiniteQuery, useMutation, useQuery, useQueryClient, } from '@tanstack/react-query';
11
11
  import { appId } from '../../constants';
12
- import { assignTeamMembersRole, getAllRoleAssignments, getOrgs, getScopes, revokeUserRoles, getUserAssignedRoles, validateUsers, getCourseAuthoringFlagStates, } from './api';
12
+ import { assignTeamMembersRole, getTeamMembersAssignments, getOrgs, getScopes, revokeUserRoles, getUserAssignedRoles, validateUsers, getCourseAuthoringFlagStates, } from './api';
13
13
  const authzQueryKeys = {
14
14
  all: [appId, 'authz'],
15
- allRoleAssignments: (querySettings) => [...authzQueryKeys.all, 'allRoleAssignments', querySettings],
15
+ teamMembersAssignments: (querySettings) => [...authzQueryKeys.all, 'teamMembersAssignments', querySettings],
16
16
  orgs: (search, page, pageSize) => [...authzQueryKeys.all, 'organizations', search, page, pageSize],
17
17
  scopes: (params) => [...authzQueryKeys.all, 'scopes', params],
18
18
  userRoles: (username, querySettings) => [...authzQueryKeys.all, 'userRoles', username, querySettings],
@@ -34,7 +34,7 @@ export const useAssignTeamMembersRole = () => {
34
34
  if (!error) {
35
35
  queryClient.invalidateQueries({ queryKey: [...authzQueryKeys.all, 'userRoles'] });
36
36
  queryClient.invalidateQueries({
37
- predicate: (query) => query.queryKey.includes('allRoleAssignments'),
37
+ predicate: (query) => query.queryKey.includes('teamMembersAssignments'),
38
38
  });
39
39
  }
40
40
  },
@@ -67,33 +67,28 @@ export const useRevokeUserRoles = () => {
67
67
  predicate: (query) => query.queryKey.includes('userRoles'),
68
68
  });
69
69
  queryClient.invalidateQueries({
70
- predicate: (query) => query.queryKey.includes('allRoleAssignments'),
70
+ predicate: (query) => query.queryKey.includes('teamMembersAssignments'),
71
71
  });
72
72
  },
73
73
  });
74
74
  };
75
75
  /**
76
- * React Query hook to fetch all role assignments across scopes and roles,
77
- * with support for filtering, sorting, and pagination.
78
- * It retrieves a comprehensive list of user-role assignments based
79
- * on the provided query settings.
76
+ * React Query hook to fetch team members grouped by user, with support for
77
+ * filtering, sorting, and pagination. Each result is one user carrying the first few of
78
+ * their role assignments plus how many there are; see `getTeamMembersAssignments` for
79
+ * how filters narrow both.
80
80
  *
81
- * @param querySettings - Optional parameters for filtering by roles, scopes,
81
+ * @param querySettings - Parameters for filtering by roles, scopes,
82
82
  * organizations, search term, sorting, and pagination.
83
83
  *
84
84
  * @example
85
- * const { data: roleAssignments } = useAllRoleAssignments({ roles: 'editor', pageSize: 20 });
85
+ * const { data: teamMembers } = useTeamMembersAssignments({ roles: 'editor', pageSize: 20 });
86
86
  */
87
- export const useAllRoleAssignments = (querySettings) => {
88
- const result = useQuery({
89
- queryKey: authzQueryKeys.allRoleAssignments(querySettings),
90
- queryFn: () => getAllRoleAssignments(querySettings),
91
- staleTime: 1000 * 60 * 30, // refetch after 30 minutes
92
- retry: false,
93
- refetchOnWindowFocus: false,
94
- });
95
- return result;
96
- };
87
+ export const useTeamMembersAssignments = (querySettings) => (useQuery({
88
+ queryKey: authzQueryKeys.teamMembersAssignments(querySettings),
89
+ queryFn: () => getTeamMembersAssignments(querySettings),
90
+ staleTime: 1000 * 60 * 30, // refetch after 30 minutes
91
+ }));
97
92
  /**
98
93
  * React Query hook to fetch a paginated, searchable list of organizations.
99
94
  * Results are cached for 30 minutes — suitable for both filter dropdowns and full listings.
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../../src/authz-module/data/hooks.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n useInfiniteQuery, useMutation, useQuery, useQueryClient,\n} from '@tanstack/react-query';\nimport { appId } from '@src/constants';\nimport {\n assignTeamMembersRole, AssignTeamMembersRoleRequest, getAllRoleAssignments,\n GetAllRoleAssignmentsResponse, getOrgs, GetOrgsResponse,\n getScopes, GetScopesResponse, QuerySettings, revokeUserRoles,\n RevokeUserRolesRequest, getUserAssignedRoles, GetUserAssignmentsResponse,\n validateUsers, ValidateUsersRequest, GetScopesParams,\n getCourseAuthoringFlagStates, CourseAuthoringFlagStates,\n} from './api';\n\nconst authzQueryKeys = {\n all: [appId, 'authz'] as const,\n allRoleAssignments: (querySettings?: QuerySettings) => [...authzQueryKeys.all, 'allRoleAssignments', querySettings] as const,\n orgs: (search?: string, page?: number, pageSize?: number) => [...authzQueryKeys.all, 'organizations', search, page, pageSize] as const,\n scopes: (params?: Omit<GetScopesParams, 'page'>) => [...authzQueryKeys.all, 'scopes', params] as const,\n userRoles: (username?: string, querySettings?: QuerySettings) => [...authzQueryKeys.all, 'userRoles', username, querySettings] as const,\n courseAuthoringFlagStates: () => [...authzQueryKeys.all, 'courseAuthoringFlagStates'] as const,\n};\n\n/**\n * React Query hook to add new team members to a specific scope or manage the corresponding roles.\n * It provides a mutation function to add users with specified roles to the team or assign new roles.\n *\n * @example\n * const { mutate: assignTeamMembersRole } = useAssignTeamMembersRole();\n * assignTeamMembersRole({ data: { libraryId: 'lib:123', users: ['jdoe'], role: 'editor' } });\n */\nexport const useAssignTeamMembersRole = () => {\n const queryClient = useQueryClient();\n return useMutation({\n mutationFn: async ({ data }: {\n data: AssignTeamMembersRoleRequest;\n }) => assignTeamMembersRole(data),\n onSettled: (_data, error) => {\n if (!error) {\n queryClient.invalidateQueries({ queryKey: [...authzQueryKeys.all, 'userRoles'] });\n queryClient.invalidateQueries({\n predicate: (query) => query.queryKey.includes('allRoleAssignments'),\n });\n }\n },\n });\n};\n\n/**\n * React Query hook to validate users exist without assigning roles.\n * It checks if the provided usernames/email addresses are valid.\n *\n * @example\n * const { mutate: validateUsers } = useValidateUsers();\n * validateUsers({ data: { users: ['jdoe', 'jane@example.com'] } });\n */\nexport const useValidateUsers = () => useMutation({\n mutationFn: async ({ data }: {\n data: ValidateUsersRequest;\n }) => validateUsers(data),\n});\n\n/**\n * React Query hook to remove roles for a specific team member within a scope.\n *\n * @example\n * const { mutate: revokeUserRoles } = useRevokeUserRoles();\n * revokeUserRoles({ data: { libraryId: 'lib:123', users: ['jdoe'], role: 'editor' } });\n */\nexport const useRevokeUserRoles = () => {\n const queryClient = useQueryClient();\n return useMutation({\n mutationFn: async ({ data }: {\n data: RevokeUserRolesRequest;\n }) => revokeUserRoles(data),\n onSettled: () => {\n queryClient.invalidateQueries({\n predicate: (query) => query.queryKey.includes('userRoles'),\n });\n queryClient.invalidateQueries({\n predicate: (query) => query.queryKey.includes('allRoleAssignments'),\n });\n },\n });\n};\n\n/**\n * React Query hook to fetch all role assignments across scopes and roles,\n * with support for filtering, sorting, and pagination.\n * It retrieves a comprehensive list of user-role assignments based\n * on the provided query settings.\n *\n * @param querySettings - Optional parameters for filtering by roles, scopes,\n * organizations, search term, sorting, and pagination.\n *\n * @example\n * const { data: roleAssignments } = useAllRoleAssignments({ roles: 'editor', pageSize: 20 });\n */\nexport const useAllRoleAssignments = (querySettings: QuerySettings) => {\n const result = useQuery<GetAllRoleAssignmentsResponse, Error>({\n queryKey: authzQueryKeys.allRoleAssignments(querySettings),\n queryFn: () => getAllRoleAssignments(querySettings),\n staleTime: 1000 * 60 * 30, // refetch after 30 minutes\n retry: false,\n refetchOnWindowFocus: false,\n });\n return result;\n};\n\n/**\n * React Query hook to fetch a paginated, searchable list of organizations.\n * Results are cached for 30 minutes — suitable for both filter dropdowns and full listings.\n *\n * @param search - Optional text filter applied to organization names.\n * @param page - 1-based page number; defaults to the first page when omitted.\n * @param pageSize - Items per page; defaults to the API default when omitted.\n * @returns A `QueryResult<GetOrgsResponse>` with `results`, `count`, `next`, and `previous`.\n *\n * @example\n * ```tsx\n * const { data } = useOrgs('edX');\n * const orgs = data?.results ?? [];\n * ```\n */\nexport const useOrgs = (search?: string, page?: number, pageSize?: number) => useQuery<GetOrgsResponse, Error>({\n queryKey: authzQueryKeys.orgs(search, page, pageSize),\n queryFn: () => getOrgs(search, page, pageSize),\n staleTime: 1000 * 60 * 30,\n refetchOnWindowFocus: false,\n});\n\n/**\n * React Query hook to fetch a paginated, filterable list of scopes (courses or libraries).\n * Uses infinite query to support infinite scroll — call `fetchNextPage` to load more results.\n *\n * @param params - Filter parameters (all optional):\n * - `search` — text filter applied to scope names\n * - `scopeType` — filter by scope type (e.g. course, library)\n * - `org` — filter by organization\n * - `pageSize` — number of items per page\n * - `managementPermissionOnly` — when true, returns only scopes the requester can manage\n * @returns An `InfiniteQueryResult` whose `data.pages` contains the accumulated `GetScopesResponse` pages.\n *\n * @example\n * ```tsx\n * const { data, fetchNextPage, hasNextPage } = useScopes({ search: 'intro', orgs: ['edX'] });\n * const scopes = data?.pages.flatMap((p) => p.results) ?? [];\n * ```\n */\nexport const useScopes = (params: Omit<GetScopesParams, 'page'> = {}) => useInfiniteQuery<GetScopesResponse, Error>({\n queryKey: authzQueryKeys.scopes(params),\n queryFn: ({ pageParam }) => getScopes({ ...params, page: pageParam as number }),\n getNextPageParam: (lastPage) => {\n if (!lastPage.next) return undefined;\n try {\n const nextUrl = new URL(lastPage.next);\n const page = nextUrl.searchParams.get('page');\n return page ? parseInt(page, 10) : undefined;\n } catch {\n return undefined;\n }\n },\n initialPageParam: 1,\n staleTime: 1000 * 60 * 5,\n refetchOnWindowFocus: false,\n});\n\n/*\n * React Query hook to fetch all the roles assigned to a specific user.\n * It retrieves the full list of roles with the corresponding permissions.\n * @param username - The username of the user\n * @param querySettings - Optional query parameters for filtering, sorting, and pagination\n *\n * @example\n * ```tsx\n * const { data: userRoles } = useUserAssignedRoles('jdoe', querySettings);\n * ```\n*/\nexport const useUserAssignedRoles = (\n username?: string,\n querySettings?: QuerySettings,\n) => useQuery<GetUserAssignmentsResponse, Error>({\n queryKey: authzQueryKeys.userRoles(username, querySettings),\n queryFn: () => getUserAssignedRoles(username, querySettings),\n staleTime: 1000 * 60 * 30, // refetch after 30 minutes\n enabled: !!username,\n refetchOnWindowFocus: false,\n});\n\n/**\n * React Query hook to fetch the enablement state of the course-authoring waffle flag\n * across scopes (global, org overrides, course overrides).\n *\n * Follows the same caching rules as the permission-validation hooks.\n *\n * @example\n * ```tsx\n * const { data } = useCourseAuthoringFlagStates();\n * ```\n */\nexport const useCourseAuthoringFlagStates = () => useQuery<CourseAuthoringFlagStates, Error>({\n queryKey: authzQueryKeys.courseAuthoringFlagStates(),\n queryFn: () => getCourseAuthoringFlagStates(),\n staleTime: 1000 * 60 * 30,\n refetchOnWindowFocus: false,\n retry: false,\n});\n"]}
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../../src/authz-module/data/hooks.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n useInfiniteQuery, useMutation, useQuery, useQueryClient,\n} from '@tanstack/react-query';\nimport { appId } from '@src/constants';\nimport {\n assignTeamMembersRole, AssignTeamMembersRoleRequest, getTeamMembersAssignments,\n GetTeamMembersAssignmentsResponse, getOrgs, GetOrgsResponse,\n getScopes, GetScopesResponse, QuerySettings, revokeUserRoles,\n RevokeUserRolesRequest, getUserAssignedRoles, GetUserAssignmentsResponse,\n validateUsers, ValidateUsersRequest, GetScopesParams,\n getCourseAuthoringFlagStates, CourseAuthoringFlagStates,\n} from './api';\n\nconst authzQueryKeys = {\n all: [appId, 'authz'] as const,\n teamMembersAssignments: (querySettings?: QuerySettings) => [...authzQueryKeys.all, 'teamMembersAssignments', querySettings] as const,\n orgs: (search?: string, page?: number, pageSize?: number) => [...authzQueryKeys.all, 'organizations', search, page, pageSize] as const,\n scopes: (params?: Omit<GetScopesParams, 'page'>) => [...authzQueryKeys.all, 'scopes', params] as const,\n userRoles: (username?: string, querySettings?: QuerySettings) => [...authzQueryKeys.all, 'userRoles', username, querySettings] as const,\n courseAuthoringFlagStates: () => [...authzQueryKeys.all, 'courseAuthoringFlagStates'] as const,\n};\n\n/**\n * React Query hook to add new team members to a specific scope or manage the corresponding roles.\n * It provides a mutation function to add users with specified roles to the team or assign new roles.\n *\n * @example\n * const { mutate: assignTeamMembersRole } = useAssignTeamMembersRole();\n * assignTeamMembersRole({ data: { libraryId: 'lib:123', users: ['jdoe'], role: 'editor' } });\n */\nexport const useAssignTeamMembersRole = () => {\n const queryClient = useQueryClient();\n return useMutation({\n mutationFn: async ({ data }: {\n data: AssignTeamMembersRoleRequest;\n }) => assignTeamMembersRole(data),\n onSettled: (_data, error) => {\n if (!error) {\n queryClient.invalidateQueries({ queryKey: [...authzQueryKeys.all, 'userRoles'] });\n queryClient.invalidateQueries({\n predicate: (query) => query.queryKey.includes('teamMembersAssignments'),\n });\n }\n },\n });\n};\n\n/**\n * React Query hook to validate users exist without assigning roles.\n * It checks if the provided usernames/email addresses are valid.\n *\n * @example\n * const { mutate: validateUsers } = useValidateUsers();\n * validateUsers({ data: { users: ['jdoe', 'jane@example.com'] } });\n */\nexport const useValidateUsers = () => useMutation({\n mutationFn: async ({ data }: {\n data: ValidateUsersRequest;\n }) => validateUsers(data),\n});\n\n/**\n * React Query hook to remove roles for a specific team member within a scope.\n *\n * @example\n * const { mutate: revokeUserRoles } = useRevokeUserRoles();\n * revokeUserRoles({ data: { libraryId: 'lib:123', users: ['jdoe'], role: 'editor' } });\n */\nexport const useRevokeUserRoles = () => {\n const queryClient = useQueryClient();\n return useMutation({\n mutationFn: async ({ data }: {\n data: RevokeUserRolesRequest;\n }) => revokeUserRoles(data),\n onSettled: () => {\n queryClient.invalidateQueries({\n predicate: (query) => query.queryKey.includes('userRoles'),\n });\n queryClient.invalidateQueries({\n predicate: (query) => query.queryKey.includes('teamMembersAssignments'),\n });\n },\n });\n};\n\n/**\n * React Query hook to fetch team members grouped by user, with support for\n * filtering, sorting, and pagination. Each result is one user carrying the first few of\n * their role assignments plus how many there are; see `getTeamMembersAssignments` for\n * how filters narrow both.\n *\n * @param querySettings - Parameters for filtering by roles, scopes,\n * organizations, search term, sorting, and pagination.\n *\n * @example\n * const { data: teamMembers } = useTeamMembersAssignments({ roles: 'editor', pageSize: 20 });\n */\nexport const useTeamMembersAssignments = (querySettings: QuerySettings) => (\n useQuery<GetTeamMembersAssignmentsResponse, Error>({\n queryKey: authzQueryKeys.teamMembersAssignments(querySettings),\n queryFn: () => getTeamMembersAssignments(querySettings),\n staleTime: 1000 * 60 * 30, // refetch after 30 minutes\n })\n);\n\n/**\n * React Query hook to fetch a paginated, searchable list of organizations.\n * Results are cached for 30 minutes — suitable for both filter dropdowns and full listings.\n *\n * @param search - Optional text filter applied to organization names.\n * @param page - 1-based page number; defaults to the first page when omitted.\n * @param pageSize - Items per page; defaults to the API default when omitted.\n * @returns A `QueryResult<GetOrgsResponse>` with `results`, `count`, `next`, and `previous`.\n *\n * @example\n * ```tsx\n * const { data } = useOrgs('edX');\n * const orgs = data?.results ?? [];\n * ```\n */\nexport const useOrgs = (search?: string, page?: number, pageSize?: number) => useQuery<GetOrgsResponse, Error>({\n queryKey: authzQueryKeys.orgs(search, page, pageSize),\n queryFn: () => getOrgs(search, page, pageSize),\n staleTime: 1000 * 60 * 30,\n refetchOnWindowFocus: false,\n});\n\n/**\n * React Query hook to fetch a paginated, filterable list of scopes (courses or libraries).\n * Uses infinite query to support infinite scroll — call `fetchNextPage` to load more results.\n *\n * @param params - Filter parameters (all optional):\n * - `search` — text filter applied to scope names\n * - `scopeType` — filter by scope type (e.g. course, library)\n * - `org` — filter by organization\n * - `pageSize` — number of items per page\n * - `managementPermissionOnly` — when true, returns only scopes the requester can manage\n * @returns An `InfiniteQueryResult` whose `data.pages` contains the accumulated `GetScopesResponse` pages.\n *\n * @example\n * ```tsx\n * const { data, fetchNextPage, hasNextPage } = useScopes({ search: 'intro', orgs: ['edX'] });\n * const scopes = data?.pages.flatMap((p) => p.results) ?? [];\n * ```\n */\nexport const useScopes = (params: Omit<GetScopesParams, 'page'> = {}) => useInfiniteQuery<GetScopesResponse, Error>({\n queryKey: authzQueryKeys.scopes(params),\n queryFn: ({ pageParam }) => getScopes({ ...params, page: pageParam as number }),\n getNextPageParam: (lastPage) => {\n if (!lastPage.next) return undefined;\n try {\n const nextUrl = new URL(lastPage.next);\n const page = nextUrl.searchParams.get('page');\n return page ? parseInt(page, 10) : undefined;\n } catch {\n return undefined;\n }\n },\n initialPageParam: 1,\n staleTime: 1000 * 60 * 5,\n refetchOnWindowFocus: false,\n});\n\n/*\n * React Query hook to fetch all the roles assigned to a specific user.\n * It retrieves the full list of roles with the corresponding permissions.\n * @param username - The username of the user\n * @param querySettings - Optional query parameters for filtering, sorting, and pagination\n *\n * @example\n * ```tsx\n * const { data: userRoles } = useUserAssignedRoles('jdoe', querySettings);\n * ```\n*/\nexport const useUserAssignedRoles = (\n username?: string,\n querySettings?: QuerySettings,\n) => useQuery<GetUserAssignmentsResponse, Error>({\n queryKey: authzQueryKeys.userRoles(username, querySettings),\n queryFn: () => getUserAssignedRoles(username, querySettings),\n staleTime: 1000 * 60 * 30, // refetch after 30 minutes\n enabled: !!username,\n refetchOnWindowFocus: false,\n});\n\n/**\n * React Query hook to fetch the enablement state of the course-authoring waffle flag\n * across scopes (global, org overrides, course overrides).\n *\n * Follows the same caching rules as the permission-validation hooks.\n *\n * @example\n * ```tsx\n * const { data } = useCourseAuthoringFlagStates();\n * ```\n */\nexport const useCourseAuthoringFlagStates = () => useQuery<CourseAuthoringFlagStates, Error>({\n queryKey: authzQueryKeys.courseAuthoringFlagStates(),\n queryFn: () => getCourseAuthoringFlagStates(),\n staleTime: 1000 * 60 * 30,\n refetchOnWindowFocus: false,\n retry: false,\n});\n"]}
@@ -0,0 +1,7 @@
1
+ import { type DataTableRow } from '@openedx/paragon';
2
+ /**
3
+ * Returns a handler that toggles `row`, collapsing any other expanded row first so only one
4
+ * is open at a time. Paragon's `isExpandable` allows several, and a table whose rows expand
5
+ * into a nested table only reads clearly with one breakdown showing.
6
+ */
7
+ export declare const useExclusiveRowExpansion: (row: DataTableRow<unknown>) => () => void;
@@ -0,0 +1,24 @@
1
+ import { useContext } from 'react';
2
+ import { DataTableContext } from '@openedx/paragon';
3
+ /**
4
+ * Returns a handler that toggles `row`, collapsing any other expanded row first so only one
5
+ * is open at a time. Paragon's `isExpandable` allows several, and a table whose rows expand
6
+ * into a nested table only reads clearly with one breakdown showing.
7
+ */
8
+ export const useExclusiveRowExpansion = (row) => {
9
+ const instance = useContext(DataTableContext);
10
+ return () => {
11
+ var _a, _b;
12
+ if (!row.isExpanded && instance) {
13
+ const expanded = ((_a = instance.state) === null || _a === void 0 ? void 0 : _a.expanded) || {};
14
+ Object.keys(expanded).forEach((rowId) => {
15
+ var _a;
16
+ if (rowId !== row.id && expanded[rowId]) {
17
+ (_a = instance.toggleRowExpanded) === null || _a === void 0 ? void 0 : _a.call(instance, rowId, false);
18
+ }
19
+ });
20
+ }
21
+ (_b = row.toggleRowExpanded) === null || _b === void 0 ? void 0 : _b.call(row);
22
+ };
23
+ };
24
+ //# sourceMappingURL=useExclusiveRowExpansion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useExclusiveRowExpansion.js","sourceRoot":"","sources":["../../../src/authz-module/hooks/useExclusiveRowExpansion.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACnC,OAAO,EAAE,gBAAgB,EAAqB,MAAM,kBAAkB,CAAC;AASvE;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,GAA0B,EAAE,EAAE;IACrE,MAAM,QAAQ,GAAG,UAAU,CAAC,gBAAgB,CAAsB,CAAC;IAEnE,OAAO,GAAG,EAAE;;QACV,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,QAAQ,EAAE,CAAC;YAChC,MAAM,QAAQ,GAAG,CAAA,MAAA,QAAQ,CAAC,KAAK,0CAAE,QAAQ,KAAI,EAAE,CAAC;YAChD,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;;gBACtC,IAAI,KAAK,KAAK,GAAG,CAAC,EAAE,IAAI,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;oBACxC,MAAA,QAAQ,CAAC,iBAAiB,yDAAG,KAAK,EAAE,KAAK,CAAC,CAAC;gBAC7C,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QACD,MAAA,GAAG,CAAC,iBAAiB,mDAAI,CAAC;IAC5B,CAAC,CAAC;AACJ,CAAC,CAAC","sourcesContent":["import { useContext } from 'react';\nimport { DataTableContext, type DataTableRow } from '@openedx/paragon';\n\ninterface DataTableInstance {\n state?: {\n expanded?: Record<string, boolean>;\n };\n toggleRowExpanded?: (rowId: string, expanded: boolean) => void;\n}\n\n/**\n * Returns a handler that toggles `row`, collapsing any other expanded row first so only one\n * is open at a time. Paragon's `isExpandable` allows several, and a table whose rows expand\n * into a nested table only reads clearly with one breakdown showing.\n */\nexport const useExclusiveRowExpansion = (row: DataTableRow<unknown>) => {\n const instance = useContext(DataTableContext) as DataTableInstance;\n\n return () => {\n if (!row.isExpanded && instance) {\n const expanded = instance.state?.expanded || {};\n Object.keys(expanded).forEach((rowId) => {\n if (rowId !== row.id && expanded[rowId]) {\n instance.toggleRowExpanded?.(rowId, false);\n }\n });\n }\n row.toggleRowExpanded?.();\n };\n};\n"]}
@@ -26,7 +26,6 @@
26
26
 
27
27
  .authz-action-divider {
28
28
  border-right: var(--pgn-size-border-width) solid var(--pgn-color-border);
29
- height: var(--height-action-divider);
30
29
  width: 0;
31
30
  }
32
31
 
@@ -73,6 +72,120 @@
73
72
  max-height: 80vh;
74
73
  }
75
74
  }
75
+
76
+ // Where `.text-truncate` cuts; sized to the design's character budget.
77
+ .authz-cell-username { max-width: 15rem; } // ~24 characters
78
+ .authz-cell-email { max-width: 17rem; } // ~28 characters
79
+
80
+ &.team-members-table {
81
+ // Every row is two lines tall: the assigned-roles cell stacks scope over org.
82
+ th,
83
+ td {
84
+ padding-top: var(--pgn-spacing-spacer-3);
85
+ padding-bottom: var(--pgn-spacing-spacer-3);
86
+ }
87
+
88
+ // Keep the "+N more roles" label on one line.
89
+ td:nth-child(4) {
90
+ white-space: nowrap;
91
+ }
92
+
93
+ /*
94
+ Auto-layout starves "Assigned roles" in favour of the short Username/Email values,
95
+ so widths are pinned. Below `md` these percentages would crush the toggle and action
96
+ columns, so the table falls back to content sizing and scrolls inside Paragon's
97
+ `.pgn__data-table-container`.
98
+ */
99
+ @media (--pgn-size-breakpoint-min-width-md) {
100
+ .pgn__data-table {
101
+ table-layout: fixed;
102
+ }
103
+
104
+ th:nth-child(1) { width: 16%; } // Username
105
+ th:nth-child(2) { width: 18%; } // Email
106
+ th:nth-child(3) { width: 46%; } // Assigned roles
107
+ th:nth-child(4) { width: 13%; } // "+N more roles" toggle
108
+ th:nth-child(5) { width: 7%; } // Actions
109
+
110
+ td {
111
+ overflow: hidden;
112
+ }
113
+
114
+ // The nested table sizes its own columns; it must not inherit the widths above.
115
+ .team-members-table__subtable {
116
+ .pgn__data-table {
117
+ table-layout: auto;
118
+ }
119
+
120
+ th:nth-child(1),
121
+ th:nth-child(2),
122
+ th:nth-child(3) {
123
+ width: auto;
124
+ }
125
+ }
126
+ }
127
+
128
+ // One line per row, so it keeps tighter padding.
129
+ .team-members-table__subtable {
130
+ th,
131
+ td {
132
+ padding-top: var(--pgn-spacing-spacer-2);
133
+ padding-bottom: var(--pgn-spacing-spacer-2);
134
+ }
135
+ }
136
+ }
137
+
138
+ .team-members-table__subtable {
139
+ // The module turns `hr` horizontal below `sm`; this footer divider stays vertical.
140
+ hr {
141
+ border-right: var(--pgn-size-border-width) solid var(--pgn-color-border);
142
+ border-top: none;
143
+ height: 1rem;
144
+ width: 0;
145
+ }
146
+
147
+ // The Card already pads; the module's blanket 1rem would double it up.
148
+ .pgn__data-table-wrapper {
149
+ padding: 0;
150
+ }
151
+
152
+ // Matches PermissionTable's `bg-info-100 text-primary` band. Paragon renders its own
153
+ // `thead`, so the tokens are used rather than the utility classes.
154
+ thead th {
155
+ background-color: var(--pgn-color-info-100);
156
+ color: var(--pgn-color-primary-base);
157
+ }
158
+
159
+ /*
160
+ Paragon stripes with `.pgn__data-table.is-striped tr:nth-child(2n)` — a descendant
161
+ selector, so the striped outer table reaches in and `isStriped={false}` on the inner
162
+ one cannot override it. The `:not()` keeps a nested table that wants stripes.
163
+ */
164
+ .pgn__data-table:not(.is-striped) tr {
165
+ background-color: transparent;
166
+
167
+ // With the stripes gone, rows need an explicit divider to stay readable.
168
+ &:not(:last-child) td {
169
+ border-bottom: var(--pgn-size-border-width) solid var(--pgn-color-border);
170
+ }
171
+ }
172
+ }
173
+
174
+ // Floor the badge to the widest label, so "in" and the scope align down the column.
175
+ .authz-assigned-roles .authz-role-badge { min-width: 11rem; }
176
+
177
+ .authz-scope-cell {
178
+ min-width: 0; // Lets the flex child shrink far enough for `.text-truncate` to cut.
179
+ max-width: 25rem; // org subline, ~40 characters
180
+
181
+ &__name {
182
+ max-width: 22rem; // ~35 characters
183
+ }
184
+
185
+ &__org {
186
+ line-height: 1.2;
187
+ }
188
+ }
76
189
 
77
190
  @media(--pgn-size-breakpoint-max-width-sm){
78
191
  .flex-column-sm {
@@ -84,6 +197,12 @@
84
197
  width: 100%;
85
198
  }
86
199
  }
200
+
201
+ @media (--pgn-size-breakpoint-min-width-md) {
202
+ .authz-action-divider {
203
+ height: var(--height-action-divider);
204
+ }
205
+ }
87
206
  }
88
207
 
89
208
  .highlighted-users-input {
@@ -132,4 +251,3 @@
132
251
  .scope-list {
133
252
  max-height: 500px;
134
253
  }
135
-
@@ -39,10 +39,33 @@ declare const messages: {
39
39
  defaultMessage: string;
40
40
  description: string;
41
41
  };
42
+ 'authz.scope.aggregate.course': {
43
+ id: string;
44
+ defaultMessage: string;
45
+ description: string;
46
+ };
47
+ 'authz.scope.aggregate.library': {
48
+ id: string;
49
+ defaultMessage: string;
50
+ description: string;
51
+ };
42
52
  'authz.team.toast.retry.label': {
43
53
  id: string;
44
54
  defaultMessage: string;
45
55
  description: string;
46
56
  };
47
57
  };
58
+ /** Aggregate scope labels keyed by the kind of resource the scope covers. */
59
+ export declare const AGGREGATE_SCOPE_LABELS: {
60
+ course: {
61
+ id: string;
62
+ defaultMessage: string;
63
+ description: string;
64
+ };
65
+ library: {
66
+ id: string;
67
+ defaultMessage: string;
68
+ description: string;
69
+ };
70
+ };
48
71
  export default messages;
@@ -40,11 +40,29 @@ const messages = defineMessages({
40
40
  defaultMessage: '<Bold>The request took too long.</Bold> <Br></Br>Please check your connection and try again.',
41
41
  description: 'Request timeout message',
42
42
  },
43
+ // Aggregate scope labels, shared by every table that lists a scope beside its
44
+ // organization. The scope column names only the kind of resource; the organization
45
+ // column says how far it reaches. The wizard keeps its own, longer wording.
46
+ 'authz.scope.aggregate.course': {
47
+ id: 'authz.scope.aggregate.course',
48
+ defaultMessage: 'All courses',
49
+ description: 'Scope column label for a role that covers every course, either across the platform or within one organization. The organization column shows which.',
50
+ },
51
+ 'authz.scope.aggregate.library': {
52
+ id: 'authz.scope.aggregate.library',
53
+ defaultMessage: 'All libraries',
54
+ description: 'Scope column label for a role that covers every library, either across the platform or within one organization. The organization column shows which.',
55
+ },
43
56
  'authz.team.toast.retry.label': {
44
57
  id: 'authz.team.toast.retry.label',
45
58
  defaultMessage: 'Retry',
46
59
  description: 'Label for retry button.',
47
60
  },
48
61
  });
62
+ /** Aggregate scope labels keyed by the kind of resource the scope covers. */
63
+ export const AGGREGATE_SCOPE_LABELS = {
64
+ course: messages['authz.scope.aggregate.course'],
65
+ library: messages['authz.scope.aggregate.library'],
66
+ };
49
67
  export default messages;
50
68
  //# sourceMappingURL=messages.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/authz-module/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,MAAM,QAAQ,GAAG,cAAc,CAC7B;IACE,gCAAgC,EAAE;QAChC,EAAE,EAAE,gCAAgC;QACpC,cAAc,EAAE,kCAAkC;QAClD,WAAW,EAAE,+EAA+E;KAC7F;IACD,oCAAoC,EAAE;QACpC,EAAE,EAAE,oCAAoC;QACxC,cAAc,EAAE,aAAa;QAC7B,WAAW,EAAE,iCAAiC;KAC/C;IACD,wCAAwC,EAAE;QACxC,EAAE,EAAE,wCAAwC;QAC5C,cAAc,EAAE,gFAAgF;QAChG,WAAW,EAAE,uBAAuB;KACrC;IACD,kDAAkD,EAAE;QAClD,EAAE,EAAE,kDAAkD;QACtD,cAAc,EAAE,6KAA6K;QAC7L,WAAW,EAAE,2CAA2C;KACzD;IACD,oCAAoC,EAAE;QACpC,EAAE,EAAE,oCAAoC;QACxC,cAAc,EAAE,2FAA2F;QAC3G,WAAW,EAAE,+BAA+B;KAC7C;IACD,oCAAoC,EAAE;QACpC,EAAE,EAAE,oCAAoC;QACxC,cAAc,EAAE,iGAAiG;QACjH,WAAW,EAAE,2BAA2B;KACzC;IACD,oCAAoC,EAAE;QACpC,EAAE,EAAE,oCAAoC;QACxC,cAAc,EAAE,kGAAkG;QAClH,WAAW,EAAE,yCAAyC;KACvD;IACD,oCAAoC,EAAE;QACpC,EAAE,EAAE,oCAAoC;QACxC,cAAc,EAAE,8FAA8F;QAC9G,WAAW,EAAE,yBAAyB;KACvC;IACD,8BAA8B,EAAE;QAC9B,EAAE,EAAE,8BAA8B;QAClC,cAAc,EAAE,OAAO;QACvB,WAAW,EAAE,yBAAyB;KACvC;CACF,CACF,CAAC;AAEF,eAAe,QAAQ,CAAC","sourcesContent":["import { defineMessages } from '@openedx/frontend-base';\n\nconst messages = defineMessages(\n {\n 'authz.management.home.nav.link': {\n id: 'authz.management.home.nav.link',\n defaultMessage: 'Roles and Permissions Management',\n description: 'Text for the roles and permissions management home page title navigation link',\n },\n 'authz.management.assign.role.title': {\n id: 'authz.management.assign.role.title',\n defaultMessage: 'Assign Role',\n description: 'Text for the assign role button',\n },\n 'authz.team.toast.default.error.message': {\n id: 'authz.team.toast.default.error.message',\n defaultMessage: '<Bold>Something went wrong on our end.</Bold> <Br></Br>Please try again later.',\n description: 'Default error message',\n },\n 'authz.team.remove.user.toast.success.description': {\n id: 'authz.team.remove.user.toast.success.description',\n defaultMessage: 'The {role} role has been successfully removed.{rolesCount, plural, =0 { The user no longer has access to this library and has been removed from the member list.} other {}}',\n description: 'Team management remove user toast success',\n },\n 'authz.team.toast.500.error.message': {\n id: 'authz.team.toast.500.error.message',\n defaultMessage: '<Bold>We\\'re experiencing technical difficulties.</Bold> <Br></Br>Please try again later.',\n description: 'Internal server error message',\n },\n 'authz.team.toast.502.error.message': {\n id: 'authz.team.toast.502.error.message',\n defaultMessage: '<Bold>We\\'re having trouble connecting to our services.</Bold> <Br></Br>Please try again later.',\n description: 'Bad gateway error message',\n },\n 'authz.team.toast.503.error.message': {\n id: 'authz.team.toast.503.error.message',\n defaultMessage: '<Bold>The service is temporarily unavailable.</Bold> <Br></Br>Please try again in a few moments.',\n description: 'Service temporarily unavailable message',\n },\n 'authz.team.toast.408.error.message': {\n id: 'authz.team.toast.408.error.message',\n defaultMessage: '<Bold>The request took too long.</Bold> <Br></Br>Please check your connection and try again.',\n description: 'Request timeout message',\n },\n 'authz.team.toast.retry.label': {\n id: 'authz.team.toast.retry.label',\n defaultMessage: 'Retry',\n description: 'Label for retry button.',\n },\n },\n);\n\nexport default messages;\n"]}
1
+ {"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/authz-module/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,MAAM,QAAQ,GAAG,cAAc,CAC7B;IACE,gCAAgC,EAAE;QAChC,EAAE,EAAE,gCAAgC;QACpC,cAAc,EAAE,kCAAkC;QAClD,WAAW,EAAE,+EAA+E;KAC7F;IACD,oCAAoC,EAAE;QACpC,EAAE,EAAE,oCAAoC;QACxC,cAAc,EAAE,aAAa;QAC7B,WAAW,EAAE,iCAAiC;KAC/C;IACD,wCAAwC,EAAE;QACxC,EAAE,EAAE,wCAAwC;QAC5C,cAAc,EAAE,gFAAgF;QAChG,WAAW,EAAE,uBAAuB;KACrC;IACD,kDAAkD,EAAE;QAClD,EAAE,EAAE,kDAAkD;QACtD,cAAc,EAAE,6KAA6K;QAC7L,WAAW,EAAE,2CAA2C;KACzD;IACD,oCAAoC,EAAE;QACpC,EAAE,EAAE,oCAAoC;QACxC,cAAc,EAAE,2FAA2F;QAC3G,WAAW,EAAE,+BAA+B;KAC7C;IACD,oCAAoC,EAAE;QACpC,EAAE,EAAE,oCAAoC;QACxC,cAAc,EAAE,iGAAiG;QACjH,WAAW,EAAE,2BAA2B;KACzC;IACD,oCAAoC,EAAE;QACpC,EAAE,EAAE,oCAAoC;QACxC,cAAc,EAAE,kGAAkG;QAClH,WAAW,EAAE,yCAAyC;KACvD;IACD,oCAAoC,EAAE;QACpC,EAAE,EAAE,oCAAoC;QACxC,cAAc,EAAE,8FAA8F;QAC9G,WAAW,EAAE,yBAAyB;KACvC;IACD,8EAA8E;IAC9E,mFAAmF;IACnF,4EAA4E;IAC5E,8BAA8B,EAAE;QAC9B,EAAE,EAAE,8BAA8B;QAClC,cAAc,EAAE,aAAa;QAC7B,WAAW,EAAE,qJAAqJ;KACnK;IACD,+BAA+B,EAAE;QAC/B,EAAE,EAAE,+BAA+B;QACnC,cAAc,EAAE,eAAe;QAC/B,WAAW,EAAE,sJAAsJ;KACpK;IACD,8BAA8B,EAAE;QAC9B,EAAE,EAAE,8BAA8B;QAClC,cAAc,EAAE,OAAO;QACvB,WAAW,EAAE,yBAAyB;KACvC;CACF,CACF,CAAC;AAEF,6EAA6E;AAC7E,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,MAAM,EAAE,QAAQ,CAAC,8BAA8B,CAAC;IAChD,OAAO,EAAE,QAAQ,CAAC,+BAA+B,CAAC;CACnD,CAAC;AAEF,eAAe,QAAQ,CAAC","sourcesContent":["import { defineMessages } from '@openedx/frontend-base';\n\nconst messages = defineMessages(\n {\n 'authz.management.home.nav.link': {\n id: 'authz.management.home.nav.link',\n defaultMessage: 'Roles and Permissions Management',\n description: 'Text for the roles and permissions management home page title navigation link',\n },\n 'authz.management.assign.role.title': {\n id: 'authz.management.assign.role.title',\n defaultMessage: 'Assign Role',\n description: 'Text for the assign role button',\n },\n 'authz.team.toast.default.error.message': {\n id: 'authz.team.toast.default.error.message',\n defaultMessage: '<Bold>Something went wrong on our end.</Bold> <Br></Br>Please try again later.',\n description: 'Default error message',\n },\n 'authz.team.remove.user.toast.success.description': {\n id: 'authz.team.remove.user.toast.success.description',\n defaultMessage: 'The {role} role has been successfully removed.{rolesCount, plural, =0 { The user no longer has access to this library and has been removed from the member list.} other {}}',\n description: 'Team management remove user toast success',\n },\n 'authz.team.toast.500.error.message': {\n id: 'authz.team.toast.500.error.message',\n defaultMessage: '<Bold>We\\'re experiencing technical difficulties.</Bold> <Br></Br>Please try again later.',\n description: 'Internal server error message',\n },\n 'authz.team.toast.502.error.message': {\n id: 'authz.team.toast.502.error.message',\n defaultMessage: '<Bold>We\\'re having trouble connecting to our services.</Bold> <Br></Br>Please try again later.',\n description: 'Bad gateway error message',\n },\n 'authz.team.toast.503.error.message': {\n id: 'authz.team.toast.503.error.message',\n defaultMessage: '<Bold>The service is temporarily unavailable.</Bold> <Br></Br>Please try again in a few moments.',\n description: 'Service temporarily unavailable message',\n },\n 'authz.team.toast.408.error.message': {\n id: 'authz.team.toast.408.error.message',\n defaultMessage: '<Bold>The request took too long.</Bold> <Br></Br>Please check your connection and try again.',\n description: 'Request timeout message',\n },\n // Aggregate scope labels, shared by every table that lists a scope beside its\n // organization. The scope column names only the kind of resource; the organization\n // column says how far it reaches. The wizard keeps its own, longer wording.\n 'authz.scope.aggregate.course': {\n id: 'authz.scope.aggregate.course',\n defaultMessage: 'All courses',\n description: 'Scope column label for a role that covers every course, either across the platform or within one organization. The organization column shows which.',\n },\n 'authz.scope.aggregate.library': {\n id: 'authz.scope.aggregate.library',\n defaultMessage: 'All libraries',\n description: 'Scope column label for a role that covers every library, either across the platform or within one organization. The organization column shows which.',\n },\n 'authz.team.toast.retry.label': {\n id: 'authz.team.toast.retry.label',\n defaultMessage: 'Retry',\n description: 'Label for retry button.',\n },\n },\n);\n\n/** Aggregate scope labels keyed by the kind of resource the scope covers. */\nexport const AGGREGATE_SCOPE_LABELS = {\n course: messages['authz.scope.aggregate.course'],\n library: messages['authz.scope.aggregate.library'],\n};\n\nexport default messages;\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"useScopeListData.js","sourceRoot":"","sources":["../../../../src/authz-module/role-assignation-wizard/hooks/useScopeListData.ts"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react';\nimport { useIntl } from '@openedx/frontend-base';\nimport { Scope } from '@src/types';\nimport { useOrgs, useScopes } from '@src/authz-module/data/hooks';\nimport { useCourseAuthoringFlag } from '@src/authz-module/hooks/useCourseAuthoringFlag';\nimport { getOrgAggregateScopeKey, getPlatformAggregateScopeKey } from '@src/authz-module/constants';\nimport type { ContextType } from '@src/authz-module/constants';\nimport messages from '@src/authz-module/role-assignation-wizard/messages';\nimport useScopePermissions from './useScopePermissions';\n\ninterface UseScopeListDataParams {\n contextType: string | undefined;\n search: string;\n orgs: string[];\n}\n\nconst groupByOrg = (acc: Record<string, Scope[]>, scope: Scope): Record<string, Scope[]> => {\n const orgSlug = scope.org!.shortName;\n if (!acc[orgSlug]) acc[orgSlug] = [];\n acc[orgSlug].push(scope);\n return acc;\n};\n\nconst useScopeListData = ({ contextType, search, orgs }: UseScopeListDataParams) => {\n const intl = useIntl();\n const {\n data: scopesData,\n fetchNextPage,\n hasNextPage,\n isFetchingNextPage,\n isLoading,\n isError,\n } = useScopes({\n scopeType: contextType,\n search: search || undefined,\n orgs: orgs.length ? orgs : undefined,\n managementPermissionOnly: true,\n });\n\n const { data: orgsData } = useOrgs();\n const organizations = orgsData?.results;\n\n const { isCourseEnabled, isOrgAuthoringEnabled } = useCourseAuthoringFlag();\n\n const { allScopes, totalCount } = useMemo(\n () => {\n const scopes = scopesData?.pages.flatMap((page) => page.results) ?? [];\n const serverCount = scopesData?.pages[0]?.count ?? 0;\n // Course scopes are gated by the course-authoring flag; libraries always pass.\n if (contextType !== 'course') return { allScopes: scopes, totalCount: serverCount };\n const enabledScopes = scopes.filter((scope) => isCourseEnabled(scope.externalKey));\n // The server count includes authoring-disabled courses this filter hides; subtract\n // the ones already loaded so the reported total converges as pages arrive.\n return {\n allScopes: enabledScopes,\n totalCount: serverCount - (scopes.length - enabledScopes.length),\n };\n },\n [scopesData, contextType, isCourseEnabled],\n );\n\n const scopesByOrg = useMemo(\n () => allScopes\n .filter((s: Scope) => !!s.org)\n .reduce<Record<string, Scope[]>>(groupByOrg, {}),\n [allScopes],\n );\n\n const orderedOrgs = useMemo(() => {\n const orgSlugs = new Set(Object.keys(scopesByOrg));\n // Add orgs from the organizations list (respecting the active org filter)\n organizations?.forEach((org) => {\n if (!orgs.length || orgs.includes(org.shortName)) {\n orgSlugs.add(org.shortName);\n }\n });\n return [...orgSlugs].sort();\n }, [scopesByOrg, organizations, orgs]);\n\n const { hasPlatformPermission, orgHasPermission } = useScopePermissions({ contextType, orderedOrgs });\n\n const aggregateDescription = contextType === 'course'\n ? intl.formatMessage(messages['wizard.step2.scope.aggregate.description.course'])\n : intl.formatMessage(messages['wizard.step2.scope.aggregate.description.library']);\n\n const platformAggregateLabel = contextType === 'course'\n ? intl.formatMessage(messages['wizard.step2.scope.aggregate.platform.label.course'])\n : intl.formatMessage(messages['wizard.step2.scope.aggregate.platform.label.library']);\n\n const orgAggregateLabel = contextType === 'course'\n ? intl.formatMessage(messages['wizard.step2.scopeList.aggregate.label.course'])\n : intl.formatMessage(messages['wizard.step2.scopeList.aggregate.label.library']);\n\n const platformAggregateScopeItem: Scope | null = (contextType && hasPlatformPermission)\n ? {\n externalKey: getPlatformAggregateScopeKey(contextType as ContextType),\n displayName: platformAggregateLabel,\n description: aggregateDescription,\n org: null,\n }\n : null;\n\n /**\n * Builds a map of org-level aggregate scopes keyed by org slug.\n *\n * Each entry represents a wildcard scope that grants access to all courses or\n * libraries within an org (e.g. `course-v1:OrgSlug+*` or `lib:OrgSlug:*`).\n * Only orgs where the current user already holds permission are included.\n *\n * Returns an empty object when `contextType` is not yet defined.\n */\n const orgAggregateScopeItems = useMemo<Record<string, Scope>>(() => {\n if (!contextType) return {};\n return Object.fromEntries(\n orderedOrgs\n .filter((orgSlug) => orgHasPermission[orgSlug])\n // The org-wide aggregate is only offered when authoring is enabled for that org.\n .filter((orgSlug) => contextType !== 'course' || isOrgAuthoringEnabled(orgSlug))\n .map((orgSlug) => [\n orgSlug,\n {\n externalKey: getOrgAggregateScopeKey(contextType as ContextType, orgSlug),\n displayName: orgAggregateLabel,\n description: aggregateDescription,\n org: { id: '0', name: orgSlug, shortName: orgSlug },\n } satisfies Scope,\n ]),\n );\n }, [orderedOrgs, contextType, orgHasPermission, orgAggregateLabel, aggregateDescription, isOrgAuthoringEnabled]);\n\n return {\n organizations,\n orderedOrgs,\n scopesByOrg,\n allScopes,\n totalCount,\n queryState: {\n isLoading, isFetchingNextPage, hasNextPage, isError, fetchNextPage,\n },\n platformAggregateScopeItem,\n orgAggregateScopeItems,\n };\n};\n\nexport default useScopeListData;\n"]}
1
+ {"version":3,"file":"useScopeListData.js","sourceRoot":"","sources":["../../../../src/authz-module/role-assignation-wizard/hooks/useScopeListData.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAE,sBAAsB,EAAE,MAAM,gDAAgD,CAAC;AACxF,OAAO,EAAE,uBAAuB,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AAEpG,OAAO,QAAQ,MAAM,aAAa,CAAC;AACnC,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AAQxD,MAAM,UAAU,GAAG,CAAC,GAA4B,EAAE,KAAY,EAA2B,EAAE;IACzF,MAAM,OAAO,GAAG,KAAK,CAAC,GAAI,CAAC,SAAS,CAAC;IACrC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;QAAE,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IACrC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACzB,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAA0B,EAAE,EAAE;IACjF,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,MAAM,EACJ,IAAI,EAAE,UAAU,EAChB,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,SAAS,EACT,OAAO,GACR,GAAG,SAAS,CAAC;QACZ,SAAS,EAAE,WAAW;QACtB,MAAM,EAAE,MAAM,IAAI,SAAS;QAC3B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QACpC,wBAAwB,EAAE,IAAI;KAC/B,CAAC,CAAC;IAEH,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC;IACrC,MAAM,aAAa,GAAG,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,OAAO,CAAC;IAExC,MAAM,EAAE,eAAe,EAAE,qBAAqB,EAAE,GAAG,sBAAsB,EAAE,CAAC;IAE5E,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,OAAO,CACvC,GAAG,EAAE;;QACH,MAAM,MAAM,GAAG,MAAA,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,mCAAI,EAAE,CAAC;QACvE,MAAM,WAAW,GAAG,MAAA,MAAA,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,KAAK,CAAC,CAAC,CAAC,0CAAE,KAAK,mCAAI,CAAC,CAAC;QACrD,+EAA+E;QAC/E,IAAI,WAAW,KAAK,QAAQ;YAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;QACpF,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;QACnF,mFAAmF;QACnF,2EAA2E;QAC3E,OAAO;YACL,SAAS,EAAE,aAAa;YACxB,UAAU,EAAE,WAAW,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC;SACjE,CAAC;IACJ,CAAC,EACD,CAAC,UAAU,EAAE,WAAW,EAAE,eAAe,CAAC,CAC3C,CAAC;IAEF,MAAM,WAAW,GAAG,OAAO,CACzB,GAAG,EAAE,CAAC,SAAS;SACZ,MAAM,CAAC,CAAC,CAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;SAC7B,MAAM,CAA0B,UAAU,EAAE,EAAE,CAAC,EAClD,CAAC,SAAS,CAAC,CACZ,CAAC;IAEF,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE;QAC/B,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;QACnD,0EAA0E;QAC1E,aAAa,aAAb,aAAa,uBAAb,aAAa,CAAE,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;YAC7B,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;gBACjD,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9B,CAAC,EAAE,CAAC,WAAW,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;IAEvC,MAAM,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,GAAG,mBAAmB,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAAC;IAEtG,MAAM,oBAAoB,GAAG,WAAW,KAAK,QAAQ;QACnD,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,iDAAiD,CAAC,CAAC;QACjF,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,kDAAkD,CAAC,CAAC,CAAC;IAErF,MAAM,sBAAsB,GAAG,WAAW,KAAK,QAAQ;QACrD,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,oDAAoD,CAAC,CAAC;QACpF,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,qDAAqD,CAAC,CAAC,CAAC;IAExF,MAAM,iBAAiB,GAAG,WAAW,KAAK,QAAQ;QAChD,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,+CAA+C,CAAC,CAAC;QAC/E,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,gDAAgD,CAAC,CAAC,CAAC;IAEnF,MAAM,0BAA0B,GAAiB,CAAC,WAAW,IAAI,qBAAqB,CAAC;QACrF,CAAC,CAAC;YACE,WAAW,EAAE,4BAA4B,CAAC,WAA0B,CAAC;YACrE,WAAW,EAAE,sBAAsB;YACnC,WAAW,EAAE,oBAAoB;YACjC,GAAG,EAAE,IAAI;SACV;QACH,CAAC,CAAC,IAAI,CAAC;IAET;;;;;;;;OAQG;IACH,MAAM,sBAAsB,GAAG,OAAO,CAAwB,GAAG,EAAE;QACjE,IAAI,CAAC,WAAW;YAAE,OAAO,EAAE,CAAC;QAC5B,OAAO,MAAM,CAAC,WAAW,CACvB,WAAW;aACR,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;YAC/C,iFAAiF;aAChF,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,WAAW,KAAK,QAAQ,IAAI,qBAAqB,CAAC,OAAO,CAAC,CAAC;aAC/E,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;YAChB,OAAO;YACP;gBACE,WAAW,EAAE,uBAAuB,CAAC,WAA0B,EAAE,OAAO,CAAC;gBACzE,WAAW,EAAE,iBAAiB;gBAC9B,WAAW,EAAE,oBAAoB;gBACjC,GAAG,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE;aACpC;SAClB,CAAC,CACL,CAAC;IACJ,CAAC,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,qBAAqB,CAAC,CAAC,CAAC;IAEjH,OAAO;QACL,aAAa;QACb,WAAW;QACX,WAAW;QACX,SAAS;QACT,UAAU;QACV,UAAU,EAAE;YACV,SAAS,EAAE,kBAAkB,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa;SACnE;QACD,0BAA0B;QAC1B,sBAAsB;KACvB,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,gBAAgB,CAAC","sourcesContent":["import { useMemo } from 'react';\nimport { useIntl } from '@openedx/frontend-base';\nimport { Scope } from '@src/types';\nimport { useOrgs, useScopes } from '@src/authz-module/data/hooks';\nimport { useCourseAuthoringFlag } from '@src/authz-module/hooks/useCourseAuthoringFlag';\nimport { getOrgAggregateScopeKey, getPlatformAggregateScopeKey } from '@src/authz-module/constants';\nimport type { ContextType } from '@src/authz-module/constants';\nimport messages from '../messages';\nimport useScopePermissions from './useScopePermissions';\n\ninterface UseScopeListDataParams {\n contextType: string | undefined;\n search: string;\n orgs: string[];\n}\n\nconst groupByOrg = (acc: Record<string, Scope[]>, scope: Scope): Record<string, Scope[]> => {\n const orgSlug = scope.org!.shortName;\n if (!acc[orgSlug]) acc[orgSlug] = [];\n acc[orgSlug].push(scope);\n return acc;\n};\n\nconst useScopeListData = ({ contextType, search, orgs }: UseScopeListDataParams) => {\n const intl = useIntl();\n const {\n data: scopesData,\n fetchNextPage,\n hasNextPage,\n isFetchingNextPage,\n isLoading,\n isError,\n } = useScopes({\n scopeType: contextType,\n search: search || undefined,\n orgs: orgs.length ? orgs : undefined,\n managementPermissionOnly: true,\n });\n\n const { data: orgsData } = useOrgs();\n const organizations = orgsData?.results;\n\n const { isCourseEnabled, isOrgAuthoringEnabled } = useCourseAuthoringFlag();\n\n const { allScopes, totalCount } = useMemo(\n () => {\n const scopes = scopesData?.pages.flatMap((page) => page.results) ?? [];\n const serverCount = scopesData?.pages[0]?.count ?? 0;\n // Course scopes are gated by the course-authoring flag; libraries always pass.\n if (contextType !== 'course') return { allScopes: scopes, totalCount: serverCount };\n const enabledScopes = scopes.filter((scope) => isCourseEnabled(scope.externalKey));\n // The server count includes authoring-disabled courses this filter hides; subtract\n // the ones already loaded so the reported total converges as pages arrive.\n return {\n allScopes: enabledScopes,\n totalCount: serverCount - (scopes.length - enabledScopes.length),\n };\n },\n [scopesData, contextType, isCourseEnabled],\n );\n\n const scopesByOrg = useMemo(\n () => allScopes\n .filter((s: Scope) => !!s.org)\n .reduce<Record<string, Scope[]>>(groupByOrg, {}),\n [allScopes],\n );\n\n const orderedOrgs = useMemo(() => {\n const orgSlugs = new Set(Object.keys(scopesByOrg));\n // Add orgs from the organizations list (respecting the active org filter)\n organizations?.forEach((org) => {\n if (!orgs.length || orgs.includes(org.shortName)) {\n orgSlugs.add(org.shortName);\n }\n });\n return [...orgSlugs].sort();\n }, [scopesByOrg, organizations, orgs]);\n\n const { hasPlatformPermission, orgHasPermission } = useScopePermissions({ contextType, orderedOrgs });\n\n const aggregateDescription = contextType === 'course'\n ? intl.formatMessage(messages['wizard.step2.scope.aggregate.description.course'])\n : intl.formatMessage(messages['wizard.step2.scope.aggregate.description.library']);\n\n const platformAggregateLabel = contextType === 'course'\n ? intl.formatMessage(messages['wizard.step2.scope.aggregate.platform.label.course'])\n : intl.formatMessage(messages['wizard.step2.scope.aggregate.platform.label.library']);\n\n const orgAggregateLabel = contextType === 'course'\n ? intl.formatMessage(messages['wizard.step2.scopeList.aggregate.label.course'])\n : intl.formatMessage(messages['wizard.step2.scopeList.aggregate.label.library']);\n\n const platformAggregateScopeItem: Scope | null = (contextType && hasPlatformPermission)\n ? {\n externalKey: getPlatformAggregateScopeKey(contextType as ContextType),\n displayName: platformAggregateLabel,\n description: aggregateDescription,\n org: null,\n }\n : null;\n\n /**\n * Builds a map of org-level aggregate scopes keyed by org slug.\n *\n * Each entry represents a wildcard scope that grants access to all courses or\n * libraries within an org (e.g. `course-v1:OrgSlug+*` or `lib:OrgSlug:*`).\n * Only orgs where the current user already holds permission are included.\n *\n * Returns an empty object when `contextType` is not yet defined.\n */\n const orgAggregateScopeItems = useMemo<Record<string, Scope>>(() => {\n if (!contextType) return {};\n return Object.fromEntries(\n orderedOrgs\n .filter((orgSlug) => orgHasPermission[orgSlug])\n // The org-wide aggregate is only offered when authoring is enabled for that org.\n .filter((orgSlug) => contextType !== 'course' || isOrgAuthoringEnabled(orgSlug))\n .map((orgSlug) => [\n orgSlug,\n {\n externalKey: getOrgAggregateScopeKey(contextType as ContextType, orgSlug),\n displayName: orgAggregateLabel,\n description: aggregateDescription,\n org: { id: '0', name: orgSlug, shortName: orgSlug },\n } satisfies Scope,\n ]),\n );\n }, [orderedOrgs, contextType, orgHasPermission, orgAggregateLabel, aggregateDescription, isOrgAuthoringEnabled]);\n\n return {\n organizations,\n orderedOrgs,\n scopesByOrg,\n allScopes,\n totalCount,\n queryState: {\n isLoading, isFetchingNextPage, hasNextPage, isError, fetchNextPage,\n },\n platformAggregateScopeItem,\n orgAggregateScopeItems,\n };\n};\n\nexport default useScopeListData;\n"]}
@@ -6,18 +6,36 @@ import { DataTable, TextFilter, } from '@openedx/paragon';
6
6
  import { useToastManager } from '../../components/ToastManager/ToastManagerContext';
7
7
  import { LIBRARY_ROLE_KEYS } from '../roles-permissions';
8
8
  import { useViewTeamPermissions } from '../hooks/useViewTeamPermissions';
9
- import { useCourseAuthoringFlag } from '../hooks/useCourseAuthoringFlag';
10
9
  import { useQuerySettings } from '../hooks/useQuerySettings';
11
10
  import OrgFilter from '../components/TableControlBar/OrgFilter';
12
11
  import RolesFilter from '../components/TableControlBar/RolesFilter';
13
12
  import ScopesFilter from '../components/TableControlBar/ScopesFilter';
14
13
  import TableControlBar from '../components/TableControlBar/TableControlBar';
15
14
  import { getCellHeader } from '../utils';
16
- import { createViewActionCell, NameCell, OrgCell, RoleCell, ScopeCell, } from '../components/TableCells';
17
- import { useAllRoleAssignments } from '../data/hooks';
15
+ import { useTeamMembersAssignments } from '../data/hooks';
18
16
  import { TABLE_DEFAULT_PAGE_SIZE } from '../constants';
19
17
  import messages from './messages';
20
18
  import TableFooter from '../components/TableFooter/TableFooter';
19
+ import AssignedRolesCell from './components/AssignedRolesCell';
20
+ import { EmailCell, NameCell } from './components/TeamMemberCells';
21
+ import MoreRolesToggle from './components/MoreRolesToggle';
22
+ import UserAssignmentsSubTable from './components/UserAssignmentsSubTable';
23
+ import TeamMemberViewActionCell from './components/TeamMemberViewActionCell';
24
+ const toastedErrors = new WeakSet();
25
+ /**
26
+ * Stable stand-in for a query that has not resolved. A fresh object literal here would
27
+ * hand `DataTable` a new `data` array on every render, and react-table resets its
28
+ * expanded-row state whenever `data` changes identity.
29
+ */
30
+ const EMPTY_ASSIGNMENTS = {
31
+ results: [], count: 0, next: null, previous: null,
32
+ };
33
+ // Org, scope and role stay in the column set — TableControlBar derives its filter
34
+ // controls from the columns — but are hidden, since the design surfaces them inside
35
+ // each user's role breakdown instead of as top-level columns. They carry no accessor
36
+ // (a user row has no single org/scope/role), so each opts into filtering with
37
+ // `defaultCanFilter`, which react-table otherwise infers from the accessor.
38
+ const HIDDEN_FILTER_COLUMNS = ['org', 'scope', 'role'];
21
39
  const TeamMembersTable = ({ presetScope }) => {
22
40
  const intl = useIntl();
23
41
  const { showErrorToast } = useToastManager();
@@ -34,33 +52,45 @@ const TeamMembersTable = ({ presetScope }) => {
34
52
  } : undefined;
35
53
  const { querySettings, handleTableFetch } = useQuerySettings(initialQuerySettings);
36
54
  const { isCourseViewAllowed } = useViewTeamPermissions();
37
- const { isCourseEnabled } = useCourseAuthoringFlag();
38
55
  const effectiveQuerySettings = useMemo(() => {
39
56
  if (isCourseViewAllowed || querySettings.roles)
40
57
  return querySettings;
41
58
  return Object.assign(Object.assign({}, querySettings), { roles: LIBRARY_ROLE_KEYS });
42
59
  }, [isCourseViewAllowed, querySettings]);
43
- const { data: { results: roleAssignments, count } = { results: [], count: 0 }, isLoading: isLoadingAllRoleAssignments, error, refetch, } = useAllRoleAssignments(effectiveQuerySettings);
44
- const viewActionCell = useMemo(() => createViewActionCell({ isCourseEnabled }), [isCourseEnabled]);
60
+ const { data: { results: teamMembers, count } = EMPTY_ASSIGNMENTS, isLoading: isLoadingTeamMembers, error, refetch, } = useTeamMembersAssignments(effectiveQuerySettings);
45
61
  const initialFilters = presetScope ? [{ id: 'scope', value: [presetScope] }] : [];
62
+ /**
63
+ * Only transient failures reach here.
64
+ */
46
65
  useEffect(() => {
47
- if (error) {
66
+ if (error && !toastedErrors.has(error)) {
67
+ toastedErrors.add(error);
48
68
  showErrorToast(error, refetch);
49
69
  }
50
70
  }, [error, showErrorToast, refetch]);
51
71
  const pageCount = Math.ceil(count / TABLE_DEFAULT_PAGE_SIZE);
52
72
  const fetchData = useMemo(() => debounce(handleTableFetch, 500), [handleTableFetch]);
53
73
  useEffect(() => () => fetchData.cancel(), [fetchData]);
54
- return (_jsxs(DataTable, { isFilterable: true, isPaginated: true, isSortable: true, manualFilters: true, manualPagination: true, manualSortBy: true, numBreakoutFilters: 4, fetchData: fetchData, data: roleAssignments, itemCount: count, pageCount: pageCount, initialState: { pageSize: TABLE_DEFAULT_PAGE_SIZE, filters: initialFilters }, isLoading: isLoadingAllRoleAssignments, additionalColumns: [
74
+ const showingUsersLabel = intl.formatMessage(messages['authz.team.members.table.showing.users.text'], { pageSize: teamMembers.length, itemCount: count });
75
+ return (_jsxs(DataTable, { isExpandable: true, isFilterable: true, isPaginated: true, isSortable: true, manualFilters: true, manualPagination: true, manualSortBy: true, numBreakoutFilters: 4, fetchData: fetchData, data: teamMembers, itemCount: count, pageCount: pageCount, initialState: {
76
+ pageSize: TABLE_DEFAULT_PAGE_SIZE,
77
+ filters: initialFilters,
78
+ hiddenColumns: HIDDEN_FILTER_COLUMNS,
79
+ }, isLoading: isLoadingTeamMembers, renderRowSubComponent: ({ row }) => (_jsx(UserAssignmentsSubTable, { row: row })), additionalColumns: [
80
+ {
81
+ id: 'moreRoles',
82
+ Header: '',
83
+ Cell: MoreRolesToggle,
84
+ },
55
85
  {
56
86
  id: 'action',
57
87
  Header: intl.formatMessage(messages['authz.team.members.table.column.actions.title']),
58
- Cell: viewActionCell,
88
+ Cell: TeamMemberViewActionCell,
59
89
  },
60
90
  ], columns: [
61
91
  {
62
92
  id: 'username',
63
- Header: intl.formatMessage(messages['authz.team.members.table.column.name.title']),
93
+ Header: intl.formatMessage(messages['authz.team.members.table.column.username.title']),
64
94
  accessor: 'username',
65
95
  Cell: NameCell,
66
96
  filter: 'text',
@@ -70,38 +100,46 @@ const TeamMembersTable = ({ presetScope }) => {
70
100
  {
71
101
  Header: intl.formatMessage(messages['authz.team.members.table.column.email.title']),
72
102
  accessor: 'email',
103
+ Cell: EmailCell,
73
104
  disableFilters: true,
74
105
  filter: 'text',
75
106
  Filter: TextFilter,
76
107
  },
77
108
  {
109
+ id: 'assignedRoles',
110
+ Header: intl.formatMessage(messages['authz.team.members.table.column.assigned.roles.title']),
111
+ Cell: AssignedRolesCell,
112
+ disableFilters: true,
113
+ disableSortBy: true,
114
+ },
115
+ {
116
+ id: 'org',
78
117
  Header: getCellHeader('org', intl.formatMessage(messages['authz.team.members.table.column.organization.title']), columnsWithFiltersApplied),
79
- accessor: 'org',
80
- Cell: OrgCell,
81
118
  filter: 'includesValue',
119
+ defaultCanFilter: true,
82
120
  Filter: OrgFilter,
83
121
  filterButtonText: intl.formatMessage(messages['authz.team.members.table.column.organization.title']),
84
122
  filterOrder: 2,
85
123
  },
86
124
  {
125
+ id: 'scope',
87
126
  Header: getCellHeader('scope', intl.formatMessage(messages['authz.team.members.table.column.scope.title']), columnsWithFiltersApplied),
88
- accessor: 'scope',
89
- Cell: ScopeCell,
90
127
  filter: 'includesValue',
128
+ defaultCanFilter: true,
91
129
  Filter: ScopesFilter,
92
130
  filterButtonText: intl.formatMessage(messages['authz.team.members.table.column.scope.title']),
93
131
  filterOrder: 4,
94
132
  },
95
133
  {
134
+ id: 'role',
96
135
  Header: getCellHeader('role', intl.formatMessage(messages['authz.team.members.table.column.role.title']), columnsWithFiltersApplied),
97
- accessor: 'role',
98
136
  filter: 'includesValue',
99
- Cell: RoleCell,
137
+ defaultCanFilter: true,
100
138
  Filter: RolesFilter,
101
139
  filterButtonText: intl.formatMessage(messages['authz.team.members.table.column.role.title']),
102
140
  filterOrder: 3,
103
141
  },
104
- ], children: [_jsx(TableControlBar, { onFilterChange: setColumnsWithFiltersApplied }), _jsx(DataTable.Table, {}), _jsx(TableFooter, {})] }));
142
+ ], children: [_jsx(TableControlBar, { onFilterChange: setColumnsWithFiltersApplied, countLabel: showingUsersLabel }), _jsx(DataTable.Table, {}), _jsx(TableFooter, { showingMessage: messages['authz.team.members.table.showing.users.text'] })] }));
105
143
  };
106
144
  export default TeamMembersTable;
107
145
  //# sourceMappingURL=TeamMembersTable.js.map