Skip to content

fix: execution plan svg not saving in chrome #1744

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

Merged
merged 4 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
@@ -1,7 +1,8 @@
import React from 'react';

import {ArrowUpRightFromSquare} from '@gravity-ui/icons';
import {Button, Tooltip} from '@gravity-ui/uikit';
import {ArrowDownToLine, ArrowUpRightFromSquare, ChevronDown} from '@gravity-ui/icons';
import type {ButtonProps} from '@gravity-ui/uikit';
import {Button, DropdownMenu} from '@gravity-ui/uikit';

import {planToSvgApi} from '../../../../../../store/reducers/planToSvg';
import type {QueryPlan, ScriptPlan} from '../../../../../../types/api/query';
Expand All @@ -24,20 +25,42 @@ export function PlanToSvgButton({plan, database}: PlanToSvgButtonProps) {
const [blobUrl, setBlobUrl] = React.useState<string | null>(null);
const [getPlanToSvg, {isLoading}] = planToSvgApi.useLazyPlanToSvgQueryQuery();

const handleClick = React.useCallback(() => {
getPlanToSvg({plan, database})
const handleGetSvg = React.useCallback(() => {
if (blobUrl) {
return Promise.resolve(blobUrl);
}

return getPlanToSvg({plan, database})
.unwrap()
.then((result) => {
const blob = new Blob([result], {type: 'image/svg+xml'});
const url = URL.createObjectURL(blob);
setBlobUrl(url);
setError(null);
window.open(url, '_blank');
return url;
})
.catch((err) => {
setError(JSON.stringify(err));
throw err;
});
}, [database, getPlanToSvg, plan]);
}, [database, getPlanToSvg, plan, blobUrl]);

const handleOpenInNewTab = React.useCallback(() => {
handleGetSvg().then((url) => {
window.open(url, '_blank');
});
}, [handleGetSvg]);

const handleDownload = React.useCallback(() => {
handleGetSvg().then((url) => {
const link = document.createElement('a');
link.href = url;
link.download = 'query-plan.svg';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
}, [handleGetSvg]);

React.useEffect(() => {
return () => {
Expand All @@ -47,21 +70,34 @@ export function PlanToSvgButton({plan, database}: PlanToSvgButtonProps) {
};
}, [blobUrl]);

return (
<Tooltip
content={error ? i18n('text_error-plan-svg', {error}) : i18n('text_open-plan-svg')}
>
const items = [
{
text: i18n('text_open-new-tab'),
icon: <ArrowUpRightFromSquare />,
action: handleOpenInNewTab,
},
{
text: i18n('text_download'),
icon: <ArrowDownToLine />,
action: handleDownload,
},
];

const renderSwitcher = (props: ButtonProps) => {
return (
<Button
view={getButtonView(error, isLoading)}
loading={isLoading}
onClick={handleClick}
disabled={isLoading}
{...props}
>
{i18n('text_plan-svg')}
<Button.Icon>
<ArrowUpRightFromSquare />
<ChevronDown />
</Button.Icon>
</Button>
</Tooltip>
);
);
};

return <DropdownMenu renderSwitcher={renderSwitcher} items={items} />;
}
3 changes: 2 additions & 1 deletion src/containers/Tenant/Query/QueryResult/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"title.truncated": "Truncated",
"title.result": "Result",
"text_plan-svg": "Execution plan",
"text_open-plan-svg": "Open execution plan in new window",
"text_open-new-tab": "Open in new tab",
"text_download": "Download",
"text_error-plan-svg": "Error: {{error}}"
}
47 changes: 44 additions & 3 deletions tests/suites/tenant/queryEditor/planToSvg.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ test.describe('Test Plan to SVG functionality', async () => {
await tenantPage.goto(pageQueryParams);
});

test('Plan to SVG experiment shows execution plan in new tab', async ({page}) => {
test('Plan to SVG dropdown shows options and opens plan in new tab', async ({page}) => {
const queryEditor = new QueryEditor(page);

// 1. Turn on Plan to SVG experiment
Expand All @@ -37,17 +37,58 @@ test.describe('Test Plan to SVG functionality', async () => {
expect(status).toBe('Completed');
}).toPass();

// 4. Check if Execution Plan button appears and click it
// 4. Check if Execution Plan button appears and click it to open dropdown
const executionPlanButton = page.locator('button:has-text("Execution plan")');
await expect(executionPlanButton).toBeVisible();
await executionPlanButton.click();

// 5. Verify dropdown menu items are visible
const openInNewTabOption = page.locator('text="Open in new tab"');
const downloadOption = page.locator('text="Download"');
await expect(openInNewTabOption).toBeVisible();
await expect(downloadOption).toBeVisible();

// 6. Click "Open in new tab" option
await openInNewTabOption.click();
await page.waitForTimeout(1000); // Wait for new tab to open

// 5. Verify we're taken to a new tab with SVG content
// 7. Verify we're taken to a new tab with SVG content
const svgElement = page.locator('svg').first();
await expect(svgElement).toBeVisible();
});

test('Plan to SVG download option triggers file download', async ({page}) => {
const queryEditor = new QueryEditor(page);

// 1. Turn on Plan to SVG experiment
await toggleExperiment(page, 'on', 'Execution plan');

// 2. Set query and run it
await queryEditor.setQuery(testQuery);
await queryEditor.clickRunButton();

// 3. Wait for query execution to complete
await expect(async () => {
const status = await queryEditor.getExecutionStatus();
expect(status).toBe('Completed');
}).toPass();

// 4. Click execution plan button to open dropdown
const executionPlanButton = page.locator('button:has-text("Execution plan")');
await executionPlanButton.click();

// 5. Setup download listener before clicking download
const downloadPromise = page.waitForEvent('download');

// 6. Click download option
const downloadOption = page.locator('text="Download"');
await downloadOption.click();

// 7. Wait for download to start and verify filename
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe('query-plan.svg');
});

test('Statistics setting becomes disabled when execution plan experiment is enabled', async ({
page,
}) => {
Expand Down
Loading