Skip to content

Commit 91217b3

Browse files
committed
🐛(frontend) retry check media status after page reload
Previous refactoring removed the retry logic for checking media status after a page reload. This commit reintroduces that functionality to ensure uploads are properly processed even after a page reload. We improve the test coverage to validate this behavior.
1 parent ab271bc commit 91217b3

File tree

6 files changed

+189
-135
lines changed

6 files changed

+189
-135
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ and this project adheres to
2424
- ♿(frontend) improve accessibility:
2525
- ♿(frontend) remove empty alt on logo due to Axe a11y error #1516
2626
- 🐛(backend) fix s3 version_id validation
27+
- 🐛(frontend) retry check media status after page reload #1555
2728

2829
## [3.8.2] - 2025-10-17
2930

src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ test.describe('Doc Editor', () => {
494494
if (request.method().includes('GET')) {
495495
await route.fulfill({
496496
json: {
497-
status: requestCount ? 'ready' : 'processing',
497+
status: requestCount > 1 ? 'ready' : 'processing',
498498
file: '/anything.html',
499499
},
500500
});
@@ -518,6 +518,12 @@ test.describe('Doc Editor', () => {
518518
await fileChooser.setFiles(path.join(__dirname, 'assets/test.html'));
519519

520520
await expect(editor.getByText('Analyzing file...')).toBeVisible();
521+
522+
// To be sure the retry happens even after a page reload
523+
await page.reload();
524+
525+
await expect(editor.getByText('Analyzing file...')).toBeVisible();
526+
521527
// The retry takes a few seconds
522528
await expect(editor.getByText('test.html')).toBeVisible({
523529
timeout: 7000,

src/frontend/apps/impress/src/features/docs/doc-editor/api/checkDocMediaStatus.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { APIError, errorCauses } from '@/api';
2+
import { sleep } from '@/utils';
23

34
interface CheckDocMediaStatusResponse {
45
file?: string;
@@ -25,3 +26,28 @@ export const checkDocMediaStatus = async ({
2526

2627
return response.json() as Promise<CheckDocMediaStatusResponse>;
2728
};
29+
30+
/**
31+
* Upload file can be analyzed on the server side,
32+
* we had this function to wait for the analysis to be done
33+
* before returning the file url. It will keep the loader
34+
* on the upload button until the analysis is done.
35+
* @param url
36+
* @returns Promise<CheckDocMediaStatusResponse> status_code
37+
* @description Waits for the upload to be analyzed by checking the status of the file.
38+
*/
39+
export const loopCheckDocMediaStatus = async (
40+
url: string,
41+
): Promise<CheckDocMediaStatusResponse> => {
42+
const SLEEP_TIME = 5000;
43+
const response = await checkDocMediaStatus({
44+
urlMedia: url,
45+
});
46+
47+
if (response.status === 'ready') {
48+
return response;
49+
} else {
50+
await sleep(SLEEP_TIME);
51+
return await loopCheckDocMediaStatus(url);
52+
}
53+
};

src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-blocks/PdfBlock.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ type CreatePDFBlockConfig = BlockConfig<
4646

4747
interface PdfBlockComponentProps {
4848
block: BlockNoDefaults<
49-
Record<'callout', CreatePDFBlockConfig>,
49+
Record<'pdf', CreatePDFBlockConfig>,
5050
InlineContentSchema,
5151
StyleSchema
5252
>;
Lines changed: 119 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,138 @@
1+
import {
2+
BlockConfig,
3+
BlockNoDefaults,
4+
BlockNoteEditor,
5+
InlineContentSchema,
6+
StyleSchema,
7+
} from '@blocknote/core';
18
import { createReactBlockSpec } from '@blocknote/react';
9+
import { t } from 'i18next';
10+
import { useEffect } from 'react';
211

312
import { Box, Text } from '@/components';
13+
import { useMediaUrl } from '@/core';
414

15+
import { loopCheckDocMediaStatus } from '../../api';
516
import Loader from '../../assets/loader.svg';
617
import Warning from '../../assets/warning.svg';
718

19+
type UploadLoaderPropSchema = {
20+
readonly information: { readonly default: '' };
21+
readonly type: {
22+
readonly default: 'loading';
23+
readonly values: readonly ['loading', 'warning'];
24+
};
25+
readonly blockUploadName: { readonly default: '' };
26+
readonly blockUploadShowPreview: { readonly default: true };
27+
readonly blockUploadType: {
28+
readonly default: '';
29+
};
30+
readonly blockUploadUrl: { readonly default: '' };
31+
};
32+
33+
type UploadLoaderBlockConfig = BlockConfig<
34+
'uploadLoader',
35+
UploadLoaderPropSchema,
36+
'none'
37+
>;
38+
39+
type UploadLoaderBlockType = BlockNoDefaults<
40+
Record<'uploadLoader', UploadLoaderBlockConfig>,
41+
InlineContentSchema,
42+
StyleSchema
43+
>;
44+
45+
type UploadLoaderEditor = BlockNoteEditor<
46+
Record<'uploadLoader', UploadLoaderBlockConfig>,
47+
InlineContentSchema,
48+
StyleSchema
49+
>;
50+
51+
interface UploadLoaderBlockComponentProps {
52+
block: UploadLoaderBlockType;
53+
editor: UploadLoaderEditor;
54+
contentRef: (node: HTMLElement | null) => void;
55+
}
56+
57+
const UploadLoaderBlockComponent = ({
58+
block,
59+
editor,
60+
}: UploadLoaderBlockComponentProps) => {
61+
const mediaUrl = useMediaUrl();
62+
63+
useEffect(() => {
64+
if (!block.props.blockUploadUrl || block.props.type !== 'loading') {
65+
return;
66+
}
67+
68+
const url = block.props.blockUploadUrl;
69+
70+
loopCheckDocMediaStatus(url)
71+
.then((response) => {
72+
// Replace the loading block with the resource block (image, audio, video, pdf ...)
73+
editor.replaceBlocks(
74+
[block.id],
75+
[
76+
{
77+
type: block.props.blockUploadType,
78+
props: {
79+
url: `${mediaUrl}${response.file}`,
80+
showPreview: block.props.blockUploadShowPreview,
81+
name: block.props.blockUploadName,
82+
caption: '',
83+
backgroundColor: 'default',
84+
textAlignment: 'left',
85+
},
86+
} as never,
87+
],
88+
);
89+
})
90+
.catch((error) => {
91+
console.error('Error analyzing file:', error);
92+
93+
editor.updateBlock(block.id, {
94+
type: 'uploadLoader',
95+
props: {
96+
type: 'warning',
97+
information: t(
98+
'The antivirus has detected an anomaly in your file.',
99+
),
100+
},
101+
});
102+
});
103+
}, [block, editor, mediaUrl]);
104+
105+
return (
106+
<Box className="bn-visual-media-wrapper" $direction="row" $gap="0.5rem">
107+
{block.props.type === 'warning' ? (
108+
<Warning />
109+
) : (
110+
<Loader style={{ animation: 'spin 1.5s linear infinite' }} />
111+
)}
112+
<Text>{block.props.information}</Text>
113+
</Box>
114+
);
115+
};
116+
8117
export const UploadLoaderBlock = createReactBlockSpec(
9118
{
10119
type: 'uploadLoader',
11120
propSchema: {
12-
information: { default: '' as const },
121+
information: { default: '' },
13122
type: {
14-
default: 'loading' as const,
15-
values: ['loading', 'warning'] as const,
123+
default: 'loading',
124+
values: ['loading', 'warning'],
125+
},
126+
blockUploadName: { default: '' },
127+
blockUploadShowPreview: { default: true },
128+
blockUploadType: {
129+
default: '',
16130
},
131+
blockUploadUrl: { default: '' },
17132
},
18133
content: 'none',
19134
},
20135
{
21-
render: ({ block }) => {
22-
return (
23-
<Box className="bn-visual-media-wrapper" $direction="row" $gap="0.5rem">
24-
{block.props.type === 'warning' ? (
25-
<Warning />
26-
) : (
27-
<Loader style={{ animation: 'spin 1.5s linear infinite' }} />
28-
)}
29-
<Text>{block.props.information}</Text>
30-
</Box>
31-
);
32-
},
136+
render: (props) => <UploadLoaderBlockComponent {...props} />,
33137
},
34138
);

0 commit comments

Comments
 (0)