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(Dashboard download): Download dashboard screenshot/PDF using SupersetClient #30212

Merged
merged 2 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ describe('DownloadScreenshot component', () => {
const props = defaultProps();

fetchMock.post(
`glob:*/api/v1/dashboard/${props.dashboardId}/cache_dashboard_screenshot`,
`glob:*/api/v1/dashboard/${props.dashboardId}/cache_dashboard_screenshot/`,
{
status: 400,
body: {},
Expand All @@ -105,19 +105,23 @@ describe('DownloadScreenshot component', () => {
test('displays success message when API call succeeds', async () => {
const props = defaultProps();
fetchMock.post(
`glob:*/api/v1/dashboard/${props.dashboardId}/cache_dashboard_screenshot`,
`glob:*/api/v1/dashboard/${props.dashboardId}/cache_dashboard_screenshot/`,
{
status: 200,
body: {
image_url: 'mocked_image_url',
cache_key: 'mocked_cache_key',
},
},
);

fetchMock.get('glob:*/mocked_image_url?download_format=pdf', {
status: 200,
body: {},
});
fetchMock.get(
`glob:*/api/v1/dashboard/${props.dashboardId}/screenshot/mocked_cache_key/?download_format=pdf`,
{
status: 200,
body: {},
},
);

renderComponent();

Expand All @@ -130,14 +134,14 @@ describe('DownloadScreenshot component', () => {
});
});

test('throws error when no image URL is provided', async () => {
test('throws error when no image cache key is provided', async () => {
const props = defaultProps();
fetchMock.post(
`glob:*/api/v1/dashboard/${props.dashboardId}/cache_dashboard_screenshot`,
`glob:*/api/v1/dashboard/${props.dashboardId}/cache_dashboard_screenshot/`,
{
status: 200,
body: {
image_url: '',
cache_key: '',
},
},
);
Expand All @@ -156,24 +160,27 @@ describe('DownloadScreenshot component', () => {

test('displays success message when image retrieval succeeds', async () => {
const props = defaultProps();
const imageUrl = 'glob:*/mocked_image_url?download_format=pdf';
fetchMock.post(
`glob:*/api/v1/dashboard/${props.dashboardId}/cache_dashboard_screenshot`,
`glob:*/api/v1/dashboard/${props.dashboardId}/cache_dashboard_screenshot/`,
{
status: 200,
body: {
image_url: 'mocked_image_url',
cache_key: 'mocked_cache_key',
},
},
);

fetchMock.get(imageUrl, {
status: 200,
headers: {
'Content-Type': 'image/png',
fetchMock.get(
`glob:*/api/v1/dashboard/${props.dashboardId}/screenshot/mocked_cache_key/?download_format=pdf`,
{
status: 200,
headers: {
'Content-Type': 'application/pdf',
},
body: new Blob([], { type: 'application/pdf' }),
},
body: new Blob([], { type: 'image/png' }),
});
);

global.URL.createObjectURL = jest.fn(() => 'mockedObjectURL');
global.URL.revokeObjectURL = jest.fn();
Expand All @@ -185,7 +192,11 @@ describe('DownloadScreenshot component', () => {
userEvent.click(screen.getByRole('button', { name: 'Download' }));

await waitFor(() => {
expect(fetchMock.calls(imageUrl).length).toBe(1);
expect(
fetchMock.calls(
`glob:*/api/v1/dashboard/${props.dashboardId}/screenshot/mocked_cache_key/?download_format=pdf`,
).length,
).toBe(1);
});

// Wait for the successful image retrieval message
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
* under the License.
*/

import { logging, t, SupersetClient } from '@superset-ui/core';
import {
logging,
t,
SupersetClient,
SupersetApiError,
} from '@superset-ui/core';
import { Menu } from 'src/components/Menu';
import {
LOG_ACTIONS_DASHBOARD_DOWNLOAD_AS_IMAGE,
Expand Down Expand Up @@ -63,14 +68,13 @@ export default function DownloadScreenshot({
let retries = 0;

// this function checks if the image is ready
const checkImageReady = (imageUrl: string) =>
fetch(`${imageUrl}?download_format=${format}`)
.then(response => {
if (response.status === 404) {
throw new Error('Image not ready');
}
return response.blob();
})
const checkImageReady = (cacheKey: string) =>
SupersetClient.get({
endpoint: `/api/v1/dashboard/${dashboardId}/screenshot/${cacheKey}/?download_format=${format}`,
headers: { Accept: 'application/pdf, image/png' },
parseMethod: 'raw',
})
.then((response: Response) => response.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
Expand All @@ -80,11 +84,16 @@ export default function DownloadScreenshot({
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
})
.catch(err => {
if ((err as SupersetApiError).status === 404) {
throw new Error('Image not ready');
}
});

// this is the functions that handles the retries
const fetchImageWithRetry = (imageUrl: string) => {
checkImageReady(imageUrl)
const fetchImageWithRetry = (cacheKey: string) => {
checkImageReady(cacheKey)
.then(() => {
addSuccessToast(t('The screenshot is now being downloaded.'));
})
Expand All @@ -100,7 +109,7 @@ export default function DownloadScreenshot({
noDuplicate: true,
},
);
setTimeout(() => fetchImageWithRetry(imageUrl), RETRY_INTERVAL);
setTimeout(() => fetchImageWithRetry(cacheKey), RETRY_INTERVAL);
} else {
addDangerToast(
t(
Expand All @@ -113,24 +122,24 @@ export default function DownloadScreenshot({
};

SupersetClient.post({
endpoint: `/api/v1/dashboard/${dashboardId}/cache_dashboard_screenshot`,
endpoint: `/api/v1/dashboard/${dashboardId}/cache_dashboard_screenshot/`,
jsonPayload: {
anchor,
activeTabs,
dataMask,
},
})
.then(({ json }) => {
const imageUrl = json?.image_url;
if (!imageUrl) {
const cacheKey = json?.cache_key;
if (!cacheKey) {
throw new Error('No image URL in response');
}
addInfoToast(
t(
'The screenshot is being generated. Please, do not leave the page.',
),
);
fetchImageWithRetry(imageUrl);
fetchImageWithRetry(cacheKey);
})
.catch(error => {
logging.error(error);
Expand Down
Loading