Skip to content
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

Conversation

phungmanhcuong
Copy link
Contributor

@phungmanhcuong phungmanhcuong commented Oct 23, 2024

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.

@phungmanhcuong phungmanhcuong force-pushed the mcphung/materials-missing-folder-error-page branch from 357b033 to 0ae652d Compare October 23, 2024 11:00
@phungmanhcuong phungmanhcuong changed the title Fix: Display custom error page when file is missing in course materials Fix: Display custom error page when folder is missing in course materials Oct 23, 2024
Copy link
Contributor

@cysjonathan cysjonathan left a 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:

  1. text formatting changes (should have been squashed in first commit)
  2. Change of icon from InsertDriveFileOutlined to FolderOutlined (should have done this in the first commit in the first place)

@phungmanhcuong phungmanhcuong force-pushed the mcphung/materials-missing-folder-error-page branch from 0ae652d to 2df7c03 Compare October 24, 2024 02:57
@phungmanhcuong phungmanhcuong force-pushed the mcphung/materials-missing-folder-error-page branch 5 times, most recently from c57bf44 to c939249 Compare October 25, 2024 00:49
@phungmanhcuong phungmanhcuong self-assigned this Oct 25, 2024
@phungmanhcuong phungmanhcuong force-pushed the mcphung/materials-missing-folder-error-page branch from c939249 to e5d1958 Compare October 25, 2024 03:28
Comment on lines 38 to 52
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;
};
Copy link
Contributor

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().

Copy link
Contributor Author

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.

Comment on lines 6 to 11
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;
};
Copy link
Contributor

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.

Copy link
Contributor Author

@phungmanhcuong phungmanhcuong Oct 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be confirmed your idea, this is whats you want ?
Screenshot 2024-10-28 at 07 10 13

Copy link
Contributor

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

Copy link
Contributor Author

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.

@phungmanhcuong phungmanhcuong force-pushed the mcphung/materials-missing-folder-error-page branch 3 times, most recently from d1b5abf to 0f54b22 Compare October 30, 2024 08:14
config/routes.rb Outdated Show resolved Hide resolved
@phungmanhcuong phungmanhcuong force-pushed the mcphung/materials-missing-folder-error-page branch 2 times, most recently from 23414e8 to 8f234c3 Compare October 30, 2024 09:15
@phungmanhcuong phungmanhcuong force-pushed the mcphung/materials-missing-folder-error-page branch from 8f234c3 to 2c84924 Compare October 30, 2024 09:30
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
Copy link
Contributor

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
Copy link
Contributor

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`);
Copy link
Contributor

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}`);
Copy link
Contributor

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 {
Copy link
Contributor

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.

Comment on lines +22 to +23
const defaultMaterialId = await loadDefaultMaterialId();
({ data } = await CourseAPI.folders.fetch(defaultMaterialId));
Copy link
Contributor

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).

  1. fetch invalid folder payload (throws error)
  2. fetch valid root folderId
  3. fetch valid root folder payload

Comment on lines +66 to +79
export function dispatchFolderData(
data: FolderData,
): Operation<SaveFolderAction> {
return async (dispatch) => {
return dispatch(
actions.saveFolder(
data.currFolderInfo,
data.subfolders,
data.materials,
data.advanceStartAt,
data.permissions,
),
);
};
Copy link
Contributor

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?

@cysjonathan
Copy link
Contributor

superseded by #7626

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Accessing Subfolder that does not exist render 404 Error
4 participants