@openedx/frontend-app-gradebook 2.0.0-alpha.3 → 2.0.0-alpha.5

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 (73) hide show
  1. package/dist/Gradebook.d.ts +7 -0
  2. package/dist/Gradebook.js +12 -0
  3. package/dist/Gradebook.js.map +1 -0
  4. package/dist/Main.d.ts +0 -1
  5. package/dist/Main.js +7 -9
  6. package/dist/Main.js.map +1 -1
  7. package/dist/app.js +2 -0
  8. package/dist/app.js.map +1 -1
  9. package/dist/components/BulkManagementHistoryView/ResultsSummary.d.ts +2 -3
  10. package/dist/components/BulkManagementHistoryView/ResultsSummary.js +6 -3
  11. package/dist/components/BulkManagementHistoryView/ResultsSummary.js.map +1 -1
  12. package/dist/components/BulkManagementHistoryView/data/api.d.ts +2 -2
  13. package/dist/components/BulkManagementHistoryView/data/api.js +4 -2
  14. package/dist/components/BulkManagementHistoryView/data/api.js.map +1 -1
  15. package/dist/components/BulkManagementHistoryView/data/apiHook.js +1 -1
  16. package/dist/components/BulkManagementHistoryView/data/apiHook.js.map +1 -1
  17. package/dist/components/GradebookFilters/CourseGradeFilter/hooks.js +3 -1
  18. package/dist/components/GradebookFilters/CourseGradeFilter/hooks.js.map +1 -1
  19. package/dist/components/GradebookFilters/data/api.d.ts +4 -4
  20. package/dist/components/GradebookFilters/data/api.js +6 -6
  21. package/dist/components/GradebookFilters/data/api.js.map +1 -1
  22. package/dist/components/GradebookFilters/data/apiHook.js +2 -2
  23. package/dist/components/GradebookFilters/data/apiHook.js.map +1 -1
  24. package/dist/components/GradebookHeader/index.js +27 -4
  25. package/dist/components/GradebookHeader/index.js.map +1 -1
  26. package/dist/components/GradesView/BulkManagementControls/index.js +3 -1
  27. package/dist/components/GradesView/BulkManagementControls/index.js.map +1 -1
  28. package/dist/components/GradesView/GradesView.scss +0 -3
  29. package/dist/components/GradesView/InterventionsReport/index.js +3 -1
  30. package/dist/components/GradesView/InterventionsReport/index.js.map +1 -1
  31. package/dist/components/GradesView/data/api.d.ts +2 -1
  32. package/dist/components/GradesView/data/api.js +2 -2
  33. package/dist/components/GradesView/data/api.js.map +1 -1
  34. package/dist/components/GradesView/data/apiHook.js +11 -11
  35. package/dist/components/GradesView/data/apiHook.js.map +1 -1
  36. package/dist/components/GradesView/data/hooks.js +10 -4
  37. package/dist/components/GradesView/data/hooks.js.map +1 -1
  38. package/dist/components/WithSidebar/WithSidebar.scss +4 -3
  39. package/dist/containers/GradebookPage/index.js +3 -2
  40. package/dist/containers/GradebookPage/index.js.map +1 -1
  41. package/dist/data/api.d.ts +2 -2
  42. package/dist/data/api.js +4 -4
  43. package/dist/data/api.js.map +1 -1
  44. package/dist/data/apiHook.js +4 -4
  45. package/dist/data/apiHook.js.map +1 -1
  46. package/dist/data/courseIdContext.d.ts +2 -0
  47. package/dist/data/courseIdContext.js +6 -0
  48. package/dist/data/courseIdContext.js.map +1 -0
  49. package/dist/data/gradebookNavigationContext.d.ts +6 -0
  50. package/dist/data/gradebookNavigationContext.js +9 -0
  51. package/dist/data/gradebookNavigationContext.js.map +1 -0
  52. package/dist/data/services/lms/api.js +17 -13
  53. package/dist/data/services/lms/api.js.map +1 -1
  54. package/dist/data/services/lms/urls.d.ts +13 -13
  55. package/dist/data/services/lms/urls.js +15 -15
  56. package/dist/data/services/lms/urls.js.map +1 -1
  57. package/dist/data/services/segment/constants.d.ts +0 -5
  58. package/dist/data/services/segment/constants.js +0 -2
  59. package/dist/data/services/segment/constants.js.map +1 -1
  60. package/dist/data/services/segment/events.d.ts +8 -8
  61. package/dist/data/services/segment/events.js +10 -10
  62. package/dist/data/services/segment/events.js.map +1 -1
  63. package/dist/i18n/utils.d.ts +1 -1
  64. package/dist/index.d.ts +1 -0
  65. package/dist/index.js +1 -0
  66. package/dist/index.js.map +1 -1
  67. package/dist/routes.d.ts +3 -1
  68. package/dist/routes.js +15 -3
  69. package/dist/routes.js.map +1 -1
  70. package/dist/slots.d.ts +3 -0
  71. package/dist/slots.js +22 -0
  72. package/dist/slots.js.map +1 -0
  73. package/package.json +2 -2
@@ -9,12 +9,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { useMemo } from 'react';
11
11
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
12
- import { useParams } from 'react-router-dom';
13
12
  import lms from '../../../data/services/lms';
14
13
  import { sortAlphaAsc } from '../../../data/formatUtils';
15
14
  import { filtersSnapshot } from '../../../data/filtersSnapshot';
16
15
  import { useGradebookUi } from '../../../data/gradebookUiContext';
17
16
  import { useCourseIdWithGate } from '../../../data/apiHook';
17
+ import { useCourseId } from '../../../data/courseIdContext';
18
18
  import { trackGradesDisplayed, trackGradeOverrideSucceeded, trackGradeOverrideFailed, trackUploadOverrideSucceeded, trackUploadOverrideFailed, } from '../../../data/services/segment/events';
19
19
  // Cross-feature invalidation: a successful CSV upload refreshes the bulk-operation
20
20
  // history owned by the BulkManagementHistoryView feature.
