Skip to content

Commit df98f8a

Browse files
committed
refactor: configure api and fix lint issues
1 parent cada6a7 commit df98f8a

6 files changed

Lines changed: 85 additions & 56 deletions

File tree

src/course-outline/data/api.ts

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ import {
1414

1515
const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL;
1616

17+
const pickDefined = <T extends Record<string, any>>(obj: T) => Object.fromEntries(
18+
Object.entries(obj).filter(([, value]) => value !== undefined),
19+
);
20+
1721
export const getCourseOutlineIndexApiUrl = (
1822
courseId: string,
1923
) => `${getApiBaseUrl()}/api/contentstore/v1/course_index/${courseId}`;
@@ -239,30 +243,58 @@ export async function configureCourseSection(variables: ConfigureSectionData): P
239243
/**
240244
* Configure course subsection
241245
*/
242-
export async function configureCourseSubsection(variables: Partial<ConfigureSubsectionData> & Pick<ConfigureSubsectionData, 'itemId'>): Promise<object> {
246+
export async function configureCourseSubsection(
247+
variables: Partial<ConfigureSubsectionData> & Pick<ConfigureSubsectionData, 'itemId'>,
248+
): Promise<object> {
249+
const {
250+
itemId,
251+
isVisibleToStaffOnly,
252+
dueDate,
253+
hideAfterDue,
254+
showCorrectness,
255+
isPracticeExam,
256+
isTimeLimited,
257+
isProctoredExam,
258+
isOnboardingExam,
259+
examReviewRules,
260+
defaultTimeLimitMinutes,
261+
releaseDate,
262+
graderType,
263+
isPrereq,
264+
prereqUsageKey,
265+
prereqMinScore,
266+
prereqMinCompletion,
267+
} = variables;
268+
269+
const metadata = pickDefined({
270+
visible_to_staff_only: isVisibleToStaffOnly === undefined ? undefined : isVisibleToStaffOnly ? true : null,
271+
due: dueDate,
272+
hide_after_due: hideAfterDue,
273+
show_correctness: showCorrectness,
274+
is_practice_exam: isPracticeExam,
275+
is_time_limited: isTimeLimited,
276+
is_proctored_enabled: (isProctoredExam !== undefined || isPracticeExam !== undefined || isOnboardingExam !== undefined)
277+
? (isProctoredExam || isPracticeExam || isOnboardingExam)
278+
: undefined,
279+
exam_review_rules: examReviewRules,
280+
default_time_limit_minutes: defaultTimeLimitMinutes,
281+
is_onboarding_exam: isOnboardingExam,
282+
start: releaseDate,
283+
});
284+
285+
const body = pickDefined({
286+
publish: 'republish',
287+
graderType,
288+
isPrereq,
289+
prereqUsageKey,
290+
prereqMinScore,
291+
prereqMinCompletion,
292+
metadata,
293+
});
294+
243295
const { data } = await getAuthenticatedHttpClient()
244-
.post(getCourseItemApiUrl(variables.itemId), {
245-
publish: 'republish',
246-
...(variables.graderType !== undefined && { graderType: variables.graderType }),
247-
...(variables.isPrereq !== undefined && { isPrereq: variables.isPrereq }),
248-
...(variables.prereqUsageKey !== undefined && { prereqUsageKey: variables.prereqUsageKey }),
249-
...(variables.prereqMinScore !== undefined && { prereqMinScore: variables.prereqMinScore }),
250-
...(variables.prereqMinCompletion !== undefined && { prereqMinCompletion: variables.prereqMinCompletion }),
251-
metadata: {
252-
// The backend expects metadata.visible_to_staff_only to either true or null
253-
...(variables.isVisibleToStaffOnly !== undefined && { visible_to_staff_only: variables.isVisibleToStaffOnly ? true : null }),
254-
...(variables.dueDate !== undefined && { due: variables.dueDate }),
255-
...(variables.hideAfterDue !== undefined && { hide_after_due: variables.hideAfterDue }),
256-
...(variables.showCorrectness !== undefined && { show_correctness: variables.showCorrectness }),
257-
...(variables.isPracticeExam !== undefined && { is_practice_exam: variables.isPracticeExam }),
258-
...(variables.isTimeLimited !== undefined && { is_time_limited: variables.isTimeLimited }),
259-
...(variables.isProctoredExam !== undefined || variables.isPracticeExam !== undefined || variables.isOnboardingExam !== undefined ? { is_proctored_enabled: variables.isProctoredExam || variables.isPracticeExam || variables.isOnboardingExam } : {}),
260-
...(variables.examReviewRules !== undefined && { exam_review_rules: variables.examReviewRules }),
261-
...(variables.defaultTimeLimitMinutes !== undefined && { default_time_limit_minutes: variables.defaultTimeLimitMinutes }),
262-
...(variables.isOnboardingExam !== undefined && { is_onboarding_exam: variables.isOnboardingExam }),
263-
...(variables.releaseDate !== undefined && { start: variables.releaseDate }),
264-
},
265-
});
296+
.post(getCourseItemApiUrl(itemId), body);
297+
266298
return data;
267299
}
268300

src/course-outline/data/apiHooks.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import {
99
import { getNotificationMessage } from '@src/course-unit/data/utils';
1010
import { createGlobalState } from '@src/data/apiHooks';
1111
import type { XBlockBase, XblockChildInfo } from '@src/data/types';
12-
import { ContainerType, getBlockType, getCourseKey, normalizeContainerType } from '@src/generic/key-utils';
12+
import {
13+
ContainerType, getBlockType, getCourseKey, normalizeContainerType,
14+
} from '@src/generic/key-utils';
1315
import { useMutationWithProcessingNotification } from '@src/generic/processing-notification/data/apiHooks';
1416
import { handleResponseErrors } from '@src/generic/saving-error-alert';
1517
import { useToastContext } from '@src/generic/toast-context';
@@ -244,7 +246,7 @@ export const useConfigureSubsection = () => {
244246
const queryClient = useQueryClient();
245247
return useMutationWithProcessingNotification({
246248
mutationFn: (
247-
variables: Partial<ConfigureSubsectionData> & Pick<ConfigureSubsectionData, 'itemId'> & ParentIds
249+
variables: Partial<ConfigureSubsectionData> & Pick<ConfigureSubsectionData, 'itemId'> & ParentIds,
248250
) => configureCourseSubsection(variables),
249251
onSettled: async (_data, _err, variables) => {
250252
const courseKey = getCourseKey(variables.itemId);
@@ -253,16 +255,18 @@ export const useConfigureSubsection = () => {
253255
if (variables.isPrereq !== undefined) {
254256
const subsectionItemQueries = queryClient.getQueryCache().findAll({
255257
predicate: (query) => {
256-
const queryKey = query.queryKey;
258+
const { queryKey } = query;
257259
return Array.isArray(queryKey)
258260
&& queryKey.length >= 3
259261
&& queryKey[0] === courseOutlineQueryKeys.all[0]
260262
&& queryKey[1] === courseKey
261263
&& typeof queryKey[2] === 'string'
262-
&& normalizeContainerType(getBlockType(queryKey[2], 'empty')) === ContainerType.Subsection
264+
&& normalizeContainerType(getBlockType(queryKey[2], 'empty')) === ContainerType.Subsection;
263265
},
264266
});
265-
await Promise.all(subsectionItemQueries.map((query) => queryClient.invalidateQueries({ queryKey: query.queryKey })));
267+
await Promise.all(subsectionItemQueries.map((query) => queryClient.invalidateQueries({
268+
queryKey: query.queryKey,
269+
})));
266270
}
267271
},
268272
});

src/course-outline/outline-sidebar/info-sidebar/SubsectionSettings.tsx

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -165,28 +165,26 @@ const SpecialExamSection = ({ subsectionId, onChange }: SubProps) => {
165165
const { data: itemData } = useCourseItemData(subsectionId);
166166
const enableTimedExams = useSelector(getTimedExamsFlag);
167167
const enableProctoredExams = useSelector(getProctoredExamsFlag);
168-
const getLatestLocalState = useCallback(() => {
169-
return {
170-
isProctoredExam: itemData?.isProctoredExam,
171-
isTimeLimited: itemData?.isTimeLimited,
172-
isOnboardingExam: itemData?.isOnboardingExam,
173-
isPracticeExam: itemData?.isPracticeExam,
174-
defaultTimeLimitMinutes: itemData?.defaultTimeLimitMinutes,
175-
examReviewRules: itemData?.examReviewRules,
176-
isPrereq: itemData?.isPrereq,
177-
prereqMinScore: defaultPrereqScore(itemData?.prereqMinScore),
178-
prereqMinCompletion: defaultPrereqScore(itemData?.prereqMinCompletion),
179-
prereqUsageKey: itemData?.prereq,
180-
};
181-
}, [itemData]);
168+
const getLatestLocalState = useCallback(() => ({
169+
isProctoredExam: itemData?.isProctoredExam,
170+
isTimeLimited: itemData?.isTimeLimited,
171+
isOnboardingExam: itemData?.isOnboardingExam,
172+
isPracticeExam: itemData?.isPracticeExam,
173+
defaultTimeLimitMinutes: itemData?.defaultTimeLimitMinutes,
174+
examReviewRules: itemData?.examReviewRules,
175+
isPrereq: itemData?.isPrereq,
176+
prereqMinScore: defaultPrereqScore(itemData?.prereqMinScore),
177+
prereqMinCompletion: defaultPrereqScore(itemData?.prereqMinCompletion),
178+
prereqUsageKey: itemData?.prereq,
179+
}), [itemData]);
182180

183181
const [localState, setLocalState] = useStateWithCallback<Partial<ConfigureSubsectionData>>(
184182
getLatestLocalState(),
185-
(val) => onChange(val || {})
183+
(val) => onChange(val || {}),
186184
);
187185

188186
useEffect(() => {
189-
if (!itemData) return;
187+
if (!itemData) { return; }
190188
setLocalState({ value: getLatestLocalState(), skipCallback: true });
191189
}, [itemData]);
192190

src/generic/FormikControl.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import { Form } from '@openedx/paragon';
3-
import { FormikContextType, getIn, useFormikContext } from 'formik';
3+
import { getIn, useFormikContext } from 'formik';
44
import FormikErrorFeedback from './FormikErrorFeedback';
55

66
interface Props {
@@ -26,17 +26,12 @@ const FormikControl: React.FC<Props & React.ComponentProps<typeof Form.Control>>
2626
setFieldValue,
2727
...params
2828
}) => {
29-
let formikContext: FormikContextType<unknown> | null;
30-
try {
31-
formikContext = useFormikContext();
32-
} catch (e) {
33-
formikContext = null;
34-
}
29+
const formikContext = useFormikContext() || null;
3530

3631
const fieldTouched = formikContext ? getIn(formikContext.touched, name) : false;
3732
const fieldError = formikContext ? getIn(formikContext.errors, name) : undefined;
3833
const handleFocus = formikContext ? (
39-
e: { target: { name: any; } }
34+
e: { target: { name: any; } },
4035
) => formikContext?.setFieldError(e.target.name, undefined) : undefined;
4136
const handleBlur = formikContext ? formikContext.handleBlur : undefined;
4237
const handleChange = formikContext ? formikContext.handleChange : undefined;
@@ -49,7 +44,7 @@ const FormikControl: React.FC<Props & React.ComponentProps<typeof Form.Control>>
4944
{...params}
5045
name={name}
5146
className={controlClasses}
52-
onChange={(e: { target: { value: any; }; }) => {
47+
onChange={async (e: { target: { value: any; }; }) => {
5348
if (setFieldValue) {
5449
setFieldValue(name, e.target.value);
5550
return;
@@ -59,7 +54,7 @@ const FormikControl: React.FC<Props & React.ComponentProps<typeof Form.Control>>
5954
return;
6055
}
6156
if (formikSetFieldValue) {
62-
formikSetFieldValue(name, e.target.value);
57+
await formikSetFieldValue(name, e.target.value);
6358
}
6459
}}
6560
onBlur={handleBlur}

src/generic/key-utils.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ describe('component utils', () => {
3333
for (const input of ['', undefined, null, 'not a key', 'lb:foo', 'block-v1:foo']) {
3434
it(`throws an exception for usage key '${input}'`, () => {
3535
expect(() => getBlockType(input as any)).toThrow(`Invalid usageKey: ${input}`);
36-
expect(getBlockType(input as any, 'empty')).toBe('');
36+
expect(getBlockType(input as any, 'empty')).toBe('');
3737
});
3838
}
3939
});

src/taxonomy/tree-table/NestedRows.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import { IntlProvider } from '@edx/frontend-platform/i18n';
3-
import { fireEvent, render, screen, within } from '@testing-library/react';
3+
import { fireEvent, render, screen } from '@testing-library/react';
44

55
import NestedRows from './NestedRows';
66

0 commit comments

Comments
 (0)