@openedx/frontend-app-gradebook 2.0.0-alpha.5 → 2.0.0-alpha.6
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.
- package/README.rst +1 -5
- package/dist/app.js +0 -2
- package/dist/app.js.map +1 -1
- package/dist/components/BulkManagementHistoryView/data/apiHook.js +2 -2
- package/dist/components/BulkManagementHistoryView/data/apiHook.js.map +1 -1
- package/dist/components/GradebookFilters/data/apiHook.js +3 -3
- package/dist/components/GradebookFilters/data/apiHook.js.map +1 -1
- package/dist/components/GradesView/data/apiHook.js +7 -4
- package/dist/components/GradesView/data/apiHook.js.map +1 -1
- package/dist/components/GradesView/data/hooks.js +4 -2
- package/dist/components/GradesView/data/hooks.js.map +1 -1
- package/dist/components/GradesView/data/queryKeys.d.ts +4 -0
- package/dist/components/GradesView/data/queryKeys.js +4 -0
- package/dist/components/GradesView/data/queryKeys.js.map +1 -1
- package/dist/data/apiHook.js +3 -3
- package/dist/data/apiHook.js.map +1 -1
- package/dist/data/query.d.ts +14 -0
- package/dist/data/query.js +27 -0
- package/dist/data/query.js.map +1 -0
- package/dist/data/queryKeys.js +3 -0
- package/dist/data/queryKeys.js.map +1 -1
- package/package.json +1 -1
- package/dist/data/queryClient.d.ts +0 -10
- package/dist/data/queryClient.js +0 -34
- package/dist/data/queryClient.js.map +0 -1
- package/dist/providers.d.ts +0 -3
- package/dist/providers.js +0 -13
- package/dist/providers.js.map +0 -1
package/README.rst
CHANGED
|
@@ -176,16 +176,12 @@ Directory Structure
|
|
|
176
176
|
|
|
177
177
|
* ``app.ts``
|
|
178
178
|
|
|
179
|
-
* The ``App`` object consumed by ``site.config.*.tsx``
|
|
179
|
+
* The ``App`` object consumed by ``site.config.*.tsx`` - wires ``appId``, ``routes``, and ``slots`` for frontend-base to register.
|
|
180
180
|
|
|
181
181
|
* ``routes.tsx``
|
|
182
182
|
|
|
183
183
|
* React Router route definitions for the app.
|
|
184
184
|
|
|
185
|
-
* ``providers.tsx``
|
|
186
|
-
|
|
187
|
-
* App-scoped context providers registered with frontend-base.
|
|
188
|
-
|
|
189
185
|
* ``slots``
|
|
190
186
|
|
|
191
187
|
* Slots this app exposes for host sites to plug into.
|
package/dist/app.js
CHANGED
package/dist/app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,MAAM,MAAM,UAAU,CAAC;AAC9B,OAAO,
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,MAAM,MAAM,UAAU,CAAC;AAC9B,OAAO,KAAK,MAAM,SAAS,CAAC;AAE5B,MAAM,GAAG,GAAQ;IACf,KAAK;IACL,MAAM;IACN,KAAK;CACN,CAAC;AAEF,eAAe,GAAG,CAAC","sourcesContent":["import { App } from '@openedx/frontend-base';\nimport { appId } from './constants';\nimport routes from './routes';\nimport slots from './slots';\n\nconst app: App = {\n appId,\n routes,\n slots,\n};\n\nexport default app;\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useQuery } from '@tanstack/react-query';
|
|
2
1
|
import { useAssignmentTypes, useCourseIdWithGate } from '../../../data/apiHook';
|
|
2
|
+
import { useGradebookQuery } from '../../../data/query';
|
|
3
3
|
import { getBulkOperationHistory } from './api';
|
|
4
4
|
import { bulkOperationHistoryQueryKeys } from './queryKeys';
|
|
5
5
|
import { transformHistoryEntry } from './utils';
|
|
@@ -11,7 +11,7 @@ const EMPTY_ARRAY = [];
|
|
|
11
11
|
* Bulk grade-override upload history. Single-responsibility query hook: the
|
|
12
12
|
* caller decides when it may run via `courseId` and `enabled`.
|
|
13
13
|
*/
|
|
14
|
-
export const useBulkOperationHistory = (courseId, { enabled = true } = {}) =>
|
|
14
|
+
export const useBulkOperationHistory = (courseId, { enabled = true } = {}) => useGradebookQuery({
|
|
15
15
|
queryKey: bulkOperationHistoryQueryKeys.byCourse(courseId),
|
|
16
16
|
queryFn: () => getBulkOperationHistory(courseId),
|
|
17
17
|
enabled: !!courseId && enabled,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiHook.js","sourceRoot":"","sources":["../../../../src/components/BulkManagementHistoryView/data/apiHook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"apiHook.js","sourceRoot":"","sources":["../../../../src/components/BulkManagementHistoryView/data/apiHook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpD,OAAO,EAAE,uBAAuB,EAAE,MAAM,OAAO,CAAC;AAChD,OAAO,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAEhD,8EAA8E;AAC9E,0DAA0D;AAC1D,MAAM,WAAW,GAAY,EAAE,CAAC;AAEhC;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CACrC,QAAgB,EAChB,EAAE,OAAO,GAAG,IAAI,KAA4B,EAAE,EAC9C,EAAE,CAAC,iBAAiB,CAAC;IACrB,QAAQ,EAAE,6BAA6B,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAC1D,OAAO,EAAE,GAAG,EAAE,CAAC,uBAAuB,CAAC,QAAQ,CAAC;IAChD,OAAO,EAAE,CAAC,CAAC,QAAQ,IAAI,OAAO;CAC/B,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,GAAG,EAAE;IAClD,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,mBAAmB,EAAE,CAAC;IACpD,MAAM,EAAE,IAAI,EAAE,mBAAmB,EAAE,GAAG,kBAAkB,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAChF,MAAM,EAAE,IAAI,EAAE,GAAG,uBAAuB,CAAC,QAAQ,EAAE;QACjD,OAAO,EAAE,OAAO,IAAI,CAAC,CAAC,CAAA,mBAAmB,aAAnB,mBAAmB,uBAAnB,mBAAmB,CAAE,uBAAuB,CAAA;KACnE,CAAC,CAAC;IACH,OAAO,CAAC,IAAI,aAAJ,IAAI,cAAJ,IAAI,GAAI,WAAW,CAAC,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;AAC1D,CAAC,CAAC","sourcesContent":["import { useAssignmentTypes, useCourseIdWithGate } from '@src/data/apiHook';\nimport { useGradebookQuery } from '@src/data/query';\n\nimport { getBulkOperationHistory } from './api';\nimport { bulkOperationHistoryQueryKeys } from './queryKeys';\nimport { transformHistoryEntry } from './utils';\n\n// Stable empty reference so the shaped read-model hook doesn't return a fresh\n// array on every render while the query is still loading.\nconst EMPTY_ARRAY: never[] = [];\n\n/**\n * useBulkOperationHistory()\n * Bulk grade-override upload history. Single-responsibility query hook: the\n * caller decides when it may run via `courseId` and `enabled`.\n */\nexport const useBulkOperationHistory = (\n courseId: string,\n { enabled = true }: { enabled?: boolean } = {},\n) => useGradebookQuery({\n queryKey: bulkOperationHistoryQueryKeys.byCourse(courseId),\n queryFn: () => getBulkOperationHistory(courseId),\n enabled: !!courseId && enabled,\n});\n\n/**\n * useBulkManagementHistoryEntries()\n * Read-model for the history table: wires the roles + assignment-types gates\n * into `useBulkOperationHistory` and shapes the results with\n * `transformHistoryEntry`. Replaces the former facade selector\n * `selectors.grades.useBulkManagementHistoryEntries`.\n */\nexport const useBulkManagementHistoryEntries = () => {\n const { courseId, enabled } = useCourseIdWithGate();\n const { data: assignmentTypesData } = useAssignmentTypes(courseId, { enabled });\n const { data } = useBulkOperationHistory(courseId, {\n enabled: enabled && !!assignmentTypesData?.bulkManagementAvailable,\n });\n return (data ?? EMPTY_ARRAY).map(transformHistoryEntry);\n};\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useGradebookQuery } from '../../../data/query';
|
|
2
2
|
import { getCohorts, getTracks } from './api';
|
|
3
3
|
import { cohortsQueryKeys, tracksQueryKeys } from './queryKeys';
|
|
4
4
|
/**
|
|
@@ -6,7 +6,7 @@ import { cohortsQueryKeys, tracksQueryKeys } from './queryKeys';
|
|
|
6
6
|
* Single-responsibility query for the course's cohorts. Caller supplies
|
|
7
7
|
* `courseId` and decides when it may run via `enabled`.
|
|
8
8
|
*/
|
|
9
|
-
export const useCohorts = (courseId, { enabled = true } = {}) =>
|
|
9
|
+
export const useCohorts = (courseId, { enabled = true } = {}) => useGradebookQuery({
|
|
10
10
|
queryKey: cohortsQueryKeys.byCourse(courseId),
|
|
11
11
|
queryFn: () => getCohorts(courseId),
|
|
12
12
|
enabled: !!courseId && enabled,
|
|
@@ -16,7 +16,7 @@ export const useCohorts = (courseId, { enabled = true } = {}) => useQuery({
|
|
|
16
16
|
* Single-responsibility query for the course's enrollment tracks. Caller
|
|
17
17
|
* supplies `courseId` and decides when it may run via `enabled`.
|
|
18
18
|
*/
|
|
19
|
-
export const useTracks = (courseId, { enabled = true } = {}) =>
|
|
19
|
+
export const useTracks = (courseId, { enabled = true } = {}) => useGradebookQuery({
|
|
20
20
|
queryKey: tracksQueryKeys.byCourse(courseId),
|
|
21
21
|
queryFn: () => getTracks(courseId),
|
|
22
22
|
enabled: !!courseId && enabled,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiHook.js","sourceRoot":"","sources":["../../../../src/components/GradebookFilters/data/apiHook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"apiHook.js","sourceRoot":"","sources":["../../../../src/components/GradebookFilters/data/apiHook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEhE;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CACxB,QAAgB,EAChB,EAAE,OAAO,GAAG,IAAI,KAA4B,EAAE,EAC9C,EAAE,CAAC,iBAAiB,CAAC;IACrB,QAAQ,EAAE,gBAAgB,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAC7C,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;IACnC,OAAO,EAAE,CAAC,CAAC,QAAQ,IAAI,OAAO;CAC/B,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,QAAgB,EAChB,EAAE,OAAO,GAAG,IAAI,KAA4B,EAAE,EAC9C,EAAE,CAAC,iBAAiB,CAAC;IACrB,QAAQ,EAAE,eAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAC5C,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC;IAClC,OAAO,EAAE,CAAC,CAAC,QAAQ,IAAI,OAAO;CAC/B,CAAC,CAAC","sourcesContent":["import { useGradebookQuery } from '@src/data/query';\n\nimport { getCohorts, getTracks } from './api';\nimport { cohortsQueryKeys, tracksQueryKeys } from './queryKeys';\n\n/**\n * useCohorts()\n * Single-responsibility query for the course's cohorts. Caller supplies\n * `courseId` and decides when it may run via `enabled`.\n */\nexport const useCohorts = (\n courseId: string,\n { enabled = true }: { enabled?: boolean } = {},\n) => useGradebookQuery({\n queryKey: cohortsQueryKeys.byCourse(courseId),\n queryFn: () => getCohorts(courseId),\n enabled: !!courseId && enabled,\n});\n\n/**\n * useTracks()\n * Single-responsibility query for the course's enrollment tracks. Caller\n * supplies `courseId` and decides when it may run via `enabled`.\n */\nexport const useTracks = (\n courseId: string,\n { enabled = true }: { enabled?: boolean } = {},\n) => useGradebookQuery({\n queryKey: tracksQueryKeys.byCourse(courseId),\n queryFn: () => getTracks(courseId),\n enabled: !!courseId && enabled,\n});\n"]}
|
|
@@ -8,19 +8,20 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { useMemo } from 'react';
|
|
11
|
-
import { useMutation,
|
|
11
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
12
12
|
import lms from '../../../data/services/lms';
|
|
13
13
|
import { sortAlphaAsc } from '../../../data/formatUtils';
|
|
14
14
|
import { filtersSnapshot } from '../../../data/filtersSnapshot';
|
|
15
15
|
import { useGradebookUi } from '../../../data/gradebookUiContext';
|
|
16
16
|
import { useCourseIdWithGate } from '../../../data/apiHook';
|
|
17
17
|
import { useCourseId } from '../../../data/courseIdContext';
|
|
18
|
+
import { useGradebookQuery } from '../../../data/query';
|
|
18
19
|
import { trackGradesDisplayed, trackGradeOverrideSucceeded, trackGradeOverrideFailed, trackUploadOverrideSucceeded, trackUploadOverrideFailed, } from '../../../data/services/segment/events';
|
|
19
20
|
// Cross-feature invalidation: a successful CSV upload refreshes the bulk-operation
|
|
20
21
|
// history owned by the BulkManagementHistoryView feature.
|
|
21
22
|
import { bulkOperationHistoryQueryKeys } from '../../BulkManagementHistoryView/data/queryKeys';
|
|
22
23
|
import { getGradeOverrideHistory, getGrades, getGradesPage } from './api';
|
|
23
|
-
import { gradeOverrideHistoryQueryKeys, gradesQueryKeys } from './queryKeys';
|
|
24
|
+
import { gradeMutationKeys, gradeOverrideHistoryQueryKeys, gradesQueryKeys } from './queryKeys';
|
|
24
25
|
import { buildGradesFetchParams } from './utils';
|
|
25
26
|
/**
|
|
26
27
|
* Builds the `getGrades` params from the live `filtersSnapshot`. Reads at fetch
|
|
@@ -40,7 +41,7 @@ const buildGradebookDataParams = () => buildGradesFetchParams(filtersSnapshot);
|
|
|
40
41
|
* latest values); a non-null cursor fetches that opaque prev/next URL —
|
|
41
42
|
* replacing the legacy `fetchPrevNextGrades` thunk.
|
|
42
43
|
*/
|
|
43
|
-
export const useGrades = (courseId, gradesPageEndpoint, { enabled = true } = {}) =>
|
|
44
|
+
export const useGrades = (courseId, gradesPageEndpoint, { enabled = true } = {}) => useGradebookQuery({
|
|
44
45
|
queryKey: [...gradesQueryKeys.byCourse(courseId), gradesPageEndpoint !== null && gradesPageEndpoint !== void 0 ? gradesPageEndpoint : 'base'],
|
|
45
46
|
queryFn: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
46
47
|
const data = gradesPageEndpoint
|
|
@@ -91,7 +92,7 @@ export const useGradesData = () => {
|
|
|
91
92
|
* for a specific cell). `retry: false` because a `success: false` business error
|
|
92
93
|
* is not worth retrying.
|
|
93
94
|
*/
|
|
94
|
-
export const useGradeOverrideHistory = (subsectionId, userId) => (
|
|
95
|
+
export const useGradeOverrideHistory = (subsectionId, userId) => (useGradebookQuery({
|
|
95
96
|
queryKey: gradeOverrideHistoryQueryKeys.byCell(subsectionId !== null && subsectionId !== void 0 ? subsectionId : null, userId !== null && userId !== void 0 ? userId : null),
|
|
96
97
|
queryFn: () => getGradeOverrideHistory(subsectionId, userId),
|
|
97
98
|
enabled: !!subsectionId && userId !== undefined && userId !== null,
|
|
@@ -117,6 +118,7 @@ export const useUpdateGrades = () => {
|
|
|
117
118
|
const courseId = useCourseId();
|
|
118
119
|
const { setShowSuccess, setGradesPageEndpoint, modalState } = useGradebookUi();
|
|
119
120
|
const mutation = useMutation({
|
|
121
|
+
mutationKey: gradeMutationKeys.updateGrades,
|
|
120
122
|
mutationFn: (updateData) => lms.api.updateGradebookData(courseId, updateData),
|
|
121
123
|
onSuccess: ({ data }, updateData) => {
|
|
122
124
|
trackGradeOverrideSucceeded(courseId, data);
|
|
@@ -162,6 +164,7 @@ export const useSubmitImportGradesButtonData = () => {
|
|
|
162
164
|
const courseId = useCourseId();
|
|
163
165
|
const { setShowImportSuccessToast, resetCsvUpload, markCsvUploadSuccess, setCsvUploadErrors, } = useGradebookUi();
|
|
164
166
|
const mutation = useMutation({
|
|
167
|
+
mutationKey: gradeMutationKeys.uploadGradeCsv,
|
|
165
168
|
mutationFn: (formData) => lms.api.uploadGradeCsv(courseId, formData),
|
|
166
169
|
onMutate: () => {
|
|
167
170
|
resetCsvUpload();
|
|
@@ -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';\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"]}
|
|
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, 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 { useGradebookQuery } from '@src/data/query';\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 { gradeMutationKeys, 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) => useGradebookQuery({\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 useGradebookQuery({\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 mutationKey: gradeMutationKeys.updateGrades,\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 mutationKey: gradeMutationKeys.uploadGradeCsv,\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"]}
|
|
@@ -17,6 +17,7 @@ import { useFilters } from '../../../data/filtersContext';
|
|
|
17
17
|
import { useGradebookUi } from '../../../data/gradebookUiContext';
|
|
18
18
|
import { useCanViewGradebook } from '../../../data/apiHook';
|
|
19
19
|
import { useCourseId } from '../../../data/courseIdContext';
|
|
20
|
+
import { BASE_KEY } from '../../../data/queryKeys';
|
|
20
21
|
import { useSelectedCohortEntry, useSelectedTrackEntry, } from '../../GradebookFilters/data/hooks';
|
|
21
22
|
import { useGradesData, useGradeOverrideHistory } from './apiHook';
|
|
22
23
|
import { gradesQueryKeys } from './queryKeys';
|
|
@@ -72,12 +73,13 @@ export const useSelectedAssignmentLabel = () => { var _a; return (_a = useSelect
|
|
|
72
73
|
/**
|
|
73
74
|
* useShouldShowSpinner()
|
|
74
75
|
* The busy indicator: the roles gate combined with the grades query fetching or
|
|
75
|
-
*
|
|
76
|
+
* one of the gradebook's own mutations in flight (grade override save / CSV
|
|
77
|
+
* upload).
|
|
76
78
|
*/
|
|
77
79
|
export const useShouldShowSpinner = () => {
|
|
78
80
|
const canViewGradebook = useCanViewGradebook();
|
|
79
81
|
const { isFetching } = useGradesData();
|
|
80
|
-
const mutatingCount = useIsMutating();
|
|
82
|
+
const mutatingCount = useIsMutating({ mutationKey: BASE_KEY });
|
|
81
83
|
return canViewGradebook && (isFetching || mutatingCount > 0);
|
|
82
84
|
};
|
|
83
85
|
/**
|
|
@@ -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';\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"]}
|
|
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 { BASE_KEY } from '@src/data/queryKeys';\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 * one of the gradebook's own mutations in flight (grade override save / CSV\n * upload).\n */\nexport const useShouldShowSpinner = () => {\n const canViewGradebook = useCanViewGradebook();\n const { isFetching } = useGradesData();\n const mutatingCount = useIsMutating({ mutationKey: BASE_KEY });\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"]}
|
|
@@ -6,6 +6,10 @@ export declare const gradesQueryKeys: {
|
|
|
6
6
|
all: readonly ["gradebook"];
|
|
7
7
|
byCourse: (courseId: string) => readonly ["gradebook", string, "grades"];
|
|
8
8
|
};
|
|
9
|
+
export declare const gradeMutationKeys: {
|
|
10
|
+
updateGrades: readonly ["gradebook", "updateGrades"];
|
|
11
|
+
uploadGradeCsv: readonly ["gradebook", "uploadGradeCsv"];
|
|
12
|
+
};
|
|
9
13
|
/** Grade-override history for one learner on one subsection (edit modal). */
|
|
10
14
|
export declare const gradeOverrideHistoryQueryKeys: {
|
|
11
15
|
all: readonly ["gradebook", "gradeOverrideHistory"];
|
|
@@ -7,6 +7,10 @@ export const gradesQueryKeys = {
|
|
|
7
7
|
all: BASE_KEY,
|
|
8
8
|
byCourse: (courseId) => [...BASE_KEY, courseId, 'grades'],
|
|
9
9
|
};
|
|
10
|
+
export const gradeMutationKeys = {
|
|
11
|
+
updateGrades: [...BASE_KEY, 'updateGrades'],
|
|
12
|
+
uploadGradeCsv: [...BASE_KEY, 'uploadGradeCsv'],
|
|
13
|
+
};
|
|
10
14
|
/** Grade-override history for one learner on one subsection (edit modal). */
|
|
11
15
|
export const gradeOverrideHistoryQueryKeys = {
|
|
12
16
|
all: [...BASE_KEY, 'gradeOverrideHistory'],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queryKeys.js","sourceRoot":"","sources":["../../../../src/components/GradesView/data/queryKeys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE/C;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,GAAG,EAAE,QAAQ;IACb,QAAQ,EAAE,CAAC,QAAgB,EAAE,EAAE,CAAC,CAAC,GAAG,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAU;CAC3E,CAAC;AAEF,6EAA6E;AAC7E,MAAM,CAAC,MAAM,6BAA6B,GAAG;IAC3C,GAAG,EAAE,CAAC,GAAG,QAAQ,EAAE,sBAAsB,CAAU;IACnD,MAAM,EAAE,CACN,YAA2B,EAC3B,MAA8B,EAC9B,EAAE,CAAC,CAAC,GAAG,QAAQ,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,CAAU;CAC1E,CAAC","sourcesContent":["import { BASE_KEY } from '@src/data/queryKeys';\n\n/**\n * The main gradebook rows. The base key is a prefix of each paged key (see\n * `useGrades`), so invalidating `byCourse(courseId)` refetches any page.\n */\nexport const gradesQueryKeys = {\n all: BASE_KEY,\n byCourse: (courseId: string) => [...BASE_KEY, courseId, 'grades'] as const,\n};\n\n/** Grade-override history for one learner on one subsection (edit modal). */\nexport const gradeOverrideHistoryQueryKeys = {\n all: [...BASE_KEY, 'gradeOverrideHistory'] as const,\n byCell: (\n subsectionId: string | null,\n userId: string | number | null,\n ) => [...BASE_KEY, 'gradeOverrideHistory', subsectionId, userId] as const,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"queryKeys.js","sourceRoot":"","sources":["../../../../src/components/GradesView/data/queryKeys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE/C;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,GAAG,EAAE,QAAQ;IACb,QAAQ,EAAE,CAAC,QAAgB,EAAE,EAAE,CAAC,CAAC,GAAG,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAU;CAC3E,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,YAAY,EAAE,CAAC,GAAG,QAAQ,EAAE,cAAc,CAAU;IACpD,cAAc,EAAE,CAAC,GAAG,QAAQ,EAAE,gBAAgB,CAAU;CACzD,CAAC;AAEF,6EAA6E;AAC7E,MAAM,CAAC,MAAM,6BAA6B,GAAG;IAC3C,GAAG,EAAE,CAAC,GAAG,QAAQ,EAAE,sBAAsB,CAAU;IACnD,MAAM,EAAE,CACN,YAA2B,EAC3B,MAA8B,EAC9B,EAAE,CAAC,CAAC,GAAG,QAAQ,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,CAAU;CAC1E,CAAC","sourcesContent":["import { BASE_KEY } from '@src/data/queryKeys';\n\n/**\n * The main gradebook rows. The base key is a prefix of each paged key (see\n * `useGrades`), so invalidating `byCourse(courseId)` refetches any page.\n */\nexport const gradesQueryKeys = {\n all: BASE_KEY,\n byCourse: (courseId: string) => [...BASE_KEY, courseId, 'grades'] as const,\n};\n\nexport const gradeMutationKeys = {\n updateGrades: [...BASE_KEY, 'updateGrades'] as const,\n uploadGradeCsv: [...BASE_KEY, 'uploadGradeCsv'] as const,\n};\n\n/** Grade-override history for one learner on one subsection (edit modal). */\nexport const gradeOverrideHistoryQueryKeys = {\n all: [...BASE_KEY, 'gradeOverrideHistory'] as const,\n byCell: (\n subsectionId: string | null,\n userId: string | number | null,\n ) => [...BASE_KEY, 'gradeOverrideHistory', subsectionId, userId] as const,\n};\n"]}
|
package/dist/data/apiHook.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useQuery } from '@tanstack/react-query';
|
|
2
1
|
import { getAssignmentTypes, getCanUserViewGradebook } from './api';
|
|
3
2
|
import { useCourseId } from './courseIdContext';
|
|
3
|
+
import { useGradebookQuery } from './query';
|
|
4
4
|
import { assignmentTypesQueryKeys, rolesQueryKeys } from './queryKeys';
|
|
5
5
|
/**
|
|
6
6
|
* useCanUserViewGradebook()
|
|
@@ -10,7 +10,7 @@ import { assignmentTypesQueryKeys, rolesQueryKeys } from './queryKeys';
|
|
|
10
10
|
*/
|
|
11
11
|
export const useCanUserViewGradebook = () => {
|
|
12
12
|
const courseId = useCourseId();
|
|
13
|
-
return
|
|
13
|
+
return useGradebookQuery({
|
|
14
14
|
queryKey: rolesQueryKeys.byCourse(courseId),
|
|
15
15
|
queryFn: () => getCanUserViewGradebook(courseId),
|
|
16
16
|
enabled: !!courseId,
|
|
@@ -22,7 +22,7 @@ export const useCanUserViewGradebook = () => {
|
|
|
22
22
|
* bulk-management flags). Caller supplies `courseId` and decides when it may
|
|
23
23
|
* run via `enabled`.
|
|
24
24
|
*/
|
|
25
|
-
export const useAssignmentTypes = (courseId, { enabled = true } = {}) =>
|
|
25
|
+
export const useAssignmentTypes = (courseId, { enabled = true } = {}) => useGradebookQuery({
|
|
26
26
|
queryKey: assignmentTypesQueryKeys.byCourse(courseId),
|
|
27
27
|
queryFn: () => getAssignmentTypes(courseId),
|
|
28
28
|
enabled: !!courseId && enabled,
|
package/dist/data/apiHook.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiHook.js","sourceRoot":"","sources":["../../src/data/apiHook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"apiHook.js","sourceRoot":"","sources":["../../src/data/apiHook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,OAAO,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC5C,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,iBAAiB,CAAC;QACvB,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,iBAAiB,CAAC;IACrB,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 { getAssignmentTypes, getCanUserViewGradebook } from './api';\nimport { useCourseId } from './courseIdContext';\nimport { useGradebookQuery } from './query';\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 useGradebookQuery({\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) => useGradebookQuery({\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,14 @@
|
|
|
1
|
+
import { DefaultError, UseQueryOptions, UseQueryResult } from '@tanstack/react-query';
|
|
2
|
+
export declare const retryUnlessClientError: (failureCount: number, error: unknown) => boolean;
|
|
3
|
+
export declare const gradebookQueryOptions: {
|
|
4
|
+
readonly staleTime: number;
|
|
5
|
+
readonly refetchOnWindowFocus: false;
|
|
6
|
+
readonly retry: (failureCount: number, error: unknown) => boolean;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* These can't be defaults on a QueryClient: app providers wrap the whole site,
|
|
10
|
+
* so an app-owned client would impose them on every other app sharing it. The
|
|
11
|
+
* shell's client sets no defaults, so a query that skips this wrapper silently
|
|
12
|
+
* falls back to stock React Query behavior.
|
|
13
|
+
*/
|
|
14
|
+
export declare function useGradebookQuery<TQueryFnData, TError = DefaultError, TData = TQueryFnData>(options: UseQueryOptions<TQueryFnData, TError, TData>): UseQueryResult<TData, TError>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useQuery, } from '@tanstack/react-query';
|
|
2
|
+
const isClientError = (error) => {
|
|
3
|
+
var _a;
|
|
4
|
+
const status = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status;
|
|
5
|
+
return typeof status === 'number' && status >= 400 && status < 500;
|
|
6
|
+
};
|
|
7
|
+
export const retryUnlessClientError = (failureCount, error) => {
|
|
8
|
+
if (isClientError(error)) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
return failureCount < 3;
|
|
12
|
+
};
|
|
13
|
+
export const gradebookQueryOptions = {
|
|
14
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
15
|
+
refetchOnWindowFocus: false,
|
|
16
|
+
retry: retryUnlessClientError,
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* These can't be defaults on a QueryClient: app providers wrap the whole site,
|
|
20
|
+
* so an app-owned client would impose them on every other app sharing it. The
|
|
21
|
+
* shell's client sets no defaults, so a query that skips this wrapper silently
|
|
22
|
+
* falls back to stock React Query behavior.
|
|
23
|
+
*/
|
|
24
|
+
export function useGradebookQuery(options) {
|
|
25
|
+
return useQuery(Object.assign(Object.assign({}, gradebookQueryOptions), options));
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/data/query.ts"],"names":[],"mappings":"AAAA,OAAO,EACS,QAAQ,GACvB,MAAM,uBAAuB,CAAC;AAO/B,MAAM,aAAa,GAAG,CAAC,KAAc,EAAW,EAAE;;IAChD,MAAM,MAAM,GAAG,MAAC,KAAuB,aAAvB,KAAK,uBAAL,KAAK,CAAoB,QAAQ,0CAAE,MAAM,CAAC;IAC1D,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG,CAAC;AACrE,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,YAAoB,EAAE,KAAc,EAAW,EAAE;IACtF,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,YAAY,GAAG,CAAC,CAAC;AAC1B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,EAAE,YAAY;IACtC,oBAAoB,EAAE,KAAK;IAC3B,KAAK,EAAE,sBAAsB;CACrB,CAAC;AAEX;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAqD;IAErD,OAAO,QAAQ,iCAAmC,qBAAqB,GAAK,OAAO,EAAG,CAAC;AACzF,CAAC","sourcesContent":["import {\n DefaultError, useQuery, UseQueryOptions, UseQueryResult,\n} from '@tanstack/react-query';\n\n/** Subset of an Axios-style error we inspect, to avoid depending on axios types. */\ninterface HttpErrorLike {\n response?: { status?: number };\n}\n\nconst isClientError = (error: unknown): boolean => {\n const status = (error as HttpErrorLike)?.response?.status;\n return typeof status === 'number' && status >= 400 && status < 500;\n};\n\nexport const retryUnlessClientError = (failureCount: number, error: unknown): boolean => {\n if (isClientError(error)) {\n return false;\n }\n return failureCount < 3;\n};\n\nexport const gradebookQueryOptions = {\n staleTime: 5 * 60 * 1000, // 5 minutes\n refetchOnWindowFocus: false,\n retry: retryUnlessClientError,\n} as const;\n\n/**\n * These can't be defaults on a QueryClient: app providers wrap the whole site,\n * so an app-owned client would impose them on every other app sharing it. The\n * shell's client sets no defaults, so a query that skips this wrapper silently\n * falls back to stock React Query behavior.\n */\nexport function useGradebookQuery<TQueryFnData, TError = DefaultError, TData = TQueryFnData>(\n options: UseQueryOptions<TQueryFnData, TError, TData>,\n): UseQueryResult<TData, TError> {\n return useQuery<TQueryFnData, TError, TData>({ ...gradebookQueryOptions, ...options });\n}\n"]}
|
package/dist/data/queryKeys.js
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
// caches independently per course and mutations can invalidate a whole course
|
|
8
8
|
// subtree via a broad prefix. The array shapes match the legacy
|
|
9
9
|
// `gradebookQueryKeys` so cache identity / invalidation is preserved.
|
|
10
|
+
//
|
|
11
|
+
// Mutations are keyed under `BASE_KEY` too: the client is shared site-wide, so
|
|
12
|
+
// `useShouldShowSpinner` counts by this prefix and a keyless mutation is missed.
|
|
10
13
|
export const BASE_KEY = ['gradebook'];
|
|
11
14
|
/** Roles-based "can view the gradebook" permission gate (the fetch-cascade root). */
|
|
12
15
|
export const rolesQueryKeys = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queryKeys.js","sourceRoot":"","sources":["../../src/data/queryKeys.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,8EAA8E;AAC9E,kFAAkF;AAClF,sDAAsD;AACtD,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,gEAAgE;AAChE,sEAAsE;AACtE,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,WAAW,CAAU,CAAC;AAE/C,qFAAqF;AACrF,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,GAAG,EAAE,QAAQ;IACb,QAAQ,EAAE,CAAC,QAAgB,EAAE,EAAE,CAAC,CAAC,GAAG,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAU;CAC1E,CAAC;AAEF,kFAAkF;AAClF,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,GAAG,EAAE,QAAQ;IACb,QAAQ,EAAE,CAAC,QAAgB,EAAE,EAAE,CAAC,CAAC,GAAG,QAAQ,EAAE,QAAQ,EAAE,iBAAiB,CAAU;CACpF,CAAC","sourcesContent":["// Top-level (app-wide) query-key factories, mirroring the instructor dashboard's\n// `src/data/queryKeys.ts`: shared server-state that multiple features gate on\n// lives here (the roles permission gate + the assignment-types/config bundle used\n// by the header, filters, and bulk-management views).\n//\n// Keys are namespaced under `BASE_KEY` and scoped by `courseId` so React Query\n// caches independently per course and mutations can invalidate a whole course\n// subtree via a broad prefix. The array shapes match the legacy\n// `gradebookQueryKeys` so cache identity / invalidation is preserved.\nexport const BASE_KEY = ['gradebook'] as const;\n\n/** Roles-based \"can view the gradebook\" permission gate (the fetch-cascade root). */\nexport const rolesQueryKeys = {\n all: BASE_KEY,\n byCourse: (courseId: string) => [...BASE_KEY, courseId, 'roles'] as const,\n};\n\n/** Assignment types + grades-frozen / bulk-management flags (app-wide config). */\nexport const assignmentTypesQueryKeys = {\n all: BASE_KEY,\n byCourse: (courseId: string) => [...BASE_KEY, courseId, 'assignmentTypes'] as const,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"queryKeys.js","sourceRoot":"","sources":["../../src/data/queryKeys.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,8EAA8E;AAC9E,kFAAkF;AAClF,sDAAsD;AACtD,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,gEAAgE;AAChE,sEAAsE;AACtE,EAAE;AACF,+EAA+E;AAC/E,iFAAiF;AACjF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,WAAW,CAAU,CAAC;AAE/C,qFAAqF;AACrF,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,GAAG,EAAE,QAAQ;IACb,QAAQ,EAAE,CAAC,QAAgB,EAAE,EAAE,CAAC,CAAC,GAAG,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAU;CAC1E,CAAC;AAEF,kFAAkF;AAClF,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,GAAG,EAAE,QAAQ;IACb,QAAQ,EAAE,CAAC,QAAgB,EAAE,EAAE,CAAC,CAAC,GAAG,QAAQ,EAAE,QAAQ,EAAE,iBAAiB,CAAU;CACpF,CAAC","sourcesContent":["// Top-level (app-wide) query-key factories, mirroring the instructor dashboard's\n// `src/data/queryKeys.ts`: shared server-state that multiple features gate on\n// lives here (the roles permission gate + the assignment-types/config bundle used\n// by the header, filters, and bulk-management views).\n//\n// Keys are namespaced under `BASE_KEY` and scoped by `courseId` so React Query\n// caches independently per course and mutations can invalidate a whole course\n// subtree via a broad prefix. The array shapes match the legacy\n// `gradebookQueryKeys` so cache identity / invalidation is preserved.\n//\n// Mutations are keyed under `BASE_KEY` too: the client is shared site-wide, so\n// `useShouldShowSpinner` counts by this prefix and a keyless mutation is missed.\nexport const BASE_KEY = ['gradebook'] as const;\n\n/** Roles-based \"can view the gradebook\" permission gate (the fetch-cascade root). */\nexport const rolesQueryKeys = {\n all: BASE_KEY,\n byCourse: (courseId: string) => [...BASE_KEY, courseId, 'roles'] as const,\n};\n\n/** Assignment types + grades-frozen / bulk-management flags (app-wide config). */\nexport const assignmentTypesQueryKeys = {\n all: BASE_KEY,\n byCourse: (courseId: string) => [...BASE_KEY, courseId, 'assignmentTypes'] as const,\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { QueryClient } from '@tanstack/react-query';
|
|
2
|
-
/**
|
|
3
|
-
* Application-wide React Query client.
|
|
4
|
-
*
|
|
5
|
-
* Defaults are promoted here (rather than repeated per hook) to keep individual
|
|
6
|
-
* query hooks lean: cache server data for 5 minutes and skip retries on 4xx.
|
|
7
|
-
*/
|
|
8
|
-
export declare const makeQueryClient: () => QueryClient;
|
|
9
|
-
declare const queryClient: QueryClient;
|
|
10
|
-
export default queryClient;
|
package/dist/data/queryClient.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { QueryClient } from '@tanstack/react-query';
|
|
2
|
-
/**
|
|
3
|
-
* isClientError(error)
|
|
4
|
-
* True for 4xx responses, which are not worth retrying (bad request, auth,
|
|
5
|
-
* not found, etc.). Server (5xx) and network errors remain retryable.
|
|
6
|
-
*/
|
|
7
|
-
const isClientError = (error) => {
|
|
8
|
-
var _a;
|
|
9
|
-
const status = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status;
|
|
10
|
-
return typeof status === 'number' && status >= 400 && status < 500;
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* Application-wide React Query client.
|
|
14
|
-
*
|
|
15
|
-
* Defaults are promoted here (rather than repeated per hook) to keep individual
|
|
16
|
-
* query hooks lean: cache server data for 5 minutes and skip retries on 4xx.
|
|
17
|
-
*/
|
|
18
|
-
export const makeQueryClient = () => new QueryClient({
|
|
19
|
-
defaultOptions: {
|
|
20
|
-
queries: {
|
|
21
|
-
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
22
|
-
refetchOnWindowFocus: false,
|
|
23
|
-
retry: (failureCount, error) => {
|
|
24
|
-
if (isClientError(error)) {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
return failureCount < 3;
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
const queryClient = makeQueryClient();
|
|
33
|
-
export default queryClient;
|
|
34
|
-
//# sourceMappingURL=queryClient.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"queryClient.js","sourceRoot":"","sources":["../../src/data/queryClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAUpD;;;;GAIG;AACH,MAAM,aAAa,GAAG,CAAC,KAAc,EAAW,EAAE;;IAChD,MAAM,MAAM,GAAG,MAAC,KAAuB,aAAvB,KAAK,uBAAL,KAAK,CAAoB,QAAQ,0CAAE,MAAM,CAAC;IAC1D,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG,CAAC;AACrE,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAgB,EAAE,CAAC,IAAI,WAAW,CAAC;IAChE,cAAc,EAAE;QACd,OAAO,EAAE;YACP,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,EAAE,YAAY;YACtC,oBAAoB,EAAE,KAAK;YAC3B,KAAK,EAAE,CAAC,YAAoB,EAAE,KAAc,EAAE,EAAE;gBAC9C,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;oBACzB,OAAO,KAAK,CAAC;gBACf,CAAC;gBACD,OAAO,YAAY,GAAG,CAAC,CAAC;YAC1B,CAAC;SACF;KACF;CACF,CAAC,CAAC;AAEH,MAAM,WAAW,GAAG,eAAe,EAAE,CAAC;AAEtC,eAAe,WAAW,CAAC","sourcesContent":["import { QueryClient } from '@tanstack/react-query';\n\n/**\n * Shape of the subset of an Axios-style error we inspect to decide retries.\n * We only need the HTTP status, so we avoid a hard dependency on axios types.\n */\ninterface HttpErrorLike {\n response?: { status?: number };\n}\n\n/**\n * isClientError(error)\n * True for 4xx responses, which are not worth retrying (bad request, auth,\n * not found, etc.). Server (5xx) and network errors remain retryable.\n */\nconst isClientError = (error: unknown): boolean => {\n const status = (error as HttpErrorLike)?.response?.status;\n return typeof status === 'number' && status >= 400 && status < 500;\n};\n\n/**\n * Application-wide React Query client.\n *\n * Defaults are promoted here (rather than repeated per hook) to keep individual\n * query hooks lean: cache server data for 5 minutes and skip retries on 4xx.\n */\nexport const makeQueryClient = (): QueryClient => new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 5 * 60 * 1000, // 5 minutes\n refetchOnWindowFocus: false,\n retry: (failureCount: number, error: unknown) => {\n if (isClientError(error)) {\n return false;\n }\n return failureCount < 3;\n },\n },\n },\n});\n\nconst queryClient = makeQueryClient();\n\nexport default queryClient;\n"]}
|
package/dist/providers.d.ts
DELETED
package/dist/providers.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { QueryClientProvider } from '@tanstack/react-query';
|
|
3
|
-
import queryClient from './data/queryClient';
|
|
4
|
-
/**
|
|
5
|
-
* Wraps the app subtree in the configured query client (5-minute staleTime, no
|
|
6
|
-
* refetch on focus, no retries on 4xx — see `src/data/queryClient.ts`),
|
|
7
|
-
* shadowing the shell's default client.
|
|
8
|
-
*/
|
|
9
|
-
// eslint-disable-next-line react/prop-types
|
|
10
|
-
const QueryProvider = ({ children }) => (_jsx(QueryClientProvider, { client: queryClient, children: children }));
|
|
11
|
-
const providers = [QueryProvider];
|
|
12
|
-
export default providers;
|
|
13
|
-
//# sourceMappingURL=providers.js.map
|
package/dist/providers.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../src/providers.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAG5D,OAAO,WAAW,MAAM,uBAAuB,CAAC;AAEhD;;;;GAIG;AACH,4CAA4C;AAC5C,MAAM,aAAa,GAAgB,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CACnD,KAAC,mBAAmB,IAAC,MAAM,EAAE,WAAW,YAAG,QAAQ,GAAuB,CAC3E,CAAC;AAEF,MAAM,SAAS,GAAkB,CAAC,aAAa,CAAC,CAAC;AAEjD,eAAe,SAAS,CAAC","sourcesContent":["import { QueryClientProvider } from '@tanstack/react-query';\nimport { AppProvider } from '@openedx/frontend-base';\n\nimport queryClient from '@src/data/queryClient';\n\n/**\n * Wraps the app subtree in the configured query client (5-minute staleTime, no\n * refetch on focus, no retries on 4xx — see `src/data/queryClient.ts`),\n * shadowing the shell's default client.\n */\n// eslint-disable-next-line react/prop-types\nconst QueryProvider: AppProvider = ({ children }) => (\n <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>\n);\n\nconst providers: AppProvider[] = [QueryProvider];\n\nexport default providers;\n"]}
|