From 57d67141aeb86026c5633c56ec6893c43d15794b Mon Sep 17 00:00:00 2001 From: Nicolas Clerc Date: Mon, 7 Aug 2023 14:24:36 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=9A(frontend)=20use=20nested=20routes?= =?UTF-8?q?=20for=20markdown=20related=20resources?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As we want to only use API nested routes, our markdown components needs to handle them. --- .../components/MarkdownEditor/index.spec.tsx | 6 +-- .../src/components/MarkdownEditor/index.tsx | 5 ++- .../src/components/MdxRenderer/index.spec.tsx | 4 +- .../src/components/MdxRenderer/index.tsx | 5 ++- .../remarkLocallyHostedImagePlugin.tsx | 6 ++- .../useImageUploadManager/index.spec.tsx | 28 ++++++++++---- .../useImageUploadManager/index.tsx | 23 ++++++++--- .../lib_markdown/src/data/queries/index.tsx | 10 ++++- .../createMarkdownImage/index.spec.tsx | 38 +++++++++++-------- .../sideEffects/createMarkdownImage/index.ts | 4 +- .../pollForMarkdownImage/index.spec.tsx | 31 ++++++++------- .../pollForMarkdownImage/index.tsx | 7 ++-- 12 files changed, 111 insertions(+), 56 deletions(-) diff --git a/src/frontend/packages/lib_markdown/src/components/MarkdownEditor/index.spec.tsx b/src/frontend/packages/lib_markdown/src/components/MarkdownEditor/index.spec.tsx index c021391361..efa7396958 100644 --- a/src/frontend/packages/lib_markdown/src/components/MarkdownEditor/index.spec.tsx +++ b/src/frontend/packages/lib_markdown/src/components/MarkdownEditor/index.spec.tsx @@ -551,7 +551,7 @@ describe('', () => { // Drop an image const markdownImageId = '5459a5b2-2f81-11ed-ab8f-47c92ec0ac16'; fetchMock.postOnce( - `/api/markdown-images/`, + `/api/markdown-documents/1/markdown-images/`, markdownImageMockFactory({ id: markdownImageId, active_stamp: null, @@ -562,7 +562,7 @@ describe('', () => { }), ); fetchMock.postOnce( - `/api/markdown-images/${markdownImageId}/initiate-upload/`, + `/api/markdown-documents/1/markdown-images/${markdownImageId}/initiate-upload/`, { fields: { key: 'foo', @@ -589,7 +589,7 @@ describe('', () => { // Image is uploaded fetchMock.get( - `/api/markdown-images/${markdownImageId}/`, + `/api/markdown-documents/1/markdown-images/${markdownImageId}/`, markdownImageMockFactory({ id: markdownImageId, is_ready_to_show: true, diff --git a/src/frontend/packages/lib_markdown/src/components/MarkdownEditor/index.tsx b/src/frontend/packages/lib_markdown/src/components/MarkdownEditor/index.tsx index 275adf1d18..16e7547066 100644 --- a/src/frontend/packages/lib_markdown/src/components/MarkdownEditor/index.tsx +++ b/src/frontend/packages/lib_markdown/src/components/MarkdownEditor/index.tsx @@ -125,7 +125,10 @@ export const MarkdownEditor = ({ markdownDocumentId }: MarkdownEditorProps) => { ); }; - const { addImageUpload } = useImageUploadManager(onImageUploadFinished); + const { addImageUpload } = useImageUploadManager( + markdownDocumentId, + onImageUploadFinished, + ); // note: we don't want to fetch the markdown document regularly to prevent // any editor update while the user has not saved her document. diff --git a/src/frontend/packages/lib_markdown/src/components/MdxRenderer/index.spec.tsx b/src/frontend/packages/lib_markdown/src/components/MdxRenderer/index.spec.tsx index 3d77fb25d0..7ae39b0c36 100644 --- a/src/frontend/packages/lib_markdown/src/components/MdxRenderer/index.spec.tsx +++ b/src/frontend/packages/lib_markdown/src/components/MdxRenderer/index.spec.tsx @@ -381,7 +381,7 @@ describe('', () => { const markdownText = fs.readFileSync(file, { encoding: 'utf8' }); fetchMock.getOnce( - '/api/markdown-images/981a52a2-7caf-49a5-bb36-1d8512152214/', + `/api/markdown-documents/${markdownDocumentId}/markdown-images/981a52a2-7caf-49a5-bb36-1d8512152214/`, markdownImageMockFactory({ id: '981a52a2-7caf-49a5-bb36-1d8512152214', url: 'https://s3.link/easy-to-find.png', @@ -389,7 +389,7 @@ describe('', () => { ); fetchMock.getOnce( - '/api/markdown-images/066036cc-2dde-11ed-89f4-afcf72a20b4c/', + `/api/markdown-documents/${markdownDocumentId}/markdown-images/066036cc-2dde-11ed-89f4-afcf72a20b4c/`, markdownImageMockFactory({ id: '066036cc-2dde-11ed-89f4-afcf72a20b4c', url: null, diff --git a/src/frontend/packages/lib_markdown/src/components/MdxRenderer/index.tsx b/src/frontend/packages/lib_markdown/src/components/MdxRenderer/index.tsx index b4aa71d05a..c824071212 100644 --- a/src/frontend/packages/lib_markdown/src/components/MdxRenderer/index.tsx +++ b/src/frontend/packages/lib_markdown/src/components/MdxRenderer/index.tsx @@ -90,7 +90,10 @@ export const MdxRenderer = ({ remarkLatexPlugin(markdownDocumentId), remarkMermaidPlugin, remarkMath, - remarkLocallyHostedImagePlugin(localImagesUrlCache.current), + remarkLocallyHostedImagePlugin( + markdownDocumentId, + localImagesUrlCache.current, + ), ]; const rehypePlugins: PluggableList = [ options?.useMathjax ? rehypeMathjax : rehypeKatex, diff --git a/src/frontend/packages/lib_markdown/src/components/MdxRenderer/remarkLocallyHostedImagePlugin.tsx b/src/frontend/packages/lib_markdown/src/components/MdxRenderer/remarkLocallyHostedImagePlugin.tsx index 03ea4a3ea9..aba6c03bef 100644 --- a/src/frontend/packages/lib_markdown/src/components/MdxRenderer/remarkLocallyHostedImagePlugin.tsx +++ b/src/frontend/packages/lib_markdown/src/components/MdxRenderer/remarkLocallyHostedImagePlugin.tsx @@ -11,6 +11,7 @@ import { fetchOneMarkdownImage } from '@lib-markdown/data/queries'; import { MarkdownImageCache } from './types'; const remarkLocallyHostedImagePlugin = ( + markdownDocumentId: string, localImagesUrlCache: MarkdownImageCache, ) => { // `markdownDocumentId` is mandatory to allow API calls @@ -41,7 +42,10 @@ const remarkLocallyHostedImagePlugin = ( } if (image.url.startsWith('/uploaded/image/')) { - const response = await fetchOneMarkdownImage(imageId); + const response = await fetchOneMarkdownImage( + markdownDocumentId, + imageId, + ); if (response.url) { localImagesUrlCache[imageId] = { url: response.url, diff --git a/src/frontend/packages/lib_markdown/src/components/useImageUploadManager/index.spec.tsx b/src/frontend/packages/lib_markdown/src/components/useImageUploadManager/index.spec.tsx index bb90e94e99..fa4f92c2bf 100644 --- a/src/frontend/packages/lib_markdown/src/components/useImageUploadManager/index.spec.tsx +++ b/src/frontend/packages/lib_markdown/src/components/useImageUploadManager/index.spec.tsx @@ -23,13 +23,17 @@ jest.mock('lib-components', () => ({ describe('useImageUploadManager', () => { let getLatestUseImageUploadManagerHookValues: () => any = () => {}; let getLatestUseUploadManagerHookValues: () => any = () => {}; + const markdownDocumentId = 'truc'; const onImageUploadFinished = jest.fn(); const TestComponent = () => { const uploadManager = useUploadManager(); getLatestUseUploadManagerHookValues = () => uploadManager; - const imageUploadManager = useImageUploadManager(onImageUploadFinished); + const imageUploadManager = useImageUploadManager( + markdownDocumentId, + onImageUploadFinished, + ); getLatestUseImageUploadManagerHookValues = () => imageUploadManager; return null; }; @@ -49,8 +53,8 @@ describe('useImageUploadManager', () => { const initiateUploadDeferred = new Deferred(); - const mockcreateMarkdownImage = fetchMock.postOnce( - `/api/markdown-images/`, + const mockCreateMarkdownImage = fetchMock.postOnce( + `/api/markdown-documents/${markdownDocumentId}/markdown-images/`, markdownImageMockFactory({ id: objectId, active_stamp: null, @@ -62,7 +66,7 @@ describe('useImageUploadManager', () => { ); const mockInitiateUpload = fetchMock.postOnce( - `/api/markdown-images/${objectId}/initiate-upload/`, + `/api/markdown-documents/${markdownDocumentId}/markdown-images/${objectId}/initiate-upload/`, initiateUploadDeferred.promise, ); @@ -93,15 +97,19 @@ describe('useImageUploadManager', () => { file, progress: 0, status: UploadManagerStatus.INIT, + parentType: modelName.MARKDOWN_DOCUMENTS, + parentId: markdownDocumentId, }, }); expect( mockInitiateUpload.calls( - `/api/markdown-images/${objectId}/initiate-upload/`, + `/api/markdown-documents/${markdownDocumentId}/markdown-images/${objectId}/initiate-upload/`, ), ).toHaveLength(1); expect( - mockcreateMarkdownImage.calls(`/api/markdown-images/`), + mockCreateMarkdownImage.calls( + `/api/markdown-documents/${markdownDocumentId}/markdown-images/`, + ), ).toHaveLength(1); } { @@ -122,6 +130,8 @@ describe('useImageUploadManager', () => { file, progress: 0, status: UploadManagerStatus.UPLOADING, + parentType: modelName.MARKDOWN_DOCUMENTS, + parentId: markdownDocumentId, }, }); expect(screen.getByRole('status')).toHaveTextContent('course.gif0%'); @@ -129,7 +139,7 @@ describe('useImageUploadManager', () => { // When status will turn to SUCCESS the image polling will start fetchMock.get( - `/api/markdown-images/${objectId}/`, + `/api/markdown-documents/${markdownDocumentId}/markdown-images/${objectId}/`, markdownImageMockFactory({ id: objectId, is_ready_to_show: false, @@ -152,6 +162,8 @@ describe('useImageUploadManager', () => { file, progress: 0, status: UploadManagerStatus.SUCCESS, + parentType: modelName.MARKDOWN_DOCUMENTS, + parentId: markdownDocumentId, }, }); expect(screen.getByRole('status')).toHaveTextContent( @@ -162,7 +174,7 @@ describe('useImageUploadManager', () => { { act(() => { fetchMock.get( - `/api/markdown-images/${objectId}/`, + `/api/markdown-documents/${markdownDocumentId}/markdown-images/${objectId}/`, markdownImageMockFactory({ id: objectId, is_ready_to_show: true, diff --git a/src/frontend/packages/lib_markdown/src/components/useImageUploadManager/index.tsx b/src/frontend/packages/lib_markdown/src/components/useImageUploadManager/index.tsx index 8a1f04d203..15a5d751fe 100644 --- a/src/frontend/packages/lib_markdown/src/components/useImageUploadManager/index.tsx +++ b/src/frontend/packages/lib_markdown/src/components/useImageUploadManager/index.tsx @@ -33,6 +33,7 @@ const toasterStyle = { }; export const useImageUploadManager = ( + markdownDocumentId: string, onImageUploadFinished: (imageId: string, imageFileName: string) => void, ) => { const intl = useIntl(); @@ -55,7 +56,7 @@ export const useImageUploadManager = ( // Once the update is done, the file will be processed, we have to wait for the processing // to be done too, hence the polling. await toast.promise( - pollForMarkdownImage(imageId), + pollForMarkdownImage(markdownDocumentId, imageId), { loading: intl.formatMessage(messages.processing, { imageName: uploadingObject.file.name, @@ -83,16 +84,28 @@ export const useImageUploadManager = ( resetUpload(imageId); } }); - }, [intl, onImageUploadFinished, resetUpload, uploadManagerState]); + }, [ + intl, + markdownDocumentId, + onImageUploadFinished, + resetUpload, + uploadManagerState, + ]); const addImageUpload = useCallback( async (file: File) => { - const response = await createMarkdownImage(); + const response = await createMarkdownImage(markdownDocumentId); const markdownImageId = response.id; - addUpload(modelName.MARKDOWN_IMAGES, markdownImageId, file); + addUpload( + modelName.MARKDOWN_IMAGES, + markdownImageId, + file, + modelName.MARKDOWN_DOCUMENTS, + markdownDocumentId, + ); return markdownImageId; }, - [addUpload], + [addUpload, markdownDocumentId], ); return { addImageUpload }; diff --git a/src/frontend/packages/lib_markdown/src/data/queries/index.tsx b/src/frontend/packages/lib_markdown/src/data/queries/index.tsx index 3463bf7321..488c6bf3ab 100644 --- a/src/frontend/packages/lib_markdown/src/data/queries/index.tsx +++ b/src/frontend/packages/lib_markdown/src/data/queries/index.tsx @@ -212,10 +212,16 @@ export const markdownRenderLatex = ( }; // It has to be called outside hook context -export const fetchOneMarkdownImage = (markdownImageId: string): any => { +export const fetchOneMarkdownImage = ( + markdownDocumentId: string, + markdownImageId: string, +): any => { return fetchOne({ meta: undefined, pageParam: undefined, - queryKey: ['markdown-images', markdownImageId], + queryKey: [ + `markdown-documents/${markdownDocumentId}/markdown-images`, + markdownImageId, + ], }); }; diff --git a/src/frontend/packages/lib_markdown/src/data/sideEffects/createMarkdownImage/index.spec.tsx b/src/frontend/packages/lib_markdown/src/data/sideEffects/createMarkdownImage/index.spec.tsx index bc17d7b4e4..a008009193 100644 --- a/src/frontend/packages/lib_markdown/src/data/sideEffects/createMarkdownImage/index.spec.tsx +++ b/src/frontend/packages/lib_markdown/src/data/sideEffects/createMarkdownImage/index.spec.tsx @@ -1,9 +1,11 @@ import fetchMock from 'fetch-mock'; import { useJwt } from 'lib-components'; +import { v4 as uuidv4 } from 'uuid'; import { createMarkdownImage } from './index'; describe('createMarkdownImage', () => { + const markdownDocumentId = uuidv4(); beforeEach(() => { useJwt.setState({ jwt: 'token', @@ -13,17 +15,20 @@ describe('createMarkdownImage', () => { afterEach(() => fetchMock.restore()); it('creates a new shared live media and returns it', async () => { - fetchMock.mock('/api/markdown-images/', { - active_stamp: null, - filename: null, - id: '5570eb90-764e-4300-b92e-d3426e9046d2', - is_ready_to_show: false, - upload_state: 'pending', - url: null, - markdown_document: '72f53735-3283-456c-a562-4e1b59e2a686', - }); + fetchMock.mock( + `/api/markdown-documents/${markdownDocumentId}/markdown-images/`, + { + active_stamp: null, + filename: null, + id: '5570eb90-764e-4300-b92e-d3426e9046d2', + is_ready_to_show: false, + upload_state: 'pending', + url: null, + markdown_document: markdownDocumentId, + }, + ); - const markdownImage = await createMarkdownImage(); + const markdownImage = await createMarkdownImage(markdownDocumentId); const fetchArgs = fetchMock.lastCall()![1]!; @@ -34,7 +39,7 @@ describe('createMarkdownImage', () => { is_ready_to_show: false, upload_state: 'pending', url: null, - markdown_document: '72f53735-3283-456c-a562-4e1b59e2a686', + markdown_document: markdownDocumentId, }); expect(fetchArgs.headers).toEqual({ Authorization: 'Bearer token', @@ -45,19 +50,22 @@ describe('createMarkdownImage', () => { it('throws when it fails to create the Markdown image (request failure)', async () => { fetchMock.mock( - '/api/markdown-images/', + `/api/markdown-documents/${markdownDocumentId}/markdown-images/`, Promise.reject(new Error('Failed to perform the request')), ); - await expect(createMarkdownImage()).rejects.toThrow( + await expect(createMarkdownImage(markdownDocumentId)).rejects.toThrow( 'Failed to perform the request', ); }); it('throws when it fails to create the Markdown image (API error)', async () => { - fetchMock.mock('/api/markdown-images/', 400); + fetchMock.mock( + `/api/markdown-documents/${markdownDocumentId}/markdown-images/`, + 400, + ); - await expect(createMarkdownImage()).rejects.toThrow( + await expect(createMarkdownImage(markdownDocumentId)).rejects.toThrow( 'Failed to create a new markdown image.', ); }); diff --git a/src/frontend/packages/lib_markdown/src/data/sideEffects/createMarkdownImage/index.ts b/src/frontend/packages/lib_markdown/src/data/sideEffects/createMarkdownImage/index.ts index 7d19d3ae7f..4ac7a4605d 100644 --- a/src/frontend/packages/lib_markdown/src/data/sideEffects/createMarkdownImage/index.ts +++ b/src/frontend/packages/lib_markdown/src/data/sideEffects/createMarkdownImage/index.ts @@ -8,9 +8,9 @@ import { useJwt, } from 'lib-components'; -export const createMarkdownImage = async () => { +export const createMarkdownImage = async (markdownDocumentId: string) => { const response = await fetchWrapper( - `${API_ENDPOINT}/${modelName.MARKDOWN_IMAGES}/`, + `${API_ENDPOINT}/${modelName.MARKDOWN_DOCUMENTS}/${markdownDocumentId}/${modelName.MARKDOWN_IMAGES}/`, { headers: { Authorization: `Bearer ${useJwt.getState().getJwt()}`, diff --git a/src/frontend/packages/lib_markdown/src/data/sideEffects/pollForMarkdownImage/index.spec.tsx b/src/frontend/packages/lib_markdown/src/data/sideEffects/pollForMarkdownImage/index.spec.tsx index 4c22cce959..0500208fc1 100644 --- a/src/frontend/packages/lib_markdown/src/data/sideEffects/pollForMarkdownImage/index.spec.tsx +++ b/src/frontend/packages/lib_markdown/src/data/sideEffects/pollForMarkdownImage/index.spec.tsx @@ -2,6 +2,7 @@ import { waitFor } from '@testing-library/react'; import fetchMock from 'fetch-mock'; import { markdownImageMockFactory } from 'index'; import { report } from 'lib-components'; +import { v4 as uuidv4 } from 'uuid'; import { pollForMarkdownImage } from './index'; @@ -10,6 +11,9 @@ jest.mock('lib-components', () => ({ report: jest.fn(), })); +const markdownDocumentId = uuidv4(); +const markdownImageId = uuidv4(); + describe('pollForMarkdownImage', () => { beforeEach(() => { jest.clearAllMocks(); @@ -22,10 +26,10 @@ describe('pollForMarkdownImage', () => { it('polls the image, backing off until it is ready and resolves with a success', async () => { fetchMock.mock( - '/api/markdown-images/c43f0c8f-4d3b-4219-86c3-86367b2b88cc/', + `/api/markdown-documents/${markdownDocumentId}/markdown-images/${markdownImageId}/`, JSON.stringify( markdownImageMockFactory({ - id: 'c43f0c8f-4d3b-4219-86c3-86367b2b88cc', + id: markdownImageId, is_ready_to_show: false, }), ), @@ -33,14 +37,15 @@ describe('pollForMarkdownImage', () => { ); const promise = pollForMarkdownImage( - 'c43f0c8f-4d3b-4219-86c3-86367b2b88cc', + markdownDocumentId, + markdownImageId, 1, ); await waitFor(() => { expect( fetchMock.calls( - '/api/markdown-images/c43f0c8f-4d3b-4219-86c3-86367b2b88cc/', + `/api/markdown-documents/${markdownDocumentId}/markdown-images/${markdownImageId}/`, { method: 'GET', }, @@ -49,11 +54,11 @@ describe('pollForMarkdownImage', () => { }); const markdownImage = markdownImageMockFactory({ - id: 'c43f0c8f-4d3b-4219-86c3-86367b2b88cc', + id: markdownImageId, is_ready_to_show: true, }); fetchMock.mock( - '/api/markdown-images/c43f0c8f-4d3b-4219-86c3-86367b2b88cc/', + `/api/markdown-documents/${markdownDocumentId}/markdown-images/${markdownImageId}/`, JSON.stringify(markdownImage), { method: 'GET', @@ -64,7 +69,7 @@ describe('pollForMarkdownImage', () => { await waitFor(() => { expect( fetchMock.calls( - '/api/markdown-images/c43f0c8f-4d3b-4219-86c3-86367b2b88cc/', + `/api/markdown-documents/${markdownDocumentId}/markdown-images/${markdownImageId}/`, { method: 'GET', }, @@ -77,26 +82,26 @@ describe('pollForMarkdownImage', () => { it('polls non-existing image', async () => { fetchMock.mock( - '/api/markdown-images/c43f0c8f-4d3b-4219-86c3-86367b2b88cc/', + `/api/markdown-documents/${markdownDocumentId}/markdown-images/${markdownImageId}/`, 404, { method: 'GET' }, ); await expect(async () => { - await pollForMarkdownImage('c43f0c8f-4d3b-4219-86c3-86367b2b88cc'); + await pollForMarkdownImage(markdownDocumentId, markdownImageId); }).rejects.toThrow( - 'Failed to get /api/markdown-images/c43f0c8f-4d3b-4219-86c3-86367b2b88cc/.', + `Failed to get /api/markdown-documents/${markdownDocumentId}/markdown-images/${markdownImageId}/.`, ); expect(report).toHaveBeenCalledWith( Error( - 'Failed to get /api/markdown-images/c43f0c8f-4d3b-4219-86c3-86367b2b88cc/.', + `Failed to get /api/markdown-documents/${markdownDocumentId}/markdown-images/${markdownImageId}/.`, ), ); }); it('resolves with a failure and reports it when it fails to poll the image', async () => { fetchMock.mock( - '/api/markdown-images/15cf570a-5dc6-421a-9856-59e1b008a6fb/', + `/api/markdown-documents/${markdownDocumentId}/markdown-images/${markdownImageId}/`, Promise.reject(new Error('Failed to get the image')), { method: 'GET', @@ -105,7 +110,7 @@ describe('pollForMarkdownImage', () => { ); await expect(async () => { - await pollForMarkdownImage('15cf570a-5dc6-421a-9856-59e1b008a6fb'); + await pollForMarkdownImage(markdownDocumentId, markdownImageId); }).rejects.toThrow('Failed to get the image'); expect(report).toHaveBeenCalledWith(Error('Failed to get the image')); diff --git a/src/frontend/packages/lib_markdown/src/data/sideEffects/pollForMarkdownImage/index.tsx b/src/frontend/packages/lib_markdown/src/data/sideEffects/pollForMarkdownImage/index.tsx index a529c35e4e..c04d675a50 100644 --- a/src/frontend/packages/lib_markdown/src/data/sideEffects/pollForMarkdownImage/index.tsx +++ b/src/frontend/packages/lib_markdown/src/data/sideEffects/pollForMarkdownImage/index.tsx @@ -6,12 +6,13 @@ import { MarkdownImage, report } from 'lib-components'; import { fetchOneMarkdownImage } from '@lib-markdown/data/queries'; export async function pollForMarkdownImage( - resourceId: string, + documentId: string, + imageId: string, timer = 15, counter = 1, ): Promise { try { - const image = await fetchOneMarkdownImage(resourceId); + const image = await fetchOneMarkdownImage(documentId, imageId); if (image.is_ready_to_show) { return image; @@ -19,7 +20,7 @@ export async function pollForMarkdownImage( counter++; timer = timer * counter; await new Promise((resolve) => window.setTimeout(resolve, 100 * timer)); - return await pollForMarkdownImage(resourceId, timer, counter); + return await pollForMarkdownImage(documentId, imageId, timer, counter); } } catch (error) { report(error);