-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: [AXIMST-46] Course unit - Drag And Drop For Xblocks #158
Conversation
5325bd2
to
93ff55d
Compare
c3e4684
to
50d419c
Compare
Codecov ReportAll modified and coverable lines are covered by tests ✅
❗ Your organization needs to install the Codecov GitHub app to enable full functionality. Additional details and impacted files@@ Coverage Diff @@
## ts-develop #158 +/- ##
=============================================
Coverage ? 89.58%
=============================================
Files ? 663
Lines ? 11014
Branches ? 2307
=============================================
Hits ? 9867
Misses ? 1112
Partials ? 35 ☔ View full report in Codecov by Sentry. |
1b4c250
to
f34267a
Compare
40495a3
to
075c782
Compare
075c782
to
92ef335
Compare
import PropTypes from 'prop-types'; | ||
import { useSelector } from 'react-redux'; | ||
import { useParams } from 'react-router-dom'; | ||
import { Container, Layout, Stack } from '@edx/paragon'; | ||
import { useIntl, injectIntl } from '@edx/frontend-platform/i18n'; | ||
import { ErrorAlert } from '@edx/frontend-lib-content-components'; | ||
import { DraggableList, ErrorAlert } from '@edx/frontend-lib-content-components'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ErrorAlert
component is imported twice. Remove one of the imports to clean up the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see only one import)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, my bad
setUnitXBlocks(() => initialUnitXBlocks); | ||
}); | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
handleXBlockDragAndDrop, | |
} = useCourseUnit({ courseId, blockId }); | |
const initialXBlocksData = useMemo(() => courseVerticalChildren.children ?? [], [courseVerticalChildren.children]); | |
const [unitXBlocks, setUnitXBlocks] = useState(initialXBlocksData); | |
useEffect(() => { | |
document.title = getPageHeadTitle('', unitTitle); | |
}, [unitTitle]); | |
useEffect(() => { | |
setUnitXBlocks(courseVerticalChildren.children); | |
}, [courseVerticalChildren.children]); | |
const { | |
isShow: isShowProcessingNotification, | |
title: processingNotificationTitle, | |
} = useSelector(getProcessingNotification); | |
if (isLoading) { | |
return <Loading />; | |
} | |
if (sequenceStatus === RequestStatus.FAILED) { | |
return ( | |
<Container size="xl" className="course-unit px-4 mt-4"> | |
<ConnectionErrorAlert /> | |
</Container> | |
); | |
} | |
const finalizeXBlockOrder = (newXBlocks) => { | |
handleXBlockDragAndDrop(newXBlocks.map(xBlock => xBlock.id), () => { | |
setUnitXBlocks(initialXBlocksData); | |
}); | |
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try this, should work
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a self-invoking function (it is required for DraggableList
), the rest of your suggestions are fully taken into account
src/course-unit/CourseUnit.test.jsx
Outdated
@@ -1288,4 +1289,56 @@ describe('<CourseUnit />', () => { | |||
expect(queryByTestId('has-error-files')).toBeNull(); | |||
}); | |||
}); | |||
|
|||
describe('Drag and drop', () => { | |||
it('check xblock list is restored to original order when API call fails', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it('check xblock list is restored to original order when API call fails', async () => { | |
it('checks xblock list is restored to original order when API call fails', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Corrected
src/course-unit/data/utils.js
Outdated
|
||
/** | ||
* Updates the 'id' property of objects in the data structure using the 'blockId' value where present. | ||
* @param {Object} data - The original data structure to be updated. | ||
* @returns {Object} - The updated data structure with updated 'id' values. | ||
*/ | ||
export const updateXBlockBlockIdToId = (data) => { | ||
if (!data?.children?.length) { | ||
return data; | ||
} | ||
|
||
const updatedData = { ...data }; | ||
|
||
updatedData.children.forEach((item, index) => { | ||
if (item.blockId) { | ||
updatedData.children[index] = { ...item, id: item.blockId }; | ||
} | ||
|
||
const { userPartitionInfo } = item; | ||
if (userPartitionInfo?.selectablePartitions) { | ||
updatedData.children[index].userPartitionInfo = { | ||
...userPartitionInfo, | ||
selectablePartitions: userPartitionInfo.selectablePartitions.map(partition => ({ | ||
...partition, | ||
groups: partition.groups.map(group => (group.id ? { ...group, id: group.blockId } : group)), | ||
})), | ||
}; | ||
} | ||
}); | ||
|
||
return updatedData; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/** | |
* Updates the 'id' property of objects in the data structure using the 'blockId' value where present. | |
* @param {Object} data - The original data structure to be updated. | |
* @returns {Object} - The updated data structure with updated 'id' values. | |
*/ | |
export const updateXBlockBlockIdToId = (data) => { | |
if (!data?.children?.length) { | |
return data; | |
} | |
const updatedData = { ...data }; | |
updatedData.children.forEach((item, index) => { | |
if (item.blockId) { | |
updatedData.children[index] = { ...item, id: item.blockId }; | |
} | |
const { userPartitionInfo } = item; | |
if (userPartitionInfo?.selectablePartitions) { | |
updatedData.children[index].userPartitionInfo = { | |
...userPartitionInfo, | |
selectablePartitions: userPartitionInfo.selectablePartitions.map(partition => ({ | |
...partition, | |
groups: partition.groups.map(group => (group.id ? { ...group, id: group.blockId } : group)), | |
})), | |
}; | |
} | |
}); | |
return updatedData; | |
}; | |
export const updateXBlockBlockIdToId = (data) => { | |
// Base case: if data is not an object or is null, return it unmodified | |
if (typeof data !== 'object' || data === null) { | |
return data; | |
} | |
// Process arrays recursively | |
if (Array.isArray(data)) { | |
return data.map(updateXBlockBlockIdToId); | |
} | |
// For objects, create a new object to accumulate the results | |
const updatedData = {}; | |
Object.keys(data).forEach(key => { | |
const value = data[key]; | |
// If the current property is 'children' or 'selectablePartitions' or 'groups', process it recursively | |
if (key === 'children' || key === 'selectablePartitions' || key === 'groups') { | |
updatedData[key] = updateXBlockBlockIdToId(value); | |
} else if (key === 'blockId') { | |
// Replace 'blockId' with 'id', maintaining other properties | |
updatedData['id'] = value; | |
} else { | |
// Copy other properties unchanged | |
updatedData[key] = value; | |
} | |
}); | |
// Special handling for objects with both 'id' and 'blockId' to ensure 'blockId' takes precedence | |
if ('blockId' in data) { | |
updatedData['id'] = data['blockId']; | |
} | |
return updatedData; | |
}; |
Check if this is more effecient implementation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added and deleted unnecessary comments
Thanks
src/course-unit/hooks.jsx
Outdated
const handleXBlockDragAndDrop = (xBlockListIds, restoreCallback) => { | ||
dispatch(setXBlockOrderListQuery(blockId, xBlockListIds, restoreCallback)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const handleXBlockDragAndDrop = (xBlockListIds, restoreCallback) => { | |
dispatch(setXBlockOrderListQuery(blockId, xBlockListIds, restoreCallback)); | |
const handleXBlockDragAndDrop = (xblockListIds, restoreCallback) => { | |
dispatch(setXBlockOrderListQuery(blockId, xblockListIds, restoreCallback)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Carmel Case is tricky, but I agree with Vlad we should do either the whole word in lowercase or both letters XB in uppercase
refs:
https://github.com/openedx/frontend-app-learning/blob/9dd79ca77ecb46dd0ea616945daa21d49fea5ee8/src/courseware/course/sequence/Unit/urls.js#L16
https://github.com/openedx/frontend-lib-content-components/blob/f33a3b5521d709ae4b0a98a5bdd9744060177816/addXblock.js#L5
https://github.com/openedx/edx-platform/blob/fa66728a67705723bd9cc74d3ccc9ca3bda4b7ae/cms/static/js/views/utils/xblock_utils.js#L184
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
corrected, thanks guys
src/course-unit/data/thunk.js
Outdated
@@ -276,3 +278,26 @@ export function copyToClipboard(usageKey) { | |||
} | |||
}; | |||
} | |||
|
|||
export function setXBlockOrderListQuery(blockId, xBlockListIds, restoreCallback) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export function setXBlockOrderListQuery(blockId, xBlockListIds, restoreCallback) { | |
export function setXBlockOrderListQuery(blockId, xblockListIds, restoreCallback) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/course-unit/data/slice.js
Outdated
reorderXBlockList: (state, { payload }) => { | ||
const xBlockList = [...state.courseVerticalChildren.children]; | ||
xBlockList.sort((a, b) => payload.indexOf(a.id) - payload.indexOf(b.id)); | ||
|
||
state.courseVerticalChildren.children = [...xBlockList]; | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reorderXBlockList: (state, { payload }) => { | |
const xBlockList = [...state.courseVerticalChildren.children]; | |
xBlockList.sort((a, b) => payload.indexOf(a.id) - payload.indexOf(b.id)); | |
state.courseVerticalChildren.children = [...xBlockList]; | |
}, | |
reorderXBlockList: (state, { payload }) => { | |
// Create a map for payload IDs to their index for O(1) lookups | |
const indexMap = new Map(payload.map((id, index) => [id, index])); | |
// Directly sort the children based on the order defined in payload | |
// This avoids the need to copy the array beforehand | |
state.courseVerticalChildren.children.sort((a, b) => { | |
return (indexMap.get(a.id) || 0) - (indexMap.get(b.id) || 0); | |
}); | |
}, |
indexOf
inside sort
function will be ineffective.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved this code from another page, but your suggestion looks great!
src/course-unit/data/selectors.js
Outdated
@@ -8,5 +8,6 @@ export const getCourseSectionVertical = (state) => state.courseUnit.courseSectio | |||
export const getCourseId = (state) => state.courseDetail.courseId; | |||
export const getSequenceId = (state) => state.courseUnit.sequenceId; | |||
export const getCourseVerticalChildren = (state) => state.courseUnit.courseVerticalChildren; | |||
export const getCourseVerticalChildren2 = (state) => state.courseUnit.courseVerticalChildren.children; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this just for debug, or where this is used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! Thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks ok, except comments above
* feat: [AXIMST-46] Drag And Drop For Xblocks * feat: added tests * refactor: refactoring after review
* feat: [AXIMST-46] Drag And Drop For Xblocks * feat: added tests * refactor: refactoring after review
* feat: [AXIMST-46] Drag And Drop For Xblocks * feat: added tests * refactor: refactoring after review
* feat: [AXIMST-46] Drag And Drop For Xblocks * feat: added tests * refactor: refactoring after review
* feat: [AXIMST-46] Drag And Drop For Xblocks * feat: added tests * refactor: refactoring after review
* feat: [AXIMST-46] Drag And Drop For Xblocks * feat: added tests * refactor: refactoring after review
* feat: [AXIMST-46] Drag And Drop For Xblocks * feat: added tests * refactor: refactoring after review
* feat: [AXIMST-46] Drag And Drop For Xblocks * feat: added tests * refactor: refactoring after review
* feat: [AXIMST-46] Drag And Drop For Xblocks * feat: added tests * refactor: refactoring after review
* feat: [AXIMST-46] Drag And Drop For Xblocks * feat: added tests * refactor: refactoring after review
YouTrack
https://youtrack.raccoongang.com/issue/AXIMST-46