From 87cc44274e9123edd47e72badb0bacd485d069c6 Mon Sep 17 00:00:00 2001 From: "kshitij.sobti" Date: Fri, 23 Aug 2024 19:21:52 +0530 Subject: [PATCH 1/2] feat: Use configured DEFAULT_GRADE_DESIGNATIONS Support was recently added to edx-platform to add customisised default grade designations, this change adds support for them to this MFE as well to bring it to partiy with the edx-platform UI It also refactors the grading-settings page to use React Query and updates the logic used when partitioning grades by default to make it work better when there are more than 5 partitions. --- src/grading-settings/GradingSettings.jsx | 101 +++++++-------- src/grading-settings/GradingSettings.test.jsx | 116 +++++++++++------- src/grading-settings/data/apiHooks.ts | 26 ++++ src/grading-settings/data/selectors.js | 13 -- src/grading-settings/data/slice.js | 43 ------- src/grading-settings/data/thunks.js | 55 --------- .../grading-scale/GradingScale.jsx | 40 ++++-- .../grading-scale/GradingScale.scss | 2 +- .../components/GradingScaleSegment.jsx | 79 ------------ .../components/GradingScaleSegment.tsx | 86 +++++++++++++ src/store.js | 2 - 11 files changed, 263 insertions(+), 300 deletions(-) create mode 100644 src/grading-settings/data/apiHooks.ts delete mode 100644 src/grading-settings/data/selectors.js delete mode 100644 src/grading-settings/data/slice.js delete mode 100644 src/grading-settings/data/thunks.js delete mode 100644 src/grading-settings/grading-scale/components/GradingScaleSegment.jsx create mode 100644 src/grading-settings/grading-scale/components/GradingScaleSegment.tsx diff --git a/src/grading-settings/GradingSettings.jsx b/src/grading-settings/GradingSettings.jsx index 79234b603e..cda1a6278c 100644 --- a/src/grading-settings/GradingSettings.jsx +++ b/src/grading-settings/GradingSettings.jsx @@ -1,51 +1,59 @@ -import React, { useEffect, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import PropTypes from 'prop-types'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@edx/frontend-platform/i18n'; import { - Container, Layout, Button, StatefulButton, + Button, Container, Layout, StatefulButton, } from '@openedx/paragon'; -import { CheckCircle, Warning, Add as IconAdd } from '@openedx/paragon/icons'; - -import { useModel } from '../generic/model-store'; +import { Add as IconAdd, CheckCircle, Warning } from '@openedx/paragon/icons'; +import { + useCourseSettings, + useGradingSettings, + useGradingSettingUpdater, +} from 'CourseAuthoring/grading-settings/data/apiHooks'; +import PropTypes from 'prop-types'; +import React, { useEffect, useState } from 'react'; +import { Helmet } from 'react-helmet'; +import { STATEFUL_BUTTON_STATES } from '../constants'; import AlertMessage from '../generic/alert-message'; -import { RequestStatus } from '../data/constants'; import InternetConnectionAlert from '../generic/internet-connection-alert'; -import SubHeader from '../generic/sub-header/SubHeader'; + +import { useModel } from '../generic/model-store'; import SectionSubHeader from '../generic/section-sub-header'; -import { STATEFUL_BUTTON_STATES } from '../constants'; -import { - getGradingSettings, - getCourseAssignmentLists, - getSavingStatus, - getLoadingStatus, - getCourseSettings, -} from './data/selectors'; -import { fetchGradingSettings, sendGradingSetting, fetchCourseSettingsQuery } from './data/thunks'; -import GradingScale from './grading-scale/GradingScale'; -import GradingSidebar from './grading-sidebar'; -import messages from './messages'; +import SubHeader from '../generic/sub-header/SubHeader'; +import getPageHeadTitle from '../generic/utils'; import AssignmentSection from './assignment-section'; import CreditSection from './credit-section'; import DeadlineSection from './deadline-section'; +import GradingScale from './grading-scale/GradingScale'; +import GradingSidebar from './grading-sidebar'; import { useConvertGradeCutoffs, useUpdateGradingData } from './hooks'; -import getPageHeadTitle from '../generic/utils'; +import messages from './messages'; + +const GradingSettings = ({ courseId }) => { + const intl = useIntl(); + const { + data: gradingSettings, + isLoading: isGradingSettingsLoading, + } = useGradingSettings(courseId); + const { + data: courseSettingsData, + isLoading: isCourseSettingsLoading, + } = useCourseSettings(courseId); + const { + mutate: updateGradingSettings, + isLoading: savePending, + isSuccess: savingStatus, + isError: savingFailed, + } = useGradingSettingUpdater(courseId); + + const courseAssignmentLists = gradingSettings?.courseAssignmentLists; + const courseGradingDetails = gradingSettings?.courseDetails; -const GradingSettings = ({ intl, courseId }) => { - const gradingSettingsData = useSelector(getGradingSettings); - const courseSettingsData = useSelector(getCourseSettings); - const courseAssignmentLists = useSelector(getCourseAssignmentLists); - const savingStatus = useSelector(getSavingStatus); - const loadingStatus = useSelector(getLoadingStatus); const [showSuccessAlert, setShowSuccessAlert] = useState(false); - const dispatch = useDispatch(); - const isLoading = loadingStatus === RequestStatus.IN_PROGRESS; + const isLoading = isCourseSettingsLoading || isGradingSettingsLoading; const [isQueryPending, setIsQueryPending] = useState(false); const [showOverrideInternetConnectionAlert, setOverrideInternetConnectionAlert] = useState(false); const [eligibleGrade, setEligibleGrade] = useState(null); - const courseDetails = useModel('courseDetails', courseId); - document.title = getPageHeadTitle(courseDetails?.name, intl.formatMessage(messages.headingTitle)); + const courseName = useModel('courseDetails', courseId)?.name; const { graders, @@ -60,7 +68,7 @@ const GradingSettings = ({ intl, courseId }) => { handleResetPageData, handleAddAssignment, handleRemoveAssignment, - } = useUpdateGradingData(gradingSettingsData, setOverrideInternetConnectionAlert, setShowSuccessAlert); + } = useUpdateGradingData(courseGradingDetails, setOverrideInternetConnectionAlert, setShowSuccessAlert); const { gradeLetters, @@ -69,28 +77,22 @@ const GradingSettings = ({ intl, courseId }) => { } = useConvertGradeCutoffs(gradeCutoffs); useEffect(() => { - if (savingStatus === RequestStatus.SUCCESSFUL) { + if (savingStatus) { setShowSuccessAlert(!showSuccessAlert); setShowSavePrompt(!showSavePrompt); setTimeout(() => setShowSuccessAlert(false), 15000); setIsQueryPending(!isQueryPending); window.scrollTo({ top: 0, behavior: 'smooth' }); } - }, [savingStatus]); - - useEffect(() => { - dispatch(fetchGradingSettings(courseId)); - dispatch(fetchCourseSettingsQuery(courseId)); - }, [courseId]); + }, [savePending]); if (isLoading) { - // eslint-disable-next-line react/jsx-no-useless-fragment - return <>; + return null; } const handleQueryProcessing = () => { setShowSuccessAlert(false); - dispatch(sendGradingSetting(courseId, gradingData)); + updateGradingSettings(gradingData); }; const handleSendGradingSettingsData = () => { @@ -110,11 +112,14 @@ const GradingSettings = ({ intl, courseId }) => { default: intl.formatMessage(messages.buttonSaveText), pending: intl.formatMessage(messages.buttonSavingText), }, - disabledStates: [RequestStatus.PENDING], + disabledStates: [STATEFUL_BUTTON_STATES.pending], }; return ( <> + + {getPageHeadTitle(courseName, intl.formatMessage(messages.headingTitle))} +
{ resetDataRef={resetDataRef} setOverrideInternetConnectionAlert={setOverrideInternetConnectionAlert} setEligibleGrade={setEligibleGrade} + defaultGradeDesignations={gradingSettings?.defaultGradeDesignations} /> {courseSettingsData.creditEligibilityEnabled && courseSettingsData.isCreditCourse && ( @@ -226,7 +232,7 @@ const GradingSettings = ({ intl, courseId }) => {
{showOverrideInternetConnectionAlert && ( { }; GradingSettings.propTypes = { - intl: intlShape.isRequired, courseId: PropTypes.string.isRequired, }; -export default injectIntl(GradingSettings); +export default GradingSettings; diff --git a/src/grading-settings/GradingSettings.test.jsx b/src/grading-settings/GradingSettings.test.jsx index 955b33d016..84b574367f 100644 --- a/src/grading-settings/GradingSettings.test.jsx +++ b/src/grading-settings/GradingSettings.test.jsx @@ -1,14 +1,17 @@ -import React from 'react'; -import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; -import { AppProvider } from '@edx/frontend-platform/react'; import { initializeMockApp } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import { render, waitFor, fireEvent } from '@testing-library/react'; +import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n'; +import { AppProvider } from '@edx/frontend-platform/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { + act, fireEvent, render, screen, +} from '@testing-library/react'; import MockAdapter from 'axios-mock-adapter'; +import React from 'react'; import initializeStore from '../store'; -import { getGradingSettingsApiUrl } from './data/api'; import gradingSettings from './__mocks__/gradingSettings'; +import { getCourseSettingsApiUrl, getGradingSettingsApiUrl } from './data/api'; import GradingSettings from './GradingSettings'; import messages from './messages'; @@ -16,10 +19,14 @@ const courseId = '123'; let axiosMock; let store; +const queryClient = new QueryClient(); + const RootWrapper = () => ( - + + + ); @@ -28,10 +35,7 @@ describe('', () => { beforeEach(() => { initializeMockApp({ authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], + userId: 3, username: 'abc123', administrator: true, roles: [], }, }); @@ -40,52 +44,72 @@ describe('', () => { axiosMock .onGet(getGradingSettingsApiUrl(courseId)) .reply(200, gradingSettings); + axiosMock + .onPost(getGradingSettingsApiUrl(courseId)) + .reply(200, {}); + axiosMock.onGet(getCourseSettingsApiUrl(courseId)) + .reply(200, {}); + render(); }); - it('should render without errors', async () => { - const { getByText, getAllByText } = render(); - await waitFor(() => { - const gradingElements = getAllByText(messages.headingTitle.defaultMessage); - const gradingTitle = gradingElements[0]; - expect(getByText(messages.headingSubtitle.defaultMessage)).toBeInTheDocument(); - expect(gradingTitle).toBeInTheDocument(); - expect(getByText(messages.policy.defaultMessage)).toBeInTheDocument(); - expect(getByText(messages.policiesDescription.defaultMessage)).toBeInTheDocument(); + function testSaving() { + const saveBtn = screen.getByText(messages.buttonSaveText.defaultMessage); + expect(saveBtn).toBeInTheDocument(); + fireEvent.click(saveBtn); + expect(screen.getByText(messages.buttonSavingText.defaultMessage)).toBeInTheDocument(); + } + + function setOnlineStatus(isOnline) { + jest.spyOn(navigator, 'onLine', 'get').mockReturnValue(isOnline); + act(() => { + window.dispatchEvent(new window.Event(isOnline ? 'online' : 'offline')); }); + } + + it('should render without errors', async () => { + const gradingElements = await screen.findAllByText(messages.headingTitle.defaultMessage); + const gradingTitle = gradingElements[0]; + expect(screen.getByText(messages.headingSubtitle.defaultMessage)).toBeInTheDocument(); + expect(gradingTitle).toBeInTheDocument(); + expect(screen.getByText(messages.policy.defaultMessage)).toBeInTheDocument(); + expect(screen.getByText(messages.policiesDescription.defaultMessage)).toBeInTheDocument(); }); it('should update segment input value and show save alert', async () => { - const { getByTestId, getAllByTestId } = render(); - await waitFor(() => { - const segmentInputs = getAllByTestId('grading-scale-segment-input'); - expect(segmentInputs).toHaveLength(5); - const segmentInput = segmentInputs[1]; - fireEvent.change(segmentInput, { target: { value: 'Test' } }); - expect(segmentInput).toHaveValue('Test'); - expect(getByTestId('grading-settings-save-alert')).toBeVisible(); - }); + const segmentInputs = await screen.findAllByTestId('grading-scale-segment-input'); + expect(segmentInputs).toHaveLength(5); + const segmentInput = segmentInputs[1]; + fireEvent.change(segmentInput, { target: { value: 'Test' } }); + expect(segmentInput).toHaveValue('Test'); + expect(screen.getByTestId('grading-settings-save-alert')).toBeVisible(); }); it('should update grading scale segment input value on change and cancel the action', async () => { - const { getByText, getAllByTestId } = render(); - await waitFor(() => { - const segmentInputs = getAllByTestId('grading-scale-segment-input'); - const segmentInput = segmentInputs[1]; - fireEvent.change(segmentInput, { target: { value: 'Test' } }); - fireEvent.click(getByText(messages.buttonCancelText.defaultMessage)); - expect(segmentInput).toHaveValue('a'); - }); + const segmentInputs = await screen.findAllByTestId('grading-scale-segment-input'); + const segmentInput = segmentInputs[1]; + fireEvent.change(segmentInput, { target: { value: 'Test' } }); + fireEvent.click(screen.getByText(messages.buttonCancelText.defaultMessage)); + expect(segmentInput).toHaveValue('a'); }); + it('should save segment input changes and display saving message', async () => { - const { getByText, getAllByTestId } = render(); - await waitFor(() => { - const segmentInputs = getAllByTestId('grading-scale-segment-input'); - const segmentInput = segmentInputs[1]; - fireEvent.change(segmentInput, { target: { value: 'Test' } }); - const saveBtn = getByText(messages.buttonSaveText.defaultMessage); - expect(saveBtn).toBeInTheDocument(); - fireEvent.click(saveBtn); - expect(getByText(messages.buttonSavingText.defaultMessage)).toBeInTheDocument(); - }); + const segmentInputs = await screen.findAllByTestId('grading-scale-segment-input'); + const segmentInput = segmentInputs[1]; + fireEvent.change(segmentInput, { target: { value: 'Test' } }); + testSaving(); + }); + + it('should handle being offline gracefully', async () => { + setOnlineStatus(false); + const segmentInputs = await screen.findAllByTestId('grading-scale-segment-input'); + const segmentInput = segmentInputs[1]; + fireEvent.change(segmentInput, { target: { value: 'Test' } }); + const saveBtn = screen.getByText(messages.buttonSaveText.defaultMessage); + expect(saveBtn).toBeInTheDocument(); + fireEvent.click(saveBtn); + expect(screen.getByText(/studio's having trouble saving your work/i)).toBeInTheDocument(); + expect(screen.queryByText(messages.buttonSavingText.defaultMessage)).not.toBeInTheDocument(); + setOnlineStatus(true); + testSaving(); }); }); diff --git a/src/grading-settings/data/apiHooks.ts b/src/grading-settings/data/apiHooks.ts new file mode 100644 index 0000000000..c963575b8a --- /dev/null +++ b/src/grading-settings/data/apiHooks.ts @@ -0,0 +1,26 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { getCourseSettings, getGradingSettings, sendGradingSettings } from './api'; + +export const useGradingSettings = (courseId: string) => ( + useQuery({ + queryKey: ['gradingSettings', courseId], + queryFn: () => getGradingSettings(courseId), + }) +); + +export const useCourseSettings = (courseId: string) => ( + useQuery({ + queryKey: ['courseSettings', courseId], + queryFn: () => getCourseSettings(courseId), + }) +); + +export const useGradingSettingUpdater = (courseId: string) => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (settings) => sendGradingSettings(courseId, settings), + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ['gradingSettings', courseId] }); + }, + }); +}; diff --git a/src/grading-settings/data/selectors.js b/src/grading-settings/data/selectors.js deleted file mode 100644 index 7745167b8d..0000000000 --- a/src/grading-settings/data/selectors.js +++ /dev/null @@ -1,13 +0,0 @@ -const getLoadingStatus = (state) => state.gradingSettings.loadingStatus; -const getGradingSettings = (state) => state.gradingSettings.gradingSettings.courseDetails; -const getCourseAssignmentLists = (state) => state.gradingSettings.gradingSettings.courseAssignmentLists; -const getSavingStatus = (state) => state.gradingSettings.savingStatus; -const getCourseSettings = (state) => state.gradingSettings.courseSettings; - -export { - getLoadingStatus, - getGradingSettings, - getCourseAssignmentLists, - getSavingStatus, - getCourseSettings, -}; diff --git a/src/grading-settings/data/slice.js b/src/grading-settings/data/slice.js deleted file mode 100644 index 8d0ba50891..0000000000 --- a/src/grading-settings/data/slice.js +++ /dev/null @@ -1,43 +0,0 @@ -/* eslint-disable no-param-reassign */ -import { createSlice } from '@reduxjs/toolkit'; - -import { RequestStatus } from '../../data/constants'; - -const slice = createSlice({ - name: 'gradingSettings', - initialState: { - loadingStatus: RequestStatus.IN_PROGRESS, - savingStatus: '', - gradingSettings: {}, - courseSettings: {}, - }, - reducers: { - updateLoadingStatus: (state, { payload }) => { - state.loadingStatus = payload.status; - }, - updateSavingStatus: (state, { payload }) => { - state.savingStatus = payload.status; - }, - fetchGradingSettingsSuccess: (state, { payload }) => { - Object.assign(state.gradingSettings, payload); - }, - sendGradingSettingsSuccess: (state, { payload }) => { - Object.assign(state.gradingSettings, payload); - }, - fetchCourseSettingsSuccess: (state, { payload }) => { - Object.assign(state.courseSettings, payload); - }, - }, -}); - -export const { - updateLoadingStatus, - updateSavingStatus, - fetchGradingSettingsSuccess, - sendGradingSettingsSuccess, - fetchCourseSettingsSuccess, -} = slice.actions; - -export const { - reducer, -} = slice; diff --git a/src/grading-settings/data/thunks.js b/src/grading-settings/data/thunks.js deleted file mode 100644 index b7106eb390..0000000000 --- a/src/grading-settings/data/thunks.js +++ /dev/null @@ -1,55 +0,0 @@ -import { RequestStatus } from '../../data/constants'; -import { - getGradingSettings, - sendGradingSettings, - getCourseSettings, -} from './api'; -import { - sendGradingSettingsSuccess, - updateLoadingStatus, - updateSavingStatus, - fetchGradingSettingsSuccess, - fetchCourseSettingsSuccess, -} from './slice'; - -export function fetchGradingSettings(courseId) { - return async (dispatch) => { - dispatch(updateLoadingStatus({ status: RequestStatus.IN_PROGRESS })); - try { - const settingValues = await getGradingSettings(courseId); - dispatch(fetchGradingSettingsSuccess(settingValues)); - dispatch(updateLoadingStatus({ status: RequestStatus.SUCCESSFUL })); - } catch (error) { - dispatch(updateLoadingStatus({ status: RequestStatus.FAILED })); - } - }; -} - -export function sendGradingSetting(courseId, settings) { - return async (dispatch) => { - dispatch(updateSavingStatus({ status: RequestStatus.IN_PROGRESS })); - try { - const settingValues = await sendGradingSettings(courseId, settings); - dispatch(sendGradingSettingsSuccess(settingValues)); - dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL })); - } catch (error) { - dispatch(updateLoadingStatus({ status: RequestStatus.FAILED })); - } - }; -} - -export function fetchCourseSettingsQuery(courseId) { - return async (dispatch) => { - dispatch(updateLoadingStatus({ status: RequestStatus.IN_PROGRESS })); - - try { - const settingsValues = await getCourseSettings(courseId); - dispatch(fetchCourseSettingsSuccess(settingsValues)); - dispatch(updateLoadingStatus({ status: RequestStatus.SUCCESSFUL })); - return true; - } catch (error) { - dispatch(updateLoadingStatus({ status: RequestStatus.FAILED })); - return false; - } - }; -} diff --git a/src/grading-settings/grading-scale/GradingScale.jsx b/src/grading-settings/grading-scale/GradingScale.jsx index 7ba2e43767..2e610cbc42 100644 --- a/src/grading-settings/grading-scale/GradingScale.jsx +++ b/src/grading-settings/grading-scale/GradingScale.jsx @@ -1,19 +1,18 @@ -import React, { useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; +import { useIntl } from '@edx/frontend-platform/i18n'; import { Icon, IconButtonWithTooltip } from '@openedx/paragon'; import { Add as IconAdd } from '@openedx/paragon/icons'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import PropTypes from 'prop-types'; +import React, { useEffect, useState } from 'react'; +import { GradingScaleHandle, GradingScaleSegment, GradingScaleTicks } from './components'; +import messages from './messages'; import { useRanger } from './react-ranger'; -import messages from './messages'; import { convertGradeData, MAXIMUM_SCALE_LENGTH } from './utils'; -import { GradingScaleTicks, GradingScaleHandle, GradingScaleSegment } from './components'; -const DEFAULT_LETTERS = ['A', 'B', 'C', 'D']; +const DEFAULT_GRADE_LETTERS = ['A', 'B', 'C', 'D']; const getDefaultPassText = intl => intl.formatMessage(messages.defaultPassText); const GradingScale = ({ - intl, showSavePrompt, gradeCutoffs, setShowSuccessAlert, @@ -23,7 +22,9 @@ const GradingScale = ({ sortedGrades, setOverrideInternetConnectionAlert, setEligibleGrade, + defaultGradeDesignations, }) => { + const intl = useIntl(); const [gradingSegments, setGradingSegments] = useState(sortedGrades); const [letters, setLetters] = useState(gradeLetters); const [convertedResult, setConvertedResult] = useState({}); @@ -55,6 +56,15 @@ const GradingScale = ({ const addNewGradingSegment = () => { setGradingSegments(prevSegments => { + if (prevSegments.length >= 5) { + const segSize = MAXIMUM_SCALE_LENGTH / (prevSegments.length + 1); + return Array.from({ length: prevSegments.length + 1 }).map((_, i) => ( + { + current: 100 - i * segSize, + previous: 100 - (i + 1) * segSize, + } + )); + } const firstSegment = prevSegments[prevSegments.length - 1]; const secondSegment = prevSegments[prevSegments.length - 2]; const newCurrentValue = Math.ceil((secondSegment.current - secondSegment.previous) / 2); @@ -81,12 +91,12 @@ const GradingScale = ({ ]; }); - const nextIndex = (letters.length % DEFAULT_LETTERS.length); + const nextIndex = (letters.length % defaultGradeDesignations.length); if (gradingSegments.length === 2) { - setLetters([DEFAULT_LETTERS[0], DEFAULT_LETTERS[nextIndex]]); + setLetters([defaultGradeDesignations[0], defaultGradeDesignations[nextIndex]]); } else { - setLetters(prevLetters => [...prevLetters, DEFAULT_LETTERS[nextIndex]]); + setLetters(prevLetters => [...prevLetters, defaultGradeDesignations[nextIndex]]); } }; @@ -191,7 +201,7 @@ const GradingScale = ({ = 5} + disabled={gradingSegments.length >= (defaultGradeDesignations.length + 1)} data-testid="grading-scale-btn-add-segment" className="mr-3" src={IconAdd} @@ -230,7 +240,6 @@ const GradingScale = ({ }; GradingScale.propTypes = { - intl: intlShape.isRequired, showSavePrompt: PropTypes.func.isRequired, gradeCutoffs: PropTypes.objectOf(PropTypes.number).isRequired, gradeLetters: PropTypes.arrayOf(PropTypes.string).isRequired, @@ -245,6 +254,11 @@ GradingScale.propTypes = { }), ).isRequired, setEligibleGrade: PropTypes.func.isRequired, + defaultGradeDesignations: PropTypes.arrayOf(PropTypes.string), +}; + +GradingScale.defaultProps = { + defaultGradeDesignations: DEFAULT_GRADE_LETTERS, }; -export default injectIntl(GradingScale); +export default GradingScale; diff --git a/src/grading-settings/grading-scale/GradingScale.scss b/src/grading-settings/grading-scale/GradingScale.scss index 9c85b8c91e..f8eb30ce3e 100644 --- a/src/grading-settings/grading-scale/GradingScale.scss +++ b/src/grading-settings/grading-scale/GradingScale.scss @@ -9,6 +9,7 @@ height: 3.125rem; width: 100%; border: 1px solid var(--pgn-color-black); + overflow: hidden; .grading-scale-tick { .grading-scale-tick-number { @@ -81,7 +82,6 @@ .grading-scale-segment-content { display: flex; flex-direction: column; - margin-right: 1.25rem; margin-top: .375rem; font-size: .7rem; white-space: nowrap; diff --git a/src/grading-settings/grading-scale/components/GradingScaleSegment.jsx b/src/grading-settings/grading-scale/components/GradingScaleSegment.jsx deleted file mode 100644 index 412e936d89..0000000000 --- a/src/grading-settings/grading-scale/components/GradingScaleSegment.jsx +++ /dev/null @@ -1,79 +0,0 @@ -import { Button } from '@openedx/paragon'; -import React from 'react'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import PropTypes from 'prop-types'; - -import { getLettersOnLongScale, getLettersOnShortScale } from '../utils'; -import messages from '../messages'; - -const GradingScaleSegment = ({ - intl, - idx, - value, - getSegmentProps, - handleLetterChange, - letters, - gradingSegments, - removeGradingSegment, -}) => ( -
-
- {gradingSegments.length === 2 && ( - handleLetterChange(e, idx)} - disabled={idx === gradingSegments.length} - /> - )} - {gradingSegments.length > 2 && ( - handleLetterChange(e, idx)} - disabled={idx === gradingSegments.length} - /> - )} - - {gradingSegments[idx === 0 ? 0 : idx - 1]?.previous} - {value === 100 ? value : value - 1} - -
- {idx !== gradingSegments.length && idx - 1 !== 0 && ( - - )} -
-); - -GradingScaleSegment.propTypes = { - intl: intlShape.isRequired, - idx: PropTypes.number.isRequired, - value: PropTypes.number.isRequired, - getSegmentProps: PropTypes.func.isRequired, - handleLetterChange: PropTypes.func.isRequired, - removeGradingSegment: PropTypes.func.isRequired, - gradingSegments: PropTypes.arrayOf( - PropTypes.shape({ - current: PropTypes.number.isRequired, - previous: PropTypes.number.isRequired, - }), - ).isRequired, - letters: PropTypes.arrayOf(PropTypes.string).isRequired, -}; - -export default injectIntl(GradingScaleSegment); diff --git a/src/grading-settings/grading-scale/components/GradingScaleSegment.tsx b/src/grading-settings/grading-scale/components/GradingScaleSegment.tsx new file mode 100644 index 0000000000..ce974a90ac --- /dev/null +++ b/src/grading-settings/grading-scale/components/GradingScaleSegment.tsx @@ -0,0 +1,86 @@ +import { useIntl } from '@edx/frontend-platform/i18n'; +import { Button } from '@openedx/paragon'; +import React, { ChangeEvent } from 'react'; +import messages from '../messages'; + +import { getLettersOnLongScale, getLettersOnShortScale } from '../utils'; + +interface RangeSegment { + previous: number, + current: number, +} + +interface GradingScaleSegmentProps { + idx: number, + value: number, + getSegmentProps: () => { [key: string]: string }, + handleLetterChange: (event: ChangeEvent, idx: number) => void, + letters: [string], + gradingSegments: RangeSegment[], + removeGradingSegment: (idx: number) => void, +} + +const GradingScaleSegment = ({ + idx, + value, + getSegmentProps, + handleLetterChange, + letters, + gradingSegments, + removeGradingSegment, +}: GradingScaleSegmentProps) => { + const intl = useIntl(); + const prevValue = gradingSegments[idx === 0 ? 0 : idx - 1]?.previous ?? 0; + const segmentRightMargin = (value - prevValue) < 6 ? '0.125rem' : '1.25rem'; + return ( +
+
+ {gradingSegments.length === 2 && ( + handleLetterChange(e, idx)} + disabled={idx === gradingSegments.length} + /> + )} + {gradingSegments.length > 2 && ( + handleLetterChange(e, idx)} + disabled={idx === gradingSegments.length} + /> + )} + + {gradingSegments[idx === 0 ? 0 : idx - 1]?.previous} - {value === 100 ? value : value - 1} + +
+ {idx !== gradingSegments.length && idx - 1 !== 0 && ( + + )} +
+ ); +}; + +export default GradingScaleSegment; diff --git a/src/store.js b/src/store.js index 661862f608..bf761aadf7 100644 --- a/src/store.js +++ b/src/store.js @@ -10,7 +10,6 @@ import { reducer as discussionsReducer } from './pages-and-resources/discussions import { reducer as pagesAndResourcesReducer } from './pages-and-resources/data/slice'; import { reducer as customPagesReducer } from './custom-pages/data/slice'; import { reducer as advancedSettingsReducer } from './advanced-settings/data/slice'; -import { reducer as gradingSettingsReducer } from './grading-settings/data/slice'; import { reducer as studioHomeReducer } from './studio-home/data/slice'; import { reducer as scheduleAndDetailsReducer } from './schedule-and-details/data/slice'; import { reducer as filesReducer } from './files-and-videos/files-page/data/slice'; @@ -40,7 +39,6 @@ export default function initializeStore(preloadedState = undefined) { pagesAndResources: pagesAndResourcesReducer, scheduleAndDetails: scheduleAndDetailsReducer, advancedSettings: advancedSettingsReducer, - gradingSettings: gradingSettingsReducer, studioHome: studioHomeReducer, models: modelsReducer, live: liveReducer, From acbd7fa248fb2ade464ea254cb99dc4201523f97 Mon Sep 17 00:00:00 2001 From: "kshitij.sobti" Date: Fri, 30 Aug 2024 15:49:17 +0530 Subject: [PATCH 2/2] fix: fixes for compatibility with redwood which doesn't fully support typescript yet --- .../data/{apiHooks.ts => apiHooks.js} | 6 +- ...aleSegment.tsx => GradingScaleSegment.jsx} | 63 ++++++++++--------- 2 files changed, 35 insertions(+), 34 deletions(-) rename src/grading-settings/data/{apiHooks.ts => apiHooks.js} (78%) rename src/grading-settings/grading-scale/components/{GradingScaleSegment.tsx => GradingScaleSegment.jsx} (57%) diff --git a/src/grading-settings/data/apiHooks.ts b/src/grading-settings/data/apiHooks.js similarity index 78% rename from src/grading-settings/data/apiHooks.ts rename to src/grading-settings/data/apiHooks.js index c963575b8a..e55664f783 100644 --- a/src/grading-settings/data/apiHooks.ts +++ b/src/grading-settings/data/apiHooks.js @@ -1,21 +1,21 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { getCourseSettings, getGradingSettings, sendGradingSettings } from './api'; -export const useGradingSettings = (courseId: string) => ( +export const useGradingSettings = (courseId) => ( useQuery({ queryKey: ['gradingSettings', courseId], queryFn: () => getGradingSettings(courseId), }) ); -export const useCourseSettings = (courseId: string) => ( +export const useCourseSettings = (courseId) => ( useQuery({ queryKey: ['courseSettings', courseId], queryFn: () => getCourseSettings(courseId), }) ); -export const useGradingSettingUpdater = (courseId: string) => { +export const useGradingSettingUpdater = (courseId) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (settings) => sendGradingSettings(courseId, settings), diff --git a/src/grading-settings/grading-scale/components/GradingScaleSegment.tsx b/src/grading-settings/grading-scale/components/GradingScaleSegment.jsx similarity index 57% rename from src/grading-settings/grading-scale/components/GradingScaleSegment.tsx rename to src/grading-settings/grading-scale/components/GradingScaleSegment.jsx index ce974a90ac..364814768a 100644 --- a/src/grading-settings/grading-scale/components/GradingScaleSegment.tsx +++ b/src/grading-settings/grading-scale/components/GradingScaleSegment.jsx @@ -1,25 +1,11 @@ import { useIntl } from '@edx/frontend-platform/i18n'; import { Button } from '@openedx/paragon'; -import React, { ChangeEvent } from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import messages from '../messages'; import { getLettersOnLongScale, getLettersOnShortScale } from '../utils'; -interface RangeSegment { - previous: number, - current: number, -} - -interface GradingScaleSegmentProps { - idx: number, - value: number, - getSegmentProps: () => { [key: string]: string }, - handleLetterChange: (event: ChangeEvent, idx: number) => void, - letters: [string], - gradingSegments: RangeSegment[], - removeGradingSegment: (idx: number) => void, -} - const GradingScaleSegment = ({ idx, value, @@ -28,7 +14,7 @@ const GradingScaleSegment = ({ letters, gradingSegments, removeGradingSegment, -}: GradingScaleSegmentProps) => { +}) => { const intl = useIntl(); const prevValue = gradingSegments[idx === 0 ? 0 : idx - 1]?.previous ?? 0; const segmentRightMargin = (value - prevValue) < 6 ? '0.125rem' : '1.25rem'; @@ -46,22 +32,22 @@ const GradingScaleSegment = ({ }} > {gradingSegments.length === 2 && ( - handleLetterChange(e, idx)} - disabled={idx === gradingSegments.length} - /> + handleLetterChange(e, idx)} + disabled={idx === gradingSegments.length} + /> )} {gradingSegments.length > 2 && ( - handleLetterChange(e, idx)} - disabled={idx === gradingSegments.length} - /> + handleLetterChange(e, idx)} + disabled={idx === gradingSegments.length} + /> )} {gradingSegments[idx === 0 ? 0 : idx - 1]?.previous} - {value === 100 ? value : value - 1} @@ -83,4 +69,19 @@ const GradingScaleSegment = ({ ); }; +GradingScaleSegment.propTypes = { + idx: PropTypes.number.isRequired, + value: PropTypes.number.isRequired, + getSegmentProps: PropTypes.func.isRequired, + handleLetterChange: PropTypes.func.isRequired, + removeGradingSegment: PropTypes.func.isRequired, + gradingSegments: PropTypes.arrayOf( + PropTypes.shape({ + current: PropTypes.number.isRequired, + previous: PropTypes.number.isRequired, + }), + ).isRequired, + letters: PropTypes.arrayOf(PropTypes.string).isRequired, +}; + export default GradingScaleSegment;