-
Notifications
You must be signed in to change notification settings - Fork 78
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
Fix: Display custom error page when folder is missing in course materials #7612
Fix: Display custom error page when folder is missing in course materials #7612
Conversation
357b033
to
0ae652d
Compare
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.
Please squash both commits. Your 2nd commit changes only contain:
- text formatting changes (should have been squashed in first commit)
- Change of icon from
InsertDriveFileOutlined
toFolderOutlined
(should have done this in the first commit in the first place)
client/app/bundles/course/material/folders/pages/BaseRetrievePage.tsx
Outdated
Show resolved
Hide resolved
client/app/bundles/course/material/folders/pages/ErrorRetrievingFolderPage.tsx
Outdated
Show resolved
Hide resolved
client/app/bundles/course/material/folders/pages/FolderShow/index.tsx
Outdated
Show resolved
Hide resolved
client/app/bundles/course/material/folders/pages/FolderShow/index.tsx
Outdated
Show resolved
Hide resolved
0ae652d
to
2df7c03
Compare
client/app/bundles/course/material/folders/pages/ErrorRetrievingFolderPage.tsx
Show resolved
Hide resolved
c57bf44
to
c939249
Compare
client/app/bundles/course/material/folders/pages/ErrorRetrievingFolderPage.tsx
Show resolved
Hide resolved
c939249
to
e5d1958
Compare
client/app/bundles/course/material/BaseRetrieveMaterialPage.tsx
Outdated
Show resolved
Hide resolved
const useWorkbinURL = (courseId: string | undefined): string => { | ||
const [workbinURL, setWorkbinURL] = useState(`/courses/${courseId}`); | ||
|
||
useEffect(() => { | ||
if (courseId) { | ||
loadDefaultMaterial(+courseId).then((materialsItem) => { | ||
if (materialsItem?.path) { | ||
setWorkbinURL(materialsItem.path); | ||
} | ||
}); | ||
} | ||
}, [courseId]); | ||
|
||
return workbinURL; | ||
}; |
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.
You should be fetching the folderId in the loader,
then consume the data here using useLoaderData()
.
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 loaderData might not be available on the ErrorPage.
const extractMaterial = ( | ||
sidebar: { label: string; key: string; path: string }[], | ||
): { label: string; key: string; path: string } | undefined => | ||
sidebar.find((item) => item.key === 'materials'); | ||
|
||
export const loadDefaultMaterial = async ( | ||
courseId: number, | ||
): Promise<{ | ||
label: string; | ||
key: string; | ||
path: string; | ||
} | null> => { | ||
const { | ||
data: { sidebar }, | ||
} = await CourseAPI.courses.fetchLayout(courseId); | ||
return sidebar ? extractMaterial(sidebar) || null : null; | ||
}; |
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.
This logic for pulling information from sidebar makes the code convoluted.
Just open a new /courses/<course-id>/materials
route and then you can statically reference that route.
The route can then redirect the user to the root folder.
This will make the code clean.
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.
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.
Backend needs to open a route as well
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.
Have added new backend api and updated the fronted for using it.
d1b5abf
to
0f54b22
Compare
23414e8
to
8f234c3
Compare
…ing in course materials
8f234c3
to
2c84924
Compare
load_and_authorize_resource :material, through: :folder, class: 'Course::Material', except: :load_default | ||
|
||
def load_default | ||
@folder = Course::Material::Folder.where(course_id: current_course.id, parent_id: nil).order(:created_at).first |
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.
Use existing Course.root_folder
method @folder = current_course.root_folder
instead of re-writing verbose logic.
load_and_authorize_resource :material, through: :folder, class: 'Course::Material' | ||
load_and_authorize_resource :material, through: :folder, class: 'Course::Material', except: :load_default | ||
|
||
def load_default |
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.
You're loading the root folder, not a file, so this method does not belong in Course::Material::MaterialsController
. It should live in Course::Material::FoldersController
instead.
You will need to skip_load_and_authorize_resource :folder
for the method you create in Course::Material::FoldersController
The perk of doing that is that you can simply set the @folder
and then fallback to the default show
Folder method, which will do its own authorization checks.
This will also align json responses (all folder responses return the same json schema)
@@ -25,6 +31,10 @@ export default class MaterialsAPI extends BaseCourseAPI { | |||
); | |||
} | |||
|
|||
fetchDefault(): APIResponse<FolderMiniEntity> { | |||
return this.client.get(`${this.#materialPrefix}/load_default`); |
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.
You can also use urlPrefix
to fetch the default folder, e.g. /materials/folders
, your routes.rb
will have to update accordingly
const folderId = getIdFromUnknown(match.params?.folderId); | ||
if (!courseId) throw new Error(`Invalid course id: ${courseId}`); |
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.
Redundant check.
Your code will never throw this error unless courseId
is an empty string (You should have used getIdFromUnknown
on the courseId
).
In any case, folderId
will be invalid when the courseId
is invalid.
@@ -15,8 +17,12 @@ const getShouldDownloadFromContentDisposition = ( | |||
}; | |||
|
|||
export default class MaterialsAPI extends BaseCourseAPI { | |||
get #materialPrefix(): string { |
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.
Similarly, you shouldn't be introducing folder logic into MaterialsAPI
, it should go into client/app/api/course/MaterialFolders.js
instead.
At the same time, good to rewrite the file into TypeScript.
const defaultMaterialId = await loadDefaultMaterialId(); | ||
({ data } = await CourseAPI.folders.fetch(defaultMaterialId)); |
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.
You should align your backend API so you don't need to make 3 API calls just to get the root folder (2 calls are sufficient).
- fetch invalid folder payload (throws error)
fetch valid root folderId- fetch valid root folder payload
export function dispatchFolderData( | ||
data: FolderData, | ||
): Operation<SaveFolderAction> { | ||
return async (dispatch) => { | ||
return dispatch( | ||
actions.saveFolder( | ||
data.currFolderInfo, | ||
data.subfolders, | ||
data.materials, | ||
data.advanceStartAt, | ||
data.permissions, | ||
), | ||
); | ||
}; |
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.
Is there really a need to modify this?
superseded by #7626 |
This pull request fixes the issue where a default 404 page was shown when a folder was missing in the course materials section. Now, a custom error page is displayed instead. This resolves Issue #7437, which was raised to improve user experience by providing a more informative error message when materials are not found.