@@ -45,10 +45,10 @@ export const useGrades = (courseId, gradesPageEndpoint, { enabled = true } = {})
45
45
  queryFn: () => __awaiter(void 0, void 0, void 0, function* () {
46
46
  const data = gradesPageEndpoint
47
47
  ? yield getGradesPage(gradesPageEndpoint)
48
- : yield getGrades(buildGradebookDataParams());
48
+ : yield getGrades(Object.assign({ courseId }, buildGradebookDataParams()));
49
49
  // Mirrors the legacy `receivedGrades` redux-beacon trigger: one event per
50
50
  // actual fetch, with the filters it ran under and the resulting cursors.
51
- trackGradesDisplayed({
51
+ trackGradesDisplayed(courseId, {
52
52
  assignmentType: filtersSnapshot.assignmentType,
53
53
  cohort: filtersSnapshot.cohort,
54
54
  track: filtersSnapshot.track,
@@ -114,12 +114,12 @@ export const useGradeOverrideHistory = (subsectionId, userId) => (useQuery({
114
114
  */
115
115
  export const useUpdateGrades = () => {
116
116
  const queryClient = useQueryClient();
117
- const { courseId = '' } = useParams();
117
+ const courseId = useCourseId();
118
118
  const { setShowSuccess, setGradesPageEndpoint, modalState } = useGradebookUi();
119
119
  const mutation = useMutation({
120
- mutationFn: (updateData) => lms.api.updateGradebookData(updateData),
120
+ mutationFn: (updateData) => lms.api.updateGradebookData(courseId, updateData),
121
121
  onSuccess: ({ data }, updateData) => {
122
- trackGradeOverrideSucceeded(data);
122
+ trackGradeOverrideSucceeded(courseId, data);
123
123
  setShowSuccess(true);
124
124
  setGradesPageEndpoint(null);
125
125
  queryClient.invalidateQueries({
@@ -132,7 +132,7 @@ export const useUpdateGrades = () => {
132
132
  });
133
133
  },
134
134
  onError: (error) => {
135
- trackGradeOverrideFailed(error);
135
+ trackGradeOverrideFailed(courseId, error);
136
136
  },
137
137
  });
138
138
  return () => {
@@ -159,24 +159,24 @@ export const useUpdateGrades = () => {
159
159
  */
160
160
  export const useSubmitImportGradesButtonData = () => {
161
161
  const queryClient = useQueryClient();
162
- const { courseId = '' } = useParams();
162
+ const courseId = useCourseId();
163
163
  const { setShowImportSuccessToast, resetCsvUpload, markCsvUploadSuccess, setCsvUploadErrors, } = useGradebookUi();
164
164
  const mutation = useMutation({
165
- mutationFn: (formData) => lms.api.uploadGradeCsv(formData),
165
+ mutationFn: (formData) => lms.api.uploadGradeCsv(courseId, formData),
166
166
  onMutate: () => {
167
167
  resetCsvUpload();
168
168
  },
169
169
  onSuccess: () => {
170
170
  markCsvUploadSuccess();
171
171
  setShowImportSuccessToast(true);
172
- trackUploadOverrideSucceeded();
172
+ trackUploadOverrideSucceeded(courseId);
173
173
  queryClient.invalidateQueries({
174
174
  queryKey: bulkOperationHistoryQueryKeys.byCourse(courseId),
175
175
  });
176
176
  },
177
177
  onError: (error) => {
178
178
  var _a, _b;
179
- trackUploadOverrideFailed(error);
179
+ trackUploadOverrideFailed(courseId, error);
180
180
  if ((error === null || error === void 0 ? void 0 : error.status) === 200 && ((_b = (_a = error === null || error === void 0 ? void 0 : error.data) === null || _a === void 0 ? void 0 : _a.error_messages) === null || _b === void 0 ? void 0 : _b.length)) {
181
181
  const { error_messages: errorMessages } = error.data;
182
182
  setCsvUploadErrors(errorMessages);
@@ -1 +1 @@
1
- {"version":3,"file":"apiHook.js","sourceRoot":"","sources":["../../../../src/components/GradesView/data/apiHook.ts"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react';\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { useParams } from 'react-router-dom';\n\nimport lms from '@src/data/services/lms';\nimport { sortAlphaAsc } from '@src/data/formatUtils';\nimport { filtersSnapshot } from '@src/data/filtersSnapshot';\nimport { useGradebookUi } from '@src/data/gradebookUiContext';\nimport { useCourseIdWithGate } from '@src/data/apiHook';\nimport {\n trackGradesDisplayed,\n trackGradeOverrideSucceeded,\n trackGradeOverrideFailed,\n trackUploadOverrideSucceeded,\n trackUploadOverrideFailed,\n} from '@src/data/services/segment/events';\n// Cross-feature invalidation: a successful CSV upload refreshes the bulk-operation\n// history owned by the BulkManagementHistoryView feature.\nimport { bulkOperationHistoryQueryKeys } from '@src/components/BulkManagementHistoryView/data/queryKeys';\n\nimport { getGradeOverrideHistory, getGrades, getGradesPage } from './api';\nimport { gradeOverrideHistoryQueryKeys, gradesQueryKeys } from './queryKeys';\nimport { buildGradesFetchParams } from './utils';\n\n/**\n * Builds the `getGrades` params from the live `filtersSnapshot`. Reads at fetch\n * time (not from the query key) so imperative refetches pick up the latest\n * filter values — the FiltersProvider keeps `filtersSnapshot` in sync.\n */\nconst buildGradebookDataParams = () => buildGradesFetchParams(filtersSnapshot);\n\n/**\n * useGrades()\n * Single-responsibility query for the main gradebook rows. Caller supplies\n * `courseId`, the current pagination cursor (`gradesPageEndpoint` from\n * `GradebookUi`, or `null` for the base/unpaged page), and decides when it may\n * run via `enabled`. The base grades key is a prefix of the paged key, so\n * invalidating `byCourse(courseId)` still refetches any page. The first\n * (unpaged) page reads the current filter params at fetch time (so an\n * imperative `invalidateQueries` on filter apply / search refetches with the\n * latest values); a non-null cursor fetches that opaque prev/next URL —\n * replacing the legacy `fetchPrevNextGrades` thunk.\n */\nexport const useGrades = (\n courseId: string,\n gradesPageEndpoint: string | null,\n { enabled = true }: { enabled?: boolean } = {},\n) => useQuery({\n queryKey: [...gradesQueryKeys.byCourse(courseId), gradesPageEndpoint ?? 'base'],\n queryFn: async () => {\n const data = gradesPageEndpoint\n ? await getGradesPage(gradesPageEndpoint)\n : await getGrades(buildGradebookDataParams());\n // Mirrors the legacy `receivedGrades` redux-beacon trigger: one event per\n // actual fetch, with the filters it ran under and the resulting cursors.\n trackGradesDisplayed({\n assignmentType: filtersSnapshot.assignmentType,\n cohort: filtersSnapshot.cohort,\n track: filtersSnapshot.track,\n prev: data.previous,\n next: data.next,\n });\n return data;\n },\n enabled: !!courseId && enabled,\n});\n\n/** One subsection cell within a learner's grade row. */\nexport interface SubsectionBreakdown {\n label: string;\n category?: string;\n subsection_name?: string;\n module_id?: string;\n score_possible?: number;\n score_earned?: number;\n percent?: number;\n attempted?: boolean;\n [key: string]: unknown;\n}\n\n/** One learner's grade row from the gradebook response. */\nexport interface GradeEntry {\n user_id?: number;\n username?: string;\n external_user_key?: string;\n email?: string;\n percent?: number;\n section_breakdown: SubsectionBreakdown[];\n [key: string]: unknown;\n}\n\n/** Shaped grades read-model derived from the `useGrades` query. */\nexport interface GradesData {\n results: GradeEntry[];\n prevPage: string | null;\n nextPage: string | null;\n totalUsersCount: number;\n filteredUsersCount: number;\n isFetching: boolean;\n isError: boolean;\n isSuccess: boolean;\n}\n\n/**\n * useGradesData()\n * Shapes the `useGrades` query into the read-model the grades table / counts /\n * pagination consume — the React Query equivalent of the Redux `grades` slice\n * fields that `useGradebookData` used to mirror. Results are sorted by username\n * (matching the legacy `fetching.received` handling).\n */\nexport const useGradesData = (): GradesData => {\n const { courseId, enabled } = useCourseIdWithGate();\n const { gradesPageEndpoint } = useGradebookUi();\n const query = useGrades(courseId, gradesPageEndpoint, { enabled });\n const data = query.data as {\n results?: GradeEntry[];\n previous?: string | null;\n next?: string | null;\n total_users_count?: number;\n filtered_users_count?: number;\n } | undefined;\n const results = useMemo(\n () => [...(data?.results ?? [])].sort(sortAlphaAsc),\n [data],\n );\n return {\n results,\n prevPage: data?.previous ?? null,\n nextPage: data?.next ?? null,\n totalUsersCount: data?.total_users_count ?? 0,\n filteredUsersCount: data?.filtered_users_count ?? 0,\n isFetching: query.isFetching,\n isError: query.isError,\n isSuccess: query.isSuccess,\n };\n};\n\n/**\n * useGradeOverrideHistory(subsectionId, userId)\n * Grade-override history for a single learner/subsection, used by the edit modal.\n * Enabled only once both identifiers are present (i.e. the modal has been opened\n * for a specific cell). `retry: false` because a `success: false` business error\n * is not worth retrying.\n */\nexport const useGradeOverrideHistory = (subsectionId?: string, userId?: string | number) => (\n useQuery({\n queryKey: gradeOverrideHistoryQueryKeys.byCell(subsectionId ?? null, userId ?? null),\n queryFn: () => getGradeOverrideHistory(subsectionId as string, userId as string | number),\n enabled: !!subsectionId && userId !== undefined && userId !== null,\n retry: false,\n })\n);\n\n/** Shape of the rejection thrown by `uploadGradeCsv` (an HTTP response, not an Error). */\ninterface CsvUploadError {\n status?: number;\n data?: { error_messages?: string[]; saved?: number; total?: number };\n}\n\n/** One grade-override entry sent to the gradebook bulk-update endpoint. */\ninterface GradeOverrideUpdate {\n grade: {\n comment: string;\n earned_graded_override: string | number;\n };\n usage_id: string | null;\n user_id: number | null;\n}\n\n/**\n * useUpdateGrades()\n * Grade-override save from the edit modal. The HTTP call runs through React Query;\n * success emits the Segment `update.*` event, shows the success banner, resets to\n * the first grades page, and invalidates the grades query (so the table reflects\n * the override) and the edited cell's override history (so reopening the modal\n * shows the new entry).\n *\n * The payload is captured as mutation variables when the trigger is called (same\n * shape as the old `selectors.app.editUpdateData`): the caller closes the modal\n * right after triggering the save, and a pending mutation picks up re-rendered\n * options, so a closure over `modalState` would read the reset (empty) state.\n *\n * Returns a no-arg trigger to match the legacy `useUpdateGrades()` call site.\n */\nexport const useUpdateGrades = () => {\n const queryClient = useQueryClient();\n const { courseId = '' } = useParams();\n const { setShowSuccess, setGradesPageEndpoint, modalState } = useGradebookUi();\n const mutation = useMutation({\n mutationFn: (updateData: GradeOverrideUpdate[]) => lms.api.updateGradebookData(updateData),\n onSuccess: ({ data }, updateData) => {\n trackGradeOverrideSucceeded(data);\n setShowSuccess(true);\n setGradesPageEndpoint(null);\n queryClient.invalidateQueries({\n queryKey: gradesQueryKeys.byCourse(courseId),\n });\n updateData.forEach(({ usage_id: usageId, user_id: userId }) => {\n queryClient.invalidateQueries({\n queryKey: gradeOverrideHistoryQueryKeys.byCell(usageId, userId),\n });\n });\n },\n onError: (error) => {\n trackGradeOverrideFailed(error);\n },\n });\n return () => {\n mutation.mutate([{\n grade: {\n comment: modalState.reasonForChange,\n earned_graded_override: modalState.adjustedGradeValue,\n },\n usage_id: modalState.updateModuleId,\n user_id: modalState.updateUserId,\n }]);\n };\n};\n\n/**\n * useSubmitImportGradesButtonData()\n * Bulk grade-override CSV upload. The upload runs through React Query and, on\n * success, invalidates the bulk-operation-history query so the new upload shows up.\n * CSV progress/results/toast are React UI state (GradebookUi context) and success/\n * failure emit the Segment `uploadOverride.*` events.\n *\n * Returns `(formData) => Promise` whose promise always resolves (errors are handled\n * inside `onError`), matching the legacy thunk so the caller's `.then(clearInput)`\n * still runs on both success and failure.\n */\nexport const useSubmitImportGradesButtonData = () => {\n const queryClient = useQueryClient();\n const { courseId = '' } = useParams();\n const {\n setShowImportSuccessToast,\n resetCsvUpload,\n markCsvUploadSuccess,\n setCsvUploadErrors,\n } = useGradebookUi();\n const mutation = useMutation<unknown, CsvUploadError, FormData>({\n mutationFn: (formData) => lms.api.uploadGradeCsv(formData),\n onMutate: () => {\n resetCsvUpload();\n },\n onSuccess: () => {\n markCsvUploadSuccess();\n setShowImportSuccessToast(true);\n trackUploadOverrideSucceeded();\n queryClient.invalidateQueries({\n queryKey: bulkOperationHistoryQueryKeys.byCourse(courseId),\n });\n },\n onError: (error) => {\n trackUploadOverrideFailed(error);\n if (error?.status === 200 && error?.data?.error_messages?.length) {\n const { error_messages: errorMessages } = error.data;\n setCsvUploadErrors(errorMessages);\n } else {\n setCsvUploadErrors(['Unknown error.']);\n }\n },\n });\n return (formData: FormData) => mutation.mutateAsync(formData).catch(() => {});\n};\n"]}
1
+ {"version":3,"file":"apiHook.js","sourceRoot":"","sources":["../../../../src/components/GradesView/data/apiHook.ts"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react';\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\n\nimport lms from '@src/data/services/lms';\nimport { sortAlphaAsc } from '@src/data/formatUtils';\nimport { filtersSnapshot } from '@src/data/filtersSnapshot';\nimport { useGradebookUi } from '@src/data/gradebookUiContext';\nimport { useCourseIdWithGate } from '@src/data/apiHook';\nimport { useCourseId } from '@src/data/courseIdContext';\nimport {\n trackGradesDisplayed,\n trackGradeOverrideSucceeded,\n trackGradeOverrideFailed,\n trackUploadOverrideSucceeded,\n trackUploadOverrideFailed,\n} from '@src/data/services/segment/events';\n// Cross-feature invalidation: a successful CSV upload refreshes the bulk-operation\n// history owned by the BulkManagementHistoryView feature.\nimport { bulkOperationHistoryQueryKeys } from '@src/components/BulkManagementHistoryView/data/queryKeys';\n\nimport { getGradeOverrideHistory, getGrades, getGradesPage } from './api';\nimport { gradeOverrideHistoryQueryKeys, gradesQueryKeys } from './queryKeys';\nimport { buildGradesFetchParams } from './utils';\n\n/**\n * Builds the `getGrades` params from the live `filtersSnapshot`. Reads at fetch\n * time (not from the query key) so imperative refetches pick up the latest\n * filter values — the FiltersProvider keeps `filtersSnapshot` in sync.\n */\nconst buildGradebookDataParams = () => buildGradesFetchParams(filtersSnapshot);\n\n/**\n * useGrades()\n * Single-responsibility query for the main gradebook rows. Caller supplies\n * `courseId`, the current pagination cursor (`gradesPageEndpoint` from\n * `GradebookUi`, or `null` for the base/unpaged page), and decides when it may\n * run via `enabled`. The base grades key is a prefix of the paged key, so\n * invalidating `byCourse(courseId)` still refetches any page. The first\n * (unpaged) page reads the current filter params at fetch time (so an\n * imperative `invalidateQueries` on filter apply / search refetches with the\n * latest values); a non-null cursor fetches that opaque prev/next URL —\n * replacing the legacy `fetchPrevNextGrades` thunk.\n */\nexport const useGrades = (\n courseId: string,\n gradesPageEndpoint: string | null,\n { enabled = true }: { enabled?: boolean } = {},\n) => useQuery({\n queryKey: [...gradesQueryKeys.byCourse(courseId), gradesPageEndpoint ?? 'base'],\n queryFn: async () => {\n const data = gradesPageEndpoint\n ? await getGradesPage(gradesPageEndpoint)\n : await getGrades({ courseId, ...buildGradebookDataParams() });\n // Mirrors the legacy `receivedGrades` redux-beacon trigger: one event per\n // actual fetch, with the filters it ran under and the resulting cursors.\n trackGradesDisplayed(courseId, {\n assignmentType: filtersSnapshot.assignmentType,\n cohort: filtersSnapshot.cohort,\n track: filtersSnapshot.track,\n prev: data.previous,\n next: data.next,\n });\n return data;\n },\n enabled: !!courseId && enabled,\n});\n\n/** One subsection cell within a learner's grade row. */\nexport interface SubsectionBreakdown {\n label: string;\n category?: string;\n subsection_name?: string;\n module_id?: string;\n score_possible?: number;\n score_earned?: number;\n percent?: number;\n attempted?: boolean;\n [key: string]: unknown;\n}\n\n/** One learner's grade row from the gradebook response. */\nexport interface GradeEntry {\n user_id?: number;\n username?: string;\n external_user_key?: string;\n email?: string;\n percent?: number;\n section_breakdown: SubsectionBreakdown[];\n [key: string]: unknown;\n}\n\n/** Shaped grades read-model derived from the `useGrades` query. */\nexport interface GradesData {\n results: GradeEntry[];\n prevPage: string | null;\n nextPage: string | null;\n totalUsersCount: number;\n filteredUsersCount: number;\n isFetching: boolean;\n isError: boolean;\n isSuccess: boolean;\n}\n\n/**\n * useGradesData()\n * Shapes the `useGrades` query into the read-model the grades table / counts /\n * pagination consume — the React Query equivalent of the Redux `grades` slice\n * fields that `useGradebookData` used to mirror. Results are sorted by username\n * (matching the legacy `fetching.received` handling).\n */\nexport const useGradesData = (): GradesData => {\n const { courseId, enabled } = useCourseIdWithGate();\n const { gradesPageEndpoint } = useGradebookUi();\n const query = useGrades(courseId, gradesPageEndpoint, { enabled });\n const data = query.data as {\n results?: GradeEntry[];\n previous?: string | null;\n next?: string | null;\n total_users_count?: number;\n filtered_users_count?: number;\n } | undefined;\n const results = useMemo(\n () => [...(data?.results ?? [])].sort(sortAlphaAsc),\n [data],\n );\n return {\n results,\n prevPage: data?.previous ?? null,\n nextPage: data?.next ?? null,\n totalUsersCount: data?.total_users_count ?? 0,\n filteredUsersCount: data?.filtered_users_count ?? 0,\n isFetching: query.isFetching,\n isError: query.isError,\n isSuccess: query.isSuccess,\n };\n};\n\n/**\n * useGradeOverrideHistory(subsectionId, userId)\n * Grade-override history for a single learner/subsection, used by the edit modal.\n * Enabled only once both identifiers are present (i.e. the modal has been opened\n * for a specific cell). `retry: false` because a `success: false` business error\n * is not worth retrying.\n */\nexport const useGradeOverrideHistory = (subsectionId?: string, userId?: string | number) => (\n useQuery({\n queryKey: gradeOverrideHistoryQueryKeys.byCell(subsectionId ?? null, userId ?? null),\n queryFn: () => getGradeOverrideHistory(subsectionId as string, userId as string | number),\n enabled: !!subsectionId && userId !== undefined && userId !== null,\n retry: false,\n })\n);\n\n/** Shape of the rejection thrown by `uploadGradeCsv` (an HTTP response, not an Error). */\ninterface CsvUploadError {\n status?: number;\n data?: { error_messages?: string[]; saved?: number; total?: number };\n}\n\n/** One grade-override entry sent to the gradebook bulk-update endpoint. */\ninterface GradeOverrideUpdate {\n grade: {\n comment: string;\n earned_graded_override: string | number;\n };\n usage_id: string | null;\n user_id: number | null;\n}\n\n/**\n * useUpdateGrades()\n * Grade-override save from the edit modal. The HTTP call runs through React Query;\n * success emits the Segment `update.*` event, shows the success banner, resets to\n * the first grades page, and invalidates the grades query (so the table reflects\n * the override) and the edited cell's override history (so reopening the modal\n * shows the new entry).\n *\n * The payload is captured as mutation variables when the trigger is called (same\n * shape as the old `selectors.app.editUpdateData`): the caller closes the modal\n * right after triggering the save, and a pending mutation picks up re-rendered\n * options, so a closure over `modalState` would read the reset (empty) state.\n *\n * Returns a no-arg trigger to match the legacy `useUpdateGrades()` call site.\n */\nexport const useUpdateGrades = () => {\n const queryClient = useQueryClient();\n const courseId = useCourseId();\n const { setShowSuccess, setGradesPageEndpoint, modalState } = useGradebookUi();\n const mutation = useMutation({\n mutationFn: (updateData: GradeOverrideUpdate[]) => lms.api.updateGradebookData(courseId, updateData),\n onSuccess: ({ data }, updateData) => {\n trackGradeOverrideSucceeded(courseId, data);\n setShowSuccess(true);\n setGradesPageEndpoint(null);\n queryClient.invalidateQueries({\n queryKey: gradesQueryKeys.byCourse(courseId),\n });\n updateData.forEach(({ usage_id: usageId, user_id: userId }) => {\n queryClient.invalidateQueries({\n queryKey: gradeOverrideHistoryQueryKeys.byCell(usageId, userId),\n });\n });\n },\n onError: (error) => {\n trackGradeOverrideFailed(courseId, error);\n },\n });\n return () => {\n mutation.mutate([{\n grade: {\n comment: modalState.reasonForChange,\n earned_graded_override: modalState.adjustedGradeValue,\n },\n usage_id: modalState.updateModuleId,\n user_id: modalState.updateUserId,\n }]);\n };\n};\n\n/**\n * useSubmitImportGradesButtonData()\n * Bulk grade-override CSV upload. The upload runs through React Query and, on\n * success, invalidates the bulk-operation-history query so the new upload shows up.\n * CSV progress/results/toast are React UI state (GradebookUi context) and success/\n * failure emit the Segment `uploadOverride.*` events.\n *\n * Returns `(formData) => Promise` whose promise always resolves (errors are handled\n * inside `onError`), matching the legacy thunk so the caller's `.then(clearInput)`\n * still runs on both success and failure.\n */\nexport const useSubmitImportGradesButtonData = () => {\n const queryClient = useQueryClient();\n const courseId = useCourseId();\n const {\n setShowImportSuccessToast,\n resetCsvUpload,\n markCsvUploadSuccess,\n setCsvUploadErrors,\n } = useGradebookUi();\n const mutation = useMutation<unknown, CsvUploadError, FormData>({\n mutationFn: (formData) => lms.api.uploadGradeCsv(courseId, formData),\n onMutate: () => {\n resetCsvUpload();\n },\n onSuccess: () => {\n markCsvUploadSuccess();\n setShowImportSuccessToast(true);\n trackUploadOverrideSucceeded(courseId);\n queryClient.invalidateQueries({\n queryKey: bulkOperationHistoryQueryKeys.byCourse(courseId),\n });\n },\n onError: (error) => {\n trackUploadOverrideFailed(courseId, error);\n if (error?.status === 200 && error?.data?.error_messages?.length) {\n const { error_messages: errorMessages } = error.data;\n setCsvUploadErrors(errorMessages);\n } else {\n setCsvUploadErrors(['Unknown error.']);\n }\n },\n });\n return (formData: FormData) => mutation.mutateAsync(formData).catch(() => {});\n};\n"]}
@@ -11,12 +11,12 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  };
12
12
  import { useMemo } from 'react';
13
13
  import { useIsMutating, useQueryClient } from '@tanstack/react-query';
14
- import { useParams } from 'react-router-dom';
15
14
  import lms from '../../../data/services/lms';
16
15
  import initialFilters, { filterConfig, filters as filterNames } from '../../../data/constants/filters';
17
16
  import { useFilters } from '../../../data/filtersContext';
18
17
  import { useGradebookUi } from '../../../data/gradebookUiContext';
19
18
  import { useCanViewGradebook } from '../../../data/apiHook';
19
+ import { useCourseId } from '../../../data/courseIdContext';
20
20
  import { useSelectedCohortEntry, useSelectedTrackEntry, } from '../../GradebookFilters/data/hooks';
21
21
  import { useGradesData, useGradeOverrideHistory } from './apiHook';
22
22
  import { gradesQueryKeys } from './queryKeys';
@@ -125,8 +125,14 @@ const useLmsApiServiceArgs = () => {
125
125
  excludedCourseRoles: includeCourseRoleMembers ? '' : 'all',
126
126
  };
127
127
  };
128
- export const useGradeExportUrl = () => lms.urls.gradeCsvUrl(useLmsApiServiceArgs());
129
- export const useInterventionExportUrl = () => lms.urls.interventionExportCsvUrl(useLmsApiServiceArgs());
128
+ export const useGradeExportUrl = () => {
129
+ const courseId = useCourseId();
130
+ return lms.urls.gradeCsvUrl(courseId, useLmsApiServiceArgs());
131
+ };
132
+ export const useInterventionExportUrl = () => {
133
+ const courseId = useCourseId();
134
+ return lms.urls.interventionExportCsvUrl(courseId, useLmsApiServiceArgs());
135
+ };
130
136
  /**
131
137
  * useFilterBadgeConfig(filterName)
132
138
  * The badge config (value + isDefault) for a given filter, built from the
@@ -192,7 +198,7 @@ export const useEditModalPossibleGrade = () => {
192
198
  // refetch with the current filters.
193
199
  const useInvalidateGrades = () => {
194
200
  const queryClient = useQueryClient();
195
- const { courseId = '' } = useParams();
201
+ const courseId = useCourseId();
196
202
  return () => queryClient.invalidateQueries({ queryKey: gradesQueryKeys.byCourse(courseId) });
197
203
  };
198
204
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../../../src/components/GradesView/data/hooks.js"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react';\nimport { useIsMutating, useQueryClient } from '@tanstack/react-query';\nimport { useParams } from 'react-router-dom';\n\nimport lms from '@src/data/services/lms';\nimport initialFilters, { filterConfig, filters as filterNames } from '@src/data/constants/filters';\nimport { useFilters } from '@src/data/filtersContext';\nimport { useGradebookUi } from '@src/data/gradebookUiContext';\nimport { useCanViewGradebook } from '@src/data/apiHook';\nimport {\n useSelectedCohortEntry,\n useSelectedTrackEntry,\n} from '@src/components/GradebookFilters/data/hooks';\n\nimport { useGradesData, useGradeOverrideHistory } from './apiHook';\nimport { gradesQueryKeys } from './queryKeys';\nimport {\n chooseRelevantAssignmentData,\n formatMaxAssignmentGrade,\n formatMaxCourseGrade,\n formatMinAssignmentGrade,\n formatMinCourseGrade,\n getAssignmentsFromResultsSubstate,\n headingMapper,\n isDefault,\n relevantAssignmentDataFromResults,\n} from './utils';\n\n// Stable empty reference so read hooks don't return a fresh array each render.\nconst EMPTY_ARRAY = [];\n\n/**\n * useGradesHeadings()\n * The gradebook table headings for the current assignment-type / assignment\n * filters, derived from the grades query's section breakdown.\n */\nexport const useGradesHeadings = () => {\n const { results } = useGradesData();\n const { assignmentType, assignment: assignmentId } = useFilters();\n return useMemo(() => {\n const selectedAssignmentLabel = relevantAssignmentDataFromResults(results, assignmentId)?.label;\n const sectionBreakdown = getAssignmentsFromResultsSubstate(results);\n return headingMapper(\n assignmentType || 'All',\n selectedAssignmentLabel || 'All',\n )(sectionBreakdown);\n }, [results, assignmentType, assignmentId]);\n};\n\n/**\n * useSelectableAssignmentLabels()\n * The assignment options for the assignment dropdown, derived from the grades\n * results and filtered by the current assignment-type.\n */\nexport const useSelectableAssignmentLabels = () => {\n const { results } = useGradesData();\n const { assignmentType } = useFilters();\n return useMemo(() => {\n const all = getAssignmentsFromResultsSubstate(results)\n .map(chooseRelevantAssignmentData);\n return (assignmentType && assignmentType !== 'All')\n ? all.filter((assignment) => assignment.type === assignmentType)\n : all;\n }, [results, assignmentType]);\n};\n\n/**\n * useSelectedAssignmentData()\n * The currently-selected assignment's data (label/type/id, from the grades\n * results), or undefined when no assignment is selected.\n */\nexport const useSelectedAssignmentData = () => {\n const { results } = useGradesData();\n const { assignment: assignmentId } = useFilters();\n return useMemo(\n () => relevantAssignmentDataFromResults(results, assignmentId),\n [results, assignmentId],\n );\n};\n\n/**\n * useSelectedAssignmentLabel()\n * The label of the currently-selected assignment (from the grades results).\n */\nexport const useSelectedAssignmentLabel = () => useSelectedAssignmentData()?.label;\n\n/**\n * useShouldShowSpinner()\n * The busy indicator: the roles gate combined with the grades query fetching or\n * any in-flight mutation (grade override save / CSV upload).\n */\nexport const useShouldShowSpinner = () => {\n const canViewGradebook = useCanViewGradebook();\n const { isFetching } = useGradesData();\n const mutatingCount = useIsMutating();\n return canViewGradebook && (isFetching || mutatingCount > 0);\n};\n\n/**\n * useAllGrades()\n * The (sorted) grades rows for the table.\n */\nexport const useAllGrades = () => useGradesData().results;\n\n/**\n * useUserCounts()\n * Filtered/total user counts from the grades query.\n */\nexport const useUserCounts = () => {\n const { filteredUsersCount, totalUsersCount } = useGradesData();\n return { filteredUsersCount, totalUsersCount };\n};\n\n/**\n * useGradeData()\n * The pagination cursors + counts (from the grades query) plus the current\n * `gradeFormat` (from the GradebookUi context).\n */\nexport const useGradeData = () => {\n const {\n nextPage, prevPage, totalUsersCount, filteredUsersCount,\n } = useGradesData();\n const { gradeFormat } = useGradebookUi();\n return {\n nextPage, prevPage, totalUsersCount, filteredUsersCount, gradeFormat,\n };\n};\n\n// Export URL args, built from the FiltersProvider (applied grade limits + the\n// immediate filters) and the selected cohort name. Reuses the pure\n// `format*` helpers so the CSV/intervention URL formatting is unchanged.\nconst useLmsApiServiceArgs = () => {\n const {\n assignment, assignmentType, track, includeCourseRoleMembers,\n appliedAssignmentGradeMin, appliedAssignmentGradeMax,\n appliedCourseGradeMin, appliedCourseGradeMax,\n } = useFilters();\n const selectedCohort = useSelectedCohortEntry();\n const assignmentId = assignment || undefined;\n const opts = { assignmentId };\n return {\n cohort: selectedCohort ? selectedCohort.name : undefined,\n track,\n assignment: assignmentId,\n assignmentType,\n assignmentGradeMin: formatMinAssignmentGrade(appliedAssignmentGradeMin, opts),\n assignmentGradeMax: formatMaxAssignmentGrade(appliedAssignmentGradeMax, opts),\n courseGradeMin: formatMinCourseGrade(appliedCourseGradeMin),\n courseGradeMax: formatMaxCourseGrade(appliedCourseGradeMax),\n excludedCourseRoles: includeCourseRoleMembers ? '' : 'all',\n };\n};\n\nexport const useGradeExportUrl = () => lms.urls.gradeCsvUrl(useLmsApiServiceArgs());\nexport const useInterventionExportUrl = () => lms.urls.interventionExportCsvUrl(useLmsApiServiceArgs());\n\n/**\n * useFilterBadgeConfig(filterName)\n * The badge config (value + isDefault) for a given filter, built from the\n * FiltersProvider (immediate filters + applied grade limits + query-derived\n * assignment label) and the selected cohort/track names. Reuses `filterConfig` +\n * the pure `selectors.filters.isDefault` so formatting is unchanged.\n */\nexport const useFilterBadgeConfig = (filterName) => {\n const selectedCohort = useSelectedCohortEntry();\n const selectedTrack = useSelectedTrackEntry();\n const selectedAssignmentLabel = useSelectedAssignmentLabel();\n const {\n assignmentType, includeCourseRoleMembers,\n appliedAssignmentGradeMin, appliedAssignmentGradeMax,\n appliedCourseGradeMin, appliedCourseGradeMax,\n } = useFilters();\n\n if (filterName === filterNames.cohort || filterName === filterNames.track) {\n const entry = filterName === filterNames.cohort ? selectedCohort : selectedTrack;\n const value = entry ? entry.name : '';\n return {\n ...filterConfig[filterName],\n value,\n isDefault: value === initialFilters[filterName],\n };\n }\n\n const { filterOrder, ...config } = filterConfig[filterName];\n\n if (filterOrder) {\n const [min, max] = filterName === filterNames.assignmentGrade\n ? [appliedAssignmentGradeMin, appliedAssignmentGradeMax]\n : [appliedCourseGradeMin, appliedCourseGradeMax];\n return {\n ...config,\n value: `${min} - ${max}`,\n isDefault: isDefault(filterOrder[0], min) && isDefault(filterOrder[1], max),\n };\n }\n\n const singleValues = {\n [filterNames.assignmentType]: assignmentType,\n [filterNames.assignment]: selectedAssignmentLabel || '',\n [filterNames.includeCourseRoleMembers]: includeCourseRoleMembers,\n };\n const value = singleValues[filterName];\n return { ...config, value, isDefault: isDefault(filterName, value) };\n};\n\n/**\n * useGradeOverrideData()\n * The edit-modal override read-model, sourced from the override-history query\n * gated on the modal's subsection/user ids (from the GradebookUi context).\n */\nexport const useGradeOverrideData = () => {\n const { modalState } = useGradebookUi();\n const query = useGradeOverrideHistory(modalState.updateModuleId, modalState.updateUserId);\n const { data } = query;\n return {\n gradeOverrideHistoryResults: data?.overrideHistory ?? EMPTY_ARRAY,\n gradeOverrideCurrentEarnedGradedOverride: data?.currentEarnedGradedOverride ?? null,\n gradeOriginalEarnedGraded: data?.originalGradeEarnedGraded ?? null,\n gradeOriginalPossibleGraded: data?.originalGradePossibleGraded ?? null,\n gradeOverrideHistoryError: query.isError ? (query.error?.message || '') : '',\n hasOverrideErrors: query.isError,\n };\n};\n\n/**\n * useEditModalPossibleGrade()\n * The edit-modal \"possible\" grade: the context `adjustedGradePossible` or, as a\n * fallback, the override query's original possible-graded value.\n */\nexport const useEditModalPossibleGrade = () => {\n const { modalState } = useGradebookUi();\n const { gradeOriginalPossibleGraded } = useGradeOverrideData();\n return modalState.adjustedGradePossible || gradeOriginalPossibleGraded;\n};\n\n// Invalidate the main grades query, causing the mounted `useGrades` observer to\n// refetch with the current filters.\nconst useInvalidateGrades = () => {\n const queryClient = useQueryClient();\n const { courseId = '' } = useParams();\n return () => queryClient.invalidateQueries({ queryKey: gradesQueryKeys.byCourse(courseId) });\n};\n\n/**\n * useRefetchGrades()\n * Refetches the grades query from the first (unpaged) page with the latest\n * filters. Always invalidates (the base-page cache entry may still be fresh, and\n * a fresh entry is served as-is when the key flips back to it); when on a\n * prev/next page, also clears the pagination cursor.\n */\nexport const useRefetchGrades = () => {\n const invalidateGrades = useInvalidateGrades();\n const { gradesPageEndpoint, setGradesPageEndpoint } = useGradebookUi();\n return () => {\n if (gradesPageEndpoint !== null) {\n setGradesPageEndpoint(null);\n }\n invalidateGrades();\n };\n};\n\n/**\n * useFetchGradesIfAssignmentGradeFiltersSet()\n * Refetches from the start only when a non-default applied assignment-grade limit\n * is set (mirrors the legacy `areAssignmentGradeFiltersSet` gate).\n */\nexport const useFetchGradesIfAssignmentGradeFiltersSet = () => {\n const refetchFromStart = useRefetchGrades();\n const { appliedAssignmentGradeMin, appliedAssignmentGradeMax } = useFilters();\n return () => {\n const isSet = appliedAssignmentGradeMin !== initialFilters.assignmentGradeMin\n || appliedAssignmentGradeMax !== initialFilters.assignmentGradeMax;\n if (isSet) {\n refetchFromStart();\n }\n };\n};\n\n/**\n * useFetchPrevNextGrades()\n * Pagination trigger: sets the page cursor in the GradebookUi context (the\n * `useGrades` query keys off it and fetches that opaque prev/next URL).\n */\nexport const useFetchPrevNextGrades = () => {\n const { setGradesPageEndpoint } = useGradebookUi();\n return (endpoint) => {\n setGradesPageEndpoint(endpoint);\n };\n};\n"]}
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../../../src/components/GradesView/data/hooks.js"],"names":[],"mappings":"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","sourcesContent":["import { useMemo } from 'react';\nimport { useIsMutating, useQueryClient } from '@tanstack/react-query';\n\nimport lms from '@src/data/services/lms';\nimport initialFilters, { filterConfig, filters as filterNames } from '@src/data/constants/filters';\nimport { useFilters } from '@src/data/filtersContext';\nimport { useGradebookUi } from '@src/data/gradebookUiContext';\nimport { useCanViewGradebook } from '@src/data/apiHook';\nimport { useCourseId } from '@src/data/courseIdContext';\nimport {\n useSelectedCohortEntry,\n useSelectedTrackEntry,\n} from '@src/components/GradebookFilters/data/hooks';\n\nimport { useGradesData, useGradeOverrideHistory } from './apiHook';\nimport { gradesQueryKeys } from './queryKeys';\nimport {\n chooseRelevantAssignmentData,\n formatMaxAssignmentGrade,\n formatMaxCourseGrade,\n formatMinAssignmentGrade,\n formatMinCourseGrade,\n getAssignmentsFromResultsSubstate,\n headingMapper,\n isDefault,\n relevantAssignmentDataFromResults,\n} from './utils';\n\n// Stable empty reference so read hooks don't return a fresh array each render.\nconst EMPTY_ARRAY = [];\n\n/**\n * useGradesHeadings()\n * The gradebook table headings for the current assignment-type / assignment\n * filters, derived from the grades query's section breakdown.\n */\nexport const useGradesHeadings = () => {\n const { results } = useGradesData();\n const { assignmentType, assignment: assignmentId } = useFilters();\n return useMemo(() => {\n const selectedAssignmentLabel = relevantAssignmentDataFromResults(results, assignmentId)?.label;\n const sectionBreakdown = getAssignmentsFromResultsSubstate(results);\n return headingMapper(\n assignmentType || 'All',\n selectedAssignmentLabel || 'All',\n )(sectionBreakdown);\n }, [results, assignmentType, assignmentId]);\n};\n\n/**\n * useSelectableAssignmentLabels()\n * The assignment options for the assignment dropdown, derived from the grades\n * results and filtered by the current assignment-type.\n */\nexport const useSelectableAssignmentLabels = () => {\n const { results } = useGradesData();\n const { assignmentType } = useFilters();\n return useMemo(() => {\n const all = getAssignmentsFromResultsSubstate(results)\n .map(chooseRelevantAssignmentData);\n return (assignmentType && assignmentType !== 'All')\n ? all.filter((assignment) => assignment.type === assignmentType)\n : all;\n }, [results, assignmentType]);\n};\n\n/**\n * useSelectedAssignmentData()\n * The currently-selected assignment's data (label/type/id, from the grades\n * results), or undefined when no assignment is selected.\n */\nexport const useSelectedAssignmentData = () => {\n const { results } = useGradesData();\n const { assignment: assignmentId } = useFilters();\n return useMemo(\n () => relevantAssignmentDataFromResults(results, assignmentId),\n [results, assignmentId],\n );\n};\n\n/**\n * useSelectedAssignmentLabel()\n * The label of the currently-selected assignment (from the grades results).\n */\nexport const useSelectedAssignmentLabel = () => useSelectedAssignmentData()?.label;\n\n/**\n * useShouldShowSpinner()\n * The busy indicator: the roles gate combined with the grades query fetching or\n * any in-flight mutation (grade override save / CSV upload).\n */\nexport const useShouldShowSpinner = () => {\n const canViewGradebook = useCanViewGradebook();\n const { isFetching } = useGradesData();\n const mutatingCount = useIsMutating();\n return canViewGradebook && (isFetching || mutatingCount > 0);\n};\n\n/**\n * useAllGrades()\n * The (sorted) grades rows for the table.\n */\nexport const useAllGrades = () => useGradesData().results;\n\n/**\n * useUserCounts()\n * Filtered/total user counts from the grades query.\n */\nexport const useUserCounts = () => {\n const { filteredUsersCount, totalUsersCount } = useGradesData();\n return { filteredUsersCount, totalUsersCount };\n};\n\n/**\n * useGradeData()\n * The pagination cursors + counts (from the grades query) plus the current\n * `gradeFormat` (from the GradebookUi context).\n */\nexport const useGradeData = () => {\n const {\n nextPage, prevPage, totalUsersCount, filteredUsersCount,\n } = useGradesData();\n const { gradeFormat } = useGradebookUi();\n return {\n nextPage, prevPage, totalUsersCount, filteredUsersCount, gradeFormat,\n };\n};\n\n// Export URL args, built from the FiltersProvider (applied grade limits + the\n// immediate filters) and the selected cohort name. Reuses the pure\n// `format*` helpers so the CSV/intervention URL formatting is unchanged.\nconst useLmsApiServiceArgs = () => {\n const {\n assignment, assignmentType, track, includeCourseRoleMembers,\n appliedAssignmentGradeMin, appliedAssignmentGradeMax,\n appliedCourseGradeMin, appliedCourseGradeMax,\n } = useFilters();\n const selectedCohort = useSelectedCohortEntry();\n const assignmentId = assignment || undefined;\n const opts = { assignmentId };\n return {\n cohort: selectedCohort ? selectedCohort.name : undefined,\n track,\n assignment: assignmentId,\n assignmentType,\n assignmentGradeMin: formatMinAssignmentGrade(appliedAssignmentGradeMin, opts),\n assignmentGradeMax: formatMaxAssignmentGrade(appliedAssignmentGradeMax, opts),\n courseGradeMin: formatMinCourseGrade(appliedCourseGradeMin),\n courseGradeMax: formatMaxCourseGrade(appliedCourseGradeMax),\n excludedCourseRoles: includeCourseRoleMembers ? '' : 'all',\n };\n};\n\nexport const useGradeExportUrl = () => {\n const courseId = useCourseId();\n return lms.urls.gradeCsvUrl(courseId, useLmsApiServiceArgs());\n};\nexport const useInterventionExportUrl = () => {\n const courseId = useCourseId();\n return lms.urls.interventionExportCsvUrl(courseId, useLmsApiServiceArgs());\n};\n\n/**\n * useFilterBadgeConfig(filterName)\n * The badge config (value + isDefault) for a given filter, built from the\n * FiltersProvider (immediate filters + applied grade limits + query-derived\n * assignment label) and the selected cohort/track names. Reuses `filterConfig` +\n * the pure `selectors.filters.isDefault` so formatting is unchanged.\n */\nexport const useFilterBadgeConfig = (filterName) => {\n const selectedCohort = useSelectedCohortEntry();\n const selectedTrack = useSelectedTrackEntry();\n const selectedAssignmentLabel = useSelectedAssignmentLabel();\n const {\n assignmentType, includeCourseRoleMembers,\n appliedAssignmentGradeMin, appliedAssignmentGradeMax,\n appliedCourseGradeMin, appliedCourseGradeMax,\n } = useFilters();\n\n if (filterName === filterNames.cohort || filterName === filterNames.track) {\n const entry = filterName === filterNames.cohort ? selectedCohort : selectedTrack;\n const value = entry ? entry.name : '';\n return {\n ...filterConfig[filterName],\n value,\n isDefault: value === initialFilters[filterName],\n };\n }\n\n const { filterOrder, ...config } = filterConfig[filterName];\n\n if (filterOrder) {\n const [min, max] = filterName === filterNames.assignmentGrade\n ? [appliedAssignmentGradeMin, appliedAssignmentGradeMax]\n : [appliedCourseGradeMin, appliedCourseGradeMax];\n return {\n ...config,\n value: `${min} - ${max}`,\n isDefault: isDefault(filterOrder[0], min) && isDefault(filterOrder[1], max),\n };\n }\n\n const singleValues = {\n [filterNames.assignmentType]: assignmentType,\n [filterNames.assignment]: selectedAssignmentLabel || '',\n [filterNames.includeCourseRoleMembers]: includeCourseRoleMembers,\n };\n const value = singleValues[filterName];\n return { ...config, value, isDefault: isDefault(filterName, value) };\n};\n\n/**\n * useGradeOverrideData()\n * The edit-modal override read-model, sourced from the override-history query\n * gated on the modal's subsection/user ids (from the GradebookUi context).\n */\nexport const useGradeOverrideData = () => {\n const { modalState } = useGradebookUi();\n const query = useGradeOverrideHistory(modalState.updateModuleId, modalState.updateUserId);\n const { data } = query;\n return {\n gradeOverrideHistoryResults: data?.overrideHistory ?? EMPTY_ARRAY,\n gradeOverrideCurrentEarnedGradedOverride: data?.currentEarnedGradedOverride ?? null,\n gradeOriginalEarnedGraded: data?.originalGradeEarnedGraded ?? null,\n gradeOriginalPossibleGraded: data?.originalGradePossibleGraded ?? null,\n gradeOverrideHistoryError: query.isError ? (query.error?.message || '') : '',\n hasOverrideErrors: query.isError,\n };\n};\n\n/**\n * useEditModalPossibleGrade()\n * The edit-modal \"possible\" grade: the context `adjustedGradePossible` or, as a\n * fallback, the override query's original possible-graded value.\n */\nexport const useEditModalPossibleGrade = () => {\n const { modalState } = useGradebookUi();\n const { gradeOriginalPossibleGraded } = useGradeOverrideData();\n return modalState.adjustedGradePossible || gradeOriginalPossibleGraded;\n};\n\n// Invalidate the main grades query, causing the mounted `useGrades` observer to\n// refetch with the current filters.\nconst useInvalidateGrades = () => {\n const queryClient = useQueryClient();\n const courseId = useCourseId();\n return () => queryClient.invalidateQueries({ queryKey: gradesQueryKeys.byCourse(courseId) });\n};\n\n/**\n * useRefetchGrades()\n * Refetches the grades query from the first (unpaged) page with the latest\n * filters. Always invalidates (the base-page cache entry may still be fresh, and\n * a fresh entry is served as-is when the key flips back to it); when on a\n * prev/next page, also clears the pagination cursor.\n */\nexport const useRefetchGrades = () => {\n const invalidateGrades = useInvalidateGrades();\n const { gradesPageEndpoint, setGradesPageEndpoint } = useGradebookUi();\n return () => {\n if (gradesPageEndpoint !== null) {\n setGradesPageEndpoint(null);\n }\n invalidateGrades();\n };\n};\n\n/**\n * useFetchGradesIfAssignmentGradeFiltersSet()\n * Refetches from the start only when a non-default applied assignment-grade limit\n * is set (mirrors the legacy `areAssignmentGradeFiltersSet` gate).\n */\nexport const useFetchGradesIfAssignmentGradeFiltersSet = () => {\n const refetchFromStart = useRefetchGrades();\n const { appliedAssignmentGradeMin, appliedAssignmentGradeMax } = useFilters();\n return () => {\n const isSet = appliedAssignmentGradeMin !== initialFilters.assignmentGradeMin\n || appliedAssignmentGradeMax !== initialFilters.assignmentGradeMax;\n if (isSet) {\n refetchFromStart();\n }\n };\n};\n\n/**\n * useFetchPrevNextGrades()\n * Pagination trigger: sets the page cursor in the GradebookUi context (the\n * `useGrades` query keys off it and fetches that opaque prev/next URL).\n */\nexport const useFetchPrevNextGrades = () => {\n const { setGradesPageEndpoint } = useGradebookUi();\n return (endpoint) => {\n setGradesPageEndpoint(endpoint);\n };\n};\n"]}
@@ -4,12 +4,13 @@ $sidebar-width: 350px;
4
4
  overflow-x: auto;
5
5
  transition: margin 300ms cubic-bezier(0.4,0,0.2,1);
6
6
  margin-left: 0;
7
+ // Fill the container; min-width lets the flex item shrink so
8
+ // overflow-x can kick in instead of widening the page.
9
+ flex-grow: 1;
10
+ min-width: 0;
7
11
  .sidebar.open + & {
8
12
  margin-left: $sidebar-width;
9
13
  }
10
- &.opening {
11
- width: calc(100vw - #{$sidebar-width});
12
- }
13
14
  }
14
15
 
15
16
  .sidebar-header {
@@ -1,6 +1,7 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import queryString from 'query-string';
3
3
  import { useNavigate, useLocation } from 'react-router-dom';
4
+ import { Container } from '@openedx/paragon';
4
5
  import { useGradebookUi } from '../../data/gradebookUiContext';
5
6
  import { views } from '../../data/constants/app';
6
7
  import WithSidebar from '../../components/WithSidebar';
@@ -32,7 +33,7 @@ export const GradebookPage = () => {
32
33
  });
33
34
  navigate({ pathname, search: `?${queryString.stringify(parsed)}` });
34
35
  };
35
- return (_jsxs(_Fragment, { children: [_jsx(GradebookDataLoader, {}), _jsx(WithSidebar, { sidebar: _jsx(GradebookFilters, { updateQueryParams: updateQueryParams }), children: _jsxs("div", { className: "px-3 gradebook-content", children: [_jsx(GradebookHeader, {}), (activeView === views.bulkManagementHistory
36
+ return (_jsxs(Container, { size: "xl", fluid: true, children: [_jsx(GradebookDataLoader, {}), _jsx(WithSidebar, { sidebar: _jsx(GradebookFilters, { updateQueryParams: updateQueryParams }), children: _jsxs("div", { className: "gradebook-content", children: [_jsx(GradebookHeader, {}), (activeView === views.bulkManagementHistory
36
37
  ? _jsx(BulkManagementHistoryView, {})
37
38
  : _jsx(GradesView, { updateQueryParams: updateQueryParams }))] }) })] }));
38
39
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/containers/GradebookPage/index.jsx"],"names":[],"mappings":";AAAA,OAAO,WAAW,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE5D,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAEhD,OAAO,WAAW,MAAM,6BAA6B,CAAC;AACtD,OAAO,eAAe,MAAM,iCAAiC,CAAC;AAC9D,OAAO,UAAU,MAAM,4BAA4B,CAAC;AACpD,OAAO,gBAAgB,MAAM,kCAAkC,CAAC;AAChE,OAAO,yBAAyB,MAAM,2CAA2C,CAAC;AAElF,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AAExD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,EAAE;IAChC,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,MAAM,EAAE,UAAU,EAAE,GAAG,cAAc,EAAE,CAAC;IAExC,MAAM,iBAAiB,GAAG,CAAC,WAAW,EAAE,EAAE;QACxC,MAAM,EAAE,QAAQ,EAAE,GAAG,QAAQ,CAAC;QAC9B,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAClD,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;YACvC,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrB,MAAM,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;YACjC,CAAC;iBAAM,CAAC;gBACN,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;YACrB,CAAC;QACH,CAAC,CAAC,CAAC;QACH,QAAQ,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,WAAW,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;IACtE,CAAC,CAAC;IAEF,OAAO,CACL,8BACE,KAAC,mBAAmB,KAAG,EACvB,KAAC,WAAW,IACV,OAAO,EAAE,KAAC,gBAAgB,IAAC,iBAAiB,EAAE,iBAAiB,GAAI,YAEnE,eAAK,SAAS,EAAC,wBAAwB,aACrC,KAAC,eAAe,KAAG,EAClB,CAAC,UAAU,KAAK,KAAK,CAAC,qBAAqB;4BAC1C,CAAC,CAAC,KAAC,yBAAyB,KAAG;4BAC/B,CAAC,CAAC,KAAC,UAAU,IAAC,iBAAiB,EAAE,iBAAiB,GAAI,CACvD,IACG,GACM,IACb,CACJ,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,aAAa,CAAC","sourcesContent":["import queryString from 'query-string';\nimport { useNavigate, useLocation } from 'react-router-dom';\n\nimport { useGradebookUi } from '@src/data/gradebookUiContext';\nimport { views } from '@src/data/constants/app';\n\nimport WithSidebar from '@src/components/WithSidebar';\nimport GradebookHeader from '@src/components/GradebookHeader';\nimport GradesView from '@src/components/GradesView';\nimport GradebookFilters from '@src/components/GradebookFilters';\nimport BulkManagementHistoryView from '@src/components/BulkManagementHistoryView';\n\nimport GradebookDataLoader from './GradebookDataLoader';\n\n/**\n * <GradebookPage />\n * Top-level view for the Gradebook MFE.\n * Organizes a header and a pair of views (Grades and BulkManagement) with a toggle-able\n * filter sidebar.\n */\nexport const GradebookPage = () => {\n const navigate = useNavigate();\n const location = useLocation();\n const { activeView } = useGradebookUi();\n\n const updateQueryParams = (queryParams) => {\n const { pathname } = location;\n const parsed = queryString.parse(location.search);\n Object.keys(queryParams).forEach((key) => {\n if (queryParams[key]) {\n parsed[key] = queryParams[key];\n } else {\n delete parsed[key];\n }\n });\n navigate({ pathname, search: `?${queryString.stringify(parsed)}` });\n };\n\n return (\n <>\n <GradebookDataLoader />\n <WithSidebar\n sidebar={<GradebookFilters updateQueryParams={updateQueryParams} />}\n >\n <div className=\"px-3 gradebook-content\">\n <GradebookHeader />\n {(activeView === views.bulkManagementHistory\n ? <BulkManagementHistoryView />\n : <GradesView updateQueryParams={updateQueryParams} />\n )}\n </div>\n </WithSidebar>\n </>\n );\n};\n\nexport default GradebookPage;"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/containers/GradebookPage/index.jsx"],"names":[],"mappings":";AAAA,OAAO,WAAW,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE5D,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAEhD,OAAO,WAAW,MAAM,6BAA6B,CAAC;AACtD,OAAO,eAAe,MAAM,iCAAiC,CAAC;AAC9D,OAAO,UAAU,MAAM,4BAA4B,CAAC;AACpD,OAAO,gBAAgB,MAAM,kCAAkC,CAAC;AAChE,OAAO,yBAAyB,MAAM,2CAA2C,CAAC;AAElF,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AAExD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,EAAE;IAChC,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,MAAM,EAAE,UAAU,EAAE,GAAG,cAAc,EAAE,CAAC;IAExC,MAAM,iBAAiB,GAAG,CAAC,WAAW,EAAE,EAAE;QACxC,MAAM,EAAE,QAAQ,EAAE,GAAG,QAAQ,CAAC;QAC9B,MAAM,MAAM,GAAG,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAClD,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;YACvC,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrB,MAAM,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;YACjC,CAAC;iBAAM,CAAC;gBACN,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;YACrB,CAAC;QACH,CAAC,CAAC,CAAC;QACH,QAAQ,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,WAAW,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;IACtE,CAAC,CAAC;IAEF,OAAO,CACL,MAAC,SAAS,IAAC,IAAI,EAAC,IAAI,EAAC,KAAK,mBACxB,KAAC,mBAAmB,KAAG,EACvB,KAAC,WAAW,IACV,OAAO,EAAE,KAAC,gBAAgB,IAAC,iBAAiB,EAAE,iBAAiB,GAAI,YAEnE,eAAK,SAAS,EAAC,mBAAmB,aAChC,KAAC,eAAe,KAAG,EAClB,CAAC,UAAU,KAAK,KAAK,CAAC,qBAAqB;4BAC1C,CAAC,CAAC,KAAC,yBAAyB,KAAG;4BAC/B,CAAC,CAAC,KAAC,UAAU,IAAC,iBAAiB,EAAE,iBAAiB,GAAI,CACvD,IACG,GACM,IACJ,CACb,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,aAAa,CAAC","sourcesContent":["import queryString from 'query-string';\nimport { useNavigate, useLocation } from 'react-router-dom';\n\nimport { Container } from '@openedx/paragon';\n\nimport { useGradebookUi } from '@src/data/gradebookUiContext';\nimport { views } from '@src/data/constants/app';\n\nimport WithSidebar from '@src/components/WithSidebar';\nimport GradebookHeader from '@src/components/GradebookHeader';\nimport GradesView from '@src/components/GradesView';\nimport GradebookFilters from '@src/components/GradebookFilters';\nimport BulkManagementHistoryView from '@src/components/BulkManagementHistoryView';\n\nimport GradebookDataLoader from './GradebookDataLoader';\n\n/**\n * <GradebookPage />\n * Top-level view for the Gradebook MFE.\n * Organizes a header and a pair of views (Grades and BulkManagement) with a toggle-able\n * filter sidebar.\n */\nexport const GradebookPage = () => {\n const navigate = useNavigate();\n const location = useLocation();\n const { activeView } = useGradebookUi();\n\n const updateQueryParams = (queryParams) => {\n const { pathname } = location;\n const parsed = queryString.parse(location.search);\n Object.keys(queryParams).forEach((key) => {\n if (queryParams[key]) {\n parsed[key] = queryParams[key];\n } else {\n delete parsed[key];\n }\n });\n navigate({ pathname, search: `?${queryString.stringify(parsed)}` });\n };\n\n return (\n <Container size=\"xl\" fluid>\n <GradebookDataLoader />\n <WithSidebar\n sidebar={<GradebookFilters updateQueryParams={updateQueryParams} />}\n >\n <div className=\"gradebook-content\">\n <GradebookHeader />\n {(activeView === views.bulkManagementHistory\n ? <BulkManagementHistoryView />\n : <GradesView updateQueryParams={updateQueryParams} />\n )}\n </div>\n </WithSidebar>\n </Container>\n );\n};\n\nexport default GradebookPage;"]}
@@ -13,8 +13,8 @@ export interface AssignmentTypesData {
13
13
  */
14
14
  export declare const getCanUserViewGradebook: (courseId: string) => Promise<boolean>;
15
15
  /**
16
- * getAssignmentTypes()
16
+ * getAssignmentTypes(courseId)
17
17
  * Fetches grading info and derives the assignment-type list plus the
18
18
  * grades-frozen and bulk-management flags that ride along with it.
19
19
  */
20
- export declare const getAssignmentTypes: () => Promise<AssignmentTypesData>;
20
+ export declare const getAssignmentTypes: (courseId: string) => Promise<AssignmentTypesData>;
package/dist/data/api.js CHANGED
@@ -16,17 +16,17 @@ export const allowedRoles = ['staff', 'limited_staff', 'instructor', 'support'];
16
16
  * gradebook for this course (staff, or holding an allowed role in the course).
17
17
  */
18
18
  export const getCanUserViewGradebook = (courseId) => __awaiter(void 0, void 0, void 0, function* () {
19
- const { data } = yield lms.api.fetch.roles();
19
+ const { data } = yield lms.api.fetch.roles(courseId);
20
20
  const isAllowedRole = (role) => (role.course_id === courseId && allowedRoles.includes(role.role));
21
21
  return Boolean(data.is_staff || (data.roles || []).some(isAllowedRole));
22
22
  });
23
23
  /**
24
- * getAssignmentTypes()
24
+ * getAssignmentTypes(courseId)
25
25
  * Fetches grading info and derives the assignment-type list plus the
26
26
  * grades-frozen and bulk-management flags that ride along with it.
27
27
  */
28
- export const getAssignmentTypes = () => __awaiter(void 0, void 0, void 0, function* () {
29
- const { data } = yield lms.api.fetch.assignmentTypes();
28
+ export const getAssignmentTypes = (courseId) => __awaiter(void 0, void 0, void 0, function* () {
29
+ const { data } = yield lms.api.fetch.assignmentTypes(courseId);
30
30
  return {
31
31
  assignmentTypes: Object.keys(data.assignment_types || {}),
32
32
  areGradesFrozen: Boolean(data.grades_frozen),
@@ -1 +1 @@
1
- {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/data/api.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,GAAG,MAAM,wBAAwB,CAAC;AAEzC,oEAAoE;AACpE,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;AAchF;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAO,QAAgB,EAAoB,EAAE;IAClF,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IAC7C,MAAM,aAAa,GAAG,CAAC,IAAgB,EAAE,EAAE,CAAC,CAC1C,IAAI,CAAC,SAAS,KAAK,QAAQ,IAAI,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAChE,CAAC;IACF,OAAO,OAAO,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;AAC1E,CAAC,CAAA,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAuC,EAAE;IACzE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC;IACvD,OAAO;QACL,eAAe,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,IAAI,EAAE,CAAC;QACzD,eAAe,EAAE,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC;QAC5C,uBAAuB,EAAE,OAAO,CAAC,IAAI,CAAC,uBAAuB,CAAC;KAC/D,CAAC;AACJ,CAAC,CAAA,CAAC","sourcesContent":["import lms from '@src/data/services/lms';\n\n/** LMS course roles that grant permission to view the gradebook. */\nexport const allowedRoles = ['staff', 'limited_staff', 'instructor', 'support'];\n\ninterface CourseRole {\n course_id: string;\n role: string;\n}\n\n/** Derived assignment-type data consumed by filters and the header. */\nexport interface AssignmentTypesData {\n assignmentTypes: string[];\n areGradesFrozen: boolean;\n bulkManagementAvailable: boolean;\n}\n\n/**\n * getCanUserViewGradebook(courseId)\n * Fetches the current user's roles and derives whether they may view the\n * gradebook for this course (staff, or holding an allowed role in the course).\n */\nexport const getCanUserViewGradebook = async (courseId: string): Promise<boolean> => {\n const { data } = await lms.api.fetch.roles();\n const isAllowedRole = (role: CourseRole) => (\n role.course_id === courseId && allowedRoles.includes(role.role)\n );\n return Boolean(data.is_staff || (data.roles || []).some(isAllowedRole));\n};\n\n/**\n * getAssignmentTypes()\n * Fetches grading info and derives the assignment-type list plus the\n * grades-frozen and bulk-management flags that ride along with it.\n */\nexport const getAssignmentTypes = async (): Promise<AssignmentTypesData> => {\n const { data } = await lms.api.fetch.assignmentTypes();\n return {\n assignmentTypes: Object.keys(data.assignment_types || {}),\n areGradesFrozen: Boolean(data.grades_frozen),\n bulkManagementAvailable: Boolean(data.can_see_bulk_management),\n };\n};\n"]}
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/data/api.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,GAAG,MAAM,wBAAwB,CAAC;AAEzC,oEAAoE;AACpE,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;AAchF;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAO,QAAgB,EAAoB,EAAE;IAClF,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACrD,MAAM,aAAa,GAAG,CAAC,IAAgB,EAAE,EAAE,CAAC,CAC1C,IAAI,CAAC,SAAS,KAAK,QAAQ,IAAI,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAChE,CAAC;IACF,OAAO,OAAO,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;AAC1E,CAAC,CAAA,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAO,QAAgB,EAAgC,EAAE;IACzF,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC/D,OAAO;QACL,eAAe,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,IAAI,EAAE,CAAC;QACzD,eAAe,EAAE,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC;QAC5C,uBAAuB,EAAE,OAAO,CAAC,IAAI,CAAC,uBAAuB,CAAC;KAC/D,CAAC;AACJ,CAAC,CAAA,CAAC","sourcesContent":["import lms from '@src/data/services/lms';\n\n/** LMS course roles that grant permission to view the gradebook. */\nexport const allowedRoles = ['staff', 'limited_staff', 'instructor', 'support'];\n\ninterface CourseRole {\n course_id: string;\n role: string;\n}\n\n/** Derived assignment-type data consumed by filters and the header. */\nexport interface AssignmentTypesData {\n assignmentTypes: string[];\n areGradesFrozen: boolean;\n bulkManagementAvailable: boolean;\n}\n\n/**\n * getCanUserViewGradebook(courseId)\n * Fetches the current user's roles and derives whether they may view the\n * gradebook for this course (staff, or holding an allowed role in the course).\n */\nexport const getCanUserViewGradebook = async (courseId: string): Promise<boolean> => {\n const { data } = await lms.api.fetch.roles(courseId);\n const isAllowedRole = (role: CourseRole) => (\n role.course_id === courseId && allowedRoles.includes(role.role)\n );\n return Boolean(data.is_staff || (data.roles || []).some(isAllowedRole));\n};\n\n/**\n * getAssignmentTypes(courseId)\n * Fetches grading info and derives the assignment-type list plus the\n * grades-frozen and bulk-management flags that ride along with it.\n */\nexport const getAssignmentTypes = async (courseId: string): Promise<AssignmentTypesData> => {\n const { data } = await lms.api.fetch.assignmentTypes(courseId);\n return {\n assignmentTypes: Object.keys(data.assignment_types || {}),\n areGradesFrozen: Boolean(data.grades_frozen),\n bulkManagementAvailable: Boolean(data.can_see_bulk_management),\n };\n};\n"]}
@@ -1,6 +1,6 @@
1
1
  import { useQuery } from '@tanstack/react-query';
2
- import { useParams } from 'react-router-dom';
3
2
  import { getAssignmentTypes, getCanUserViewGradebook } from './api';
3
+ import { useCourseId } from './courseIdContext';
4
4
  import { assignmentTypesQueryKeys, rolesQueryKeys } from './queryKeys';
5
5
  /**
6
6
  * useCanUserViewGradebook()
@@ -9,7 +9,7 @@ import { assignmentTypesQueryKeys, rolesQueryKeys } from './queryKeys';
9
9
  * mirroring the legacy `fetchRoles` -> fan-out behavior.
10
10
  */
11
11
  export const useCanUserViewGradebook = () => {
12
- const { courseId = '' } = useParams();
12
+ const courseId = useCourseId();
13
13
  return useQuery({
14
14
  queryKey: rolesQueryKeys.byCourse(courseId),
15
15
  queryFn: () => getCanUserViewGradebook(courseId),
@@ -24,7 +24,7 @@ export const useCanUserViewGradebook = () => {
24
24
  */
25
25
  export const useAssignmentTypes = (courseId, { enabled = true } = {}) => useQuery({
26
26
  queryKey: assignmentTypesQueryKeys.byCourse(courseId),
27
- queryFn: getAssignmentTypes,
27
+ queryFn: () => getAssignmentTypes(courseId),
28
28
  enabled: !!courseId && enabled,
29
29
  });
30
30
  /**
@@ -48,7 +48,7 @@ export const useCanViewGradebook = () => {
48
48
  * gate rule lives in one place.
49
49
  */
50
50
  export const useCourseIdWithGate = () => {
51
- const { courseId = '' } = useParams();
51
+ const courseId = useCourseId();
52
52
  const { data: canViewGradebook } = useCanUserViewGradebook();
53
53
  return { courseId, enabled: !!canViewGradebook };
54
54
  };
@@ -1 +1 @@
1
- {"version":3,"file":"apiHook.js","sourceRoot":"","sources":["../../src/data/apiHook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,OAAO,CAAC;AACpE,OAAO,EAAE,wBAAwB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,EAAE;IAC1C,MAAM,EAAE,QAAQ,GAAG,EAAE,EAAE,GAAG,SAAS,EAAE,CAAC;IACtC,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,cAAc,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC3C,OAAO,EAAE,GAAG,EAAE,CAAC,uBAAuB,CAAC,QAAQ,CAAC;QAChD,OAAO,EAAE,CAAC,CAAC,QAAQ;KACpB,CAAC,CAAC;AACL,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,QAAgB,EAChB,EAAE,OAAO,GAAG,IAAI,KAA4B,EAAE,EAC9C,EAAE,CAAC,QAAQ,CAAC;IACZ,QAAQ,EAAE,wBAAwB,CAAC,QAAQ,CAAC,QAAQ,CAAC;IACrD,OAAO,EAAE,kBAAkB;IAC3B,OAAO,EAAE,CAAC,CAAC,QAAQ,IAAI,OAAO;CAC/B,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAY,EAAE;IAC/C,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,uBAAuB,EAAE,CAAC;IACpD,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC5C,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAA2C,EAAE;IAC9E,MAAM,EAAE,QAAQ,GAAG,EAAE,EAAE,GAAG,SAAS,EAAE,CAAC;IACtC,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,GAAG,uBAAuB,EAAE,CAAC;IAC7D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,gBAAgB,EAAE,CAAC;AACnD,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAY,EAAE;;IACjD,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,mBAAmB,EAAE,CAAC;IACpD,OAAO,CAAC,CAAC,CAAA,MAAA,kBAAkB,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,0CAAE,uBAAuB,CAAA,CAAC;AACnF,CAAC,CAAC","sourcesContent":["import { useQuery } from '@tanstack/react-query';\nimport { useParams } from 'react-router-dom';\n\nimport { getAssignmentTypes, getCanUserViewGradebook } from './api';\nimport { assignmentTypesQueryKeys, rolesQueryKeys } from './queryKeys';\n\n/**\n * useCanUserViewGradebook()\n * Query for the roles-based permission gate. This is the root of the fetch\n * cascade: the other server queries stay disabled until this resolves truthy,\n * mirroring the legacy `fetchRoles` -> fan-out behavior.\n */\nexport const useCanUserViewGradebook = () => {\n const { courseId = '' } = useParams();\n return useQuery({\n queryKey: rolesQueryKeys.byCourse(courseId),\n queryFn: () => getCanUserViewGradebook(courseId),\n enabled: !!courseId,\n });\n};\n\n/**\n * useAssignmentTypes()\n * Single-responsibility query for assignment types (types + grades-frozen +\n * bulk-management flags). Caller supplies `courseId` and decides when it may\n * run via `enabled`.\n */\nexport const useAssignmentTypes = (\n courseId: string,\n { enabled = true }: { enabled?: boolean } = {},\n) => useQuery({\n queryKey: assignmentTypesQueryKeys.byCourse(courseId),\n queryFn: getAssignmentTypes,\n enabled: !!courseId && enabled,\n});\n\n/**\n * useCanViewGradebook()\n * The optimistic boolean read of the roles gate used by UI (header, spinner):\n * `true` while the roles query is still loading (the legacy Redux initial state\n * was `true`), the resolved value on success, `false` on error.\n */\nexport const useCanViewGradebook = (): boolean => {\n const { data, isError } = useCanUserViewGradebook();\n if (isError) {\n return false;\n }\n return data === undefined ? true : !!data;\n};\n\n/**\n * useCourseIdWithGate()\n * Shared gate for the downstream server queries: the routed `courseId` plus an\n * `enabled` flag that resolves truthy once `useCanUserViewGradebook` does.\n * Callers pass the returned pair into single-responsibility query hooks so the\n * gate rule lives in one place.\n */\nexport const useCourseIdWithGate = (): { courseId: string; enabled: boolean } => {\n const { courseId = '' } = useParams();\n const { data: canViewGradebook } = useCanUserViewGradebook();\n return { courseId, enabled: !!canViewGradebook };\n};\n\n/**\n * useShowBulkManagement()\n * Whether bulk management is available for the course (derived from the\n * assignment-types query). Gates the header toggle + bulk-management controls.\n */\nexport const useShowBulkManagement = (): boolean => {\n const { courseId, enabled } = useCourseIdWithGate();\n return !!useAssignmentTypes(courseId, { enabled }).data?.bulkManagementAvailable;\n};\n"]}
1
+ {"version":3,"file":"apiHook.js","sourceRoot":"","sources":["../../src/data/apiHook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,OAAO,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,wBAAwB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,EAAE;IAC1C,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,cAAc,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC3C,OAAO,EAAE,GAAG,EAAE,CAAC,uBAAuB,CAAC,QAAQ,CAAC;QAChD,OAAO,EAAE,CAAC,CAAC,QAAQ;KACpB,CAAC,CAAC;AACL,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,QAAgB,EAChB,EAAE,OAAO,GAAG,IAAI,KAA4B,EAAE,EAC9C,EAAE,CAAC,QAAQ,CAAC;IACZ,QAAQ,EAAE,wBAAwB,CAAC,QAAQ,CAAC,QAAQ,CAAC;IACrD,OAAO,EAAE,GAAG,EAAE,CAAC,kBAAkB,CAAC,QAAQ,CAAC;IAC3C,OAAO,EAAE,CAAC,CAAC,QAAQ,IAAI,OAAO;CAC/B,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAY,EAAE;IAC/C,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,uBAAuB,EAAE,CAAC;IACpD,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC5C,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAA2C,EAAE;IAC9E,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,GAAG,uBAAuB,EAAE,CAAC;IAC7D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,gBAAgB,EAAE,CAAC;AACnD,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAY,EAAE;;IACjD,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,mBAAmB,EAAE,CAAC;IACpD,OAAO,CAAC,CAAC,CAAA,MAAA,kBAAkB,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,0CAAE,uBAAuB,CAAA,CAAC;AACnF,CAAC,CAAC","sourcesContent":["import { useQuery } from '@tanstack/react-query';\n\nimport { getAssignmentTypes, getCanUserViewGradebook } from './api';\nimport { useCourseId } from './courseIdContext';\nimport { assignmentTypesQueryKeys, rolesQueryKeys } from './queryKeys';\n\n/**\n * useCanUserViewGradebook()\n * Query for the roles-based permission gate. This is the root of the fetch\n * cascade: the other server queries stay disabled until this resolves truthy,\n * mirroring the legacy `fetchRoles` -> fan-out behavior.\n */\nexport const useCanUserViewGradebook = () => {\n const courseId = useCourseId();\n return useQuery({\n queryKey: rolesQueryKeys.byCourse(courseId),\n queryFn: () => getCanUserViewGradebook(courseId),\n enabled: !!courseId,\n });\n};\n\n/**\n * useAssignmentTypes()\n * Single-responsibility query for assignment types (types + grades-frozen +\n * bulk-management flags). Caller supplies `courseId` and decides when it may\n * run via `enabled`.\n */\nexport const useAssignmentTypes = (\n courseId: string,\n { enabled = true }: { enabled?: boolean } = {},\n) => useQuery({\n queryKey: assignmentTypesQueryKeys.byCourse(courseId),\n queryFn: () => getAssignmentTypes(courseId),\n enabled: !!courseId && enabled,\n});\n\n/**\n * useCanViewGradebook()\n * The optimistic boolean read of the roles gate used by UI (header, spinner):\n * `true` while the roles query is still loading (the legacy Redux initial state\n * was `true`), the resolved value on success, `false` on error.\n */\nexport const useCanViewGradebook = (): boolean => {\n const { data, isError } = useCanUserViewGradebook();\n if (isError) {\n return false;\n }\n return data === undefined ? true : !!data;\n};\n\n/**\n * useCourseIdWithGate()\n * Shared gate for the downstream server queries: the routed `courseId` plus an\n * `enabled` flag that resolves truthy once `useCanUserViewGradebook` does.\n * Callers pass the returned pair into single-responsibility query hooks so the\n * gate rule lives in one place.\n */\nexport const useCourseIdWithGate = (): { courseId: string; enabled: boolean } => {\n const courseId = useCourseId();\n const { data: canViewGradebook } = useCanUserViewGradebook();\n return { courseId, enabled: !!canViewGradebook };\n};\n\n/**\n * useShowBulkManagement()\n * Whether bulk management is available for the course (derived from the\n * assignment-types query). Gates the header toggle + bulk-management controls.\n */\nexport const useShowBulkManagement = (): boolean => {\n const { courseId, enabled } = useCourseIdWithGate();\n return !!useAssignmentTypes(courseId, { enabled }).data?.bulkManagementAvailable;\n};\n"]}
@@ -0,0 +1,2 @@
1
+ export declare const CourseIdContext: import("react").Context<string>;
2
+ export declare const useCourseId: () => string;
@@ -0,0 +1,6 @@
1
+ import { createContext, useContext } from 'react';
2
+ // Widget contract: hosts (route or dashboard slot) supply courseId here so the
3
+ // data hooks don't depend on the enclosing router's `:courseId` param.
4
+ export const CourseIdContext = createContext('');
5
+ export const useCourseId = () => useContext(CourseIdContext);
6
+ //# sourceMappingURL=courseIdContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"courseIdContext.js","sourceRoot":"","sources":["../../src/data/courseIdContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAElD,+EAA+E;AAC/E,uEAAuE;AACvE,MAAM,CAAC,MAAM,eAAe,GAAG,aAAa,CAAS,EAAE,CAAC,CAAC;AACzD,MAAM,CAAC,MAAM,WAAW,GAAG,GAAW,EAAE,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC","sourcesContent":["import { createContext, useContext } from 'react';\n\n// Widget contract: hosts (route or dashboard slot) supply courseId here so the\n// data hooks don't depend on the enclosing router's `:courseId` param.\nexport const CourseIdContext = createContext<string>('');\nexport const useCourseId = (): string => useContext(CourseIdContext);\n"]}
@@ -0,0 +1,6 @@
1
+ export declare const GradebookNavigationContext: import("react").Context<{
2
+ onBack?: () => void;
3
+ }>;
4
+ export declare const useGradebookNavigation: () => {
5
+ onBack?: () => void;
6
+ };
@@ -0,0 +1,9 @@
1
+ import { createContext, useContext } from 'react';
2
+ // Optional host-provided handler for the built-in "Back to Dashboard" link in
3
+ // GradebookHeader. When set, it wins over URL navigation, letting slot hosts
4
+ // return to their own local view (e.g. the CCX Coach Student Grades tab).
5
+ export const GradebookNavigationContext = createContext({
6
+ onBack: undefined,
7
+ });
8
+ export const useGradebookNavigation = () => useContext(GradebookNavigationContext);
9
+ //# sourceMappingURL=gradebookNavigationContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gradebookNavigationContext.js","sourceRoot":"","sources":["../../src/data/gradebookNavigationContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAElD,8EAA8E;AAC9E,6EAA6E;AAC7E,0EAA0E;AAC1E,MAAM,CAAC,MAAM,0BAA0B,GAAG,aAAa,CAA0B;IAC/E,MAAM,EAAE,SAAS;CAClB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,0BAA0B,CAAC,CAAC","sourcesContent":["import { createContext, useContext } from 'react';\n\n// Optional host-provided handler for the built-in \"Back to Dashboard\" link in\n// GradebookHeader. When set, it wins over URL navigation, letting slot hosts\n// return to their own local view (e.g. the CCX Coach Student Grades tab).\nexport const GradebookNavigationContext = createContext<{ onBack?: () => void }>({\n onBack: undefined,\n});\n\nexport const useGradebookNavigation = () => useContext(GradebookNavigationContext);\n"]}
@@ -7,21 +7,22 @@ const { get, post, stringifyUrl } = utils;
7
7
  /*********************************************************************************
8
8
  * GET Actions
9
9
  *********************************************************************************/
10
- const assignmentTypes = () => get(urls.getAssignmentTypesUrl());
11
- const cohorts = () => get(urls.getCohortsUrl());
12
- const roles = () => get(urls.getRolesUrl());
13
- const tracks = () => get(urls.getTracksUrl());
10
+ const assignmentTypes = (courseId) => get(urls.getAssignmentTypesUrl(courseId));
11
+ const cohorts = (courseId) => get(urls.getCohortsUrl(courseId));
12
+ const roles = (courseId) => get(urls.getRolesUrl(courseId));
13
+ const tracks = (courseId) => get(urls.getTracksUrl(courseId));
14
14
  /**
15
- * fetch.gradebookData(searchText, cohort, track, options)
15
+ * fetch.gradebookData(courseId, searchText, cohort, track, options)
16
16
  * fetches updated gradebook data based on current filter selections.
17
17
  * Raises an error if assignment grade limits are set, but not assignment.
18
+ * @param {string} courseId - course identifier
18
19
  * @param {string} searchText - search text filter
19
20
  * @param {nunber} cohort - selected cohort filter
20
21
  * @param {string} track - selected track filter
21
22
  * @param {object} options - additional optional filter values
22
23
  * @return {Promise} - get response
23
24
  */
24
- const gradebookData = (searchText, cohort, track, options = {}) => {
25
+ const gradebookData = (courseId, searchText, cohort, track, options = {}) => {
25
26
  if ((options.assignmentGradeMax || options.assignmentGradeMin) && !options.assignment) {
26
27
  throw new Error(messages.errors.missingAssignment);
27
28
  }
@@ -37,14 +38,15 @@ const gradebookData = (searchText, cohort, track, options = {}) => {
37
38
  [paramKeys.assignmentGradeMax]: options.assignmentGradeMax,
38
39
  [paramKeys.assignmentGradeMin]: options.assignmentGradeMin,
39
40
  };
40
- return get(stringifyUrl(urls.getGradebookUrl(), queryParams));
41
+ return get(stringifyUrl(urls.getGradebookUrl(courseId), queryParams));
41
42
  };
42
43
  /**
43
- * fetch.gradeBulkOperationHistory()
44
+ * fetch.gradeBulkOperationHistory(courseId)
44
45
  * fetches bulk operation history and raises an error if the operation fails
46
+ * @param {string} courseId - course identifier
45
47
  * @return {Promise} - get response
46
48
  */
47
- const gradeBulkOperationHistory = () => get(urls.getBulkHistoryUrl())
49
+ const gradeBulkOperationHistory = (courseId) => get(urls.getBulkHistoryUrl(courseId))
48
50
  .then(response => response.data)
49
51
  .catch(() => Promise.reject(Error(messages.errors.unhandledResponse)));
50
52
  /**
@@ -59,8 +61,9 @@ const gradeOverrideHistory = (subsectionId, userId) => (get(sectionOverrideHisto
59
61
  * POST Actions
60
62
  *********************************************************************************/
61
63
  /**
62
- * updateGradebookData(updateData)
64
+ * updateGradebookData(courseId, updateData)
63
65
  * sends an update message with new grades overrides
66
+ * @param {string} courseId - course identifier
64
67
  * @param {object[]} updateData
65
68
  * {
66
69
  * user_id: <int>,
@@ -74,15 +77,16 @@ const gradeOverrideHistory = (subsectionId, userId) => (get(sectionOverrideHisto
74
77
  * }
75
78
  * @return {Promise} - post response
76
79
  */
77
- const updateGradebookData = (updateData) => post(urls.getBulkUpdateUrl(), updateData);
80
+ const updateGradebookData = (courseId, updateData) => post(urls.getBulkUpdateUrl(courseId), updateData);
78
81
  /**
79
- * uploadGradeCsv(formData)
82
+ * uploadGradeCsv(courseId, formData)
80
83
  * Posts form data to grade csv url. On success, forwards response data.
81
84
  * Reject promise with result on failure.
85
+ * @param {string} courseId - course identifier
82
86
  * @param {object} formData - new grade data
83
87
  * @return {Promise} - post response
84
88
  */
85
- const uploadGradeCsv = (formData) => (post(gradeCsvUrl(), formData).then((result) => {
89
+ const uploadGradeCsv = (courseId, formData) => (post(gradeCsvUrl(courseId), formData).then((result) => {
86
90
  if (result.status === 200 && !result.data.error_messages.length) {
87
91
  return result.data;
88
92
  }