Skip to content

Commit 7b6351f

Browse files
committed
fix: proper error handling
1 parent a96c486 commit 7b6351f

File tree

2 files changed

+83
-21
lines changed

2 files changed

+83
-21
lines changed

src/containers/Tenant/Query/QueryResult/components/PlanToSvgButton/PlanToSvgButton.tsx

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22

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

77
import {planToSvgApi} from '../../../../../../store/reducers/planToSvg';
88
import type {QueryPlan, ScriptPlan} from '../../../../../../types/api/query';
@@ -40,26 +40,33 @@ export function PlanToSvgButton({plan, database}: PlanToSvgButtonProps) {
4040
return url;
4141
})
4242
.catch((err) => {
43-
setError(JSON.stringify(err));
44-
throw err;
43+
const errorMessage = err.data?.message || err.message || JSON.stringify(err);
44+
setError(errorMessage);
45+
return null;
4546
});
4647
}, [database, getPlanToSvg, plan, blobUrl]);
4748

4849
const handleOpenInNewTab = React.useCallback(() => {
4950
handleGetSvg().then((url) => {
50-
window.open(url, '_blank');
51+
if (url) {
52+
window.open(url, '_blank');
53+
}
5154
});
55+
return;
5256
}, [handleGetSvg]);
5357

5458
const handleDownload = React.useCallback(() => {
5559
handleGetSvg().then((url) => {
5660
const link = document.createElement('a');
57-
link.href = url;
58-
link.download = 'query-plan.svg';
59-
document.body.appendChild(link);
60-
link.click();
61-
document.body.removeChild(link);
61+
if (url) {
62+
link.href = url;
63+
link.download = 'query-plan.svg';
64+
document.body.appendChild(link);
65+
link.click();
66+
document.body.removeChild(link);
67+
}
6268
});
69+
return;
6370
}, [handleGetSvg]);
6471

6572
React.useEffect(() => {
@@ -85,19 +92,21 @@ export function PlanToSvgButton({plan, database}: PlanToSvgButtonProps) {
8592

8693
const renderSwitcher = (props: ButtonProps) => {
8794
return (
88-
<Button
89-
view={getButtonView(error, isLoading)}
90-
loading={isLoading}
91-
disabled={isLoading}
92-
{...props}
93-
>
94-
{i18n('text_plan-svg')}
95-
<Button.Icon>
96-
<ChevronDown />
97-
</Button.Icon>
98-
</Button>
95+
<Tooltip content={error ? i18n('text_error-plan-svg', {error}) : i18n('text_plan-svg')}>
96+
<Button
97+
view={getButtonView(error, isLoading)}
98+
loading={isLoading}
99+
disabled={isLoading}
100+
{...props}
101+
>
102+
{i18n('text_plan-svg')}
103+
<Button.Icon>
104+
<ChevronDown />
105+
</Button.Icon>
106+
</Button>
107+
</Tooltip>
99108
);
100109
};
101110

102-
return <DropdownMenu renderSwitcher={renderSwitcher} items={items} />;
111+
return <DropdownMenu renderSwitcher={renderSwitcher} items={items} disabled={Boolean(error)} />;
103112
}

tests/suites/tenant/queryEditor/planToSvg.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,59 @@ test.describe('Test Plan to SVG functionality', async () => {
8989
expect(download.suggestedFilename()).toBe('query-plan.svg');
9090
});
9191

92+
test('Plan to SVG handles API errors correctly', async ({page}) => {
93+
const queryEditor = new QueryEditor(page);
94+
95+
// 1. Turn on Plan to SVG experiment
96+
await toggleExperiment(page, 'on', 'Execution plan');
97+
98+
// 2. Set query and run it
99+
await queryEditor.setQuery(testQuery);
100+
await queryEditor.clickRunButton();
101+
102+
// 3. Wait for query execution to complete
103+
await expect(async () => {
104+
const status = await queryEditor.getExecutionStatus();
105+
expect(status).toBe('Completed');
106+
}).toPass();
107+
108+
// 4. Mock the plan2svg API to return an error
109+
await page.route('**/plan2svg**', (route) => {
110+
route.fulfill({
111+
status: 500,
112+
contentType: 'application/json',
113+
body: JSON.stringify({message: 'Failed to generate SVG'}),
114+
});
115+
});
116+
117+
// 5. Click execution plan button to open dropdown
118+
const executionPlanButton = page.locator('button:has-text("Execution plan")');
119+
await executionPlanButton.click();
120+
121+
// 6. Click "Open in new tab" option and wait for error state
122+
const openInNewTabOption = page.locator('text="Open in new tab"');
123+
await openInNewTabOption.click();
124+
await page.waitForTimeout(1000); // Wait for error to be processed
125+
126+
// 7. Close the dropdown
127+
await page.keyboard.press('Escape');
128+
129+
// 8. Verify error state
130+
await expect(executionPlanButton).toHaveClass(/flat-danger/);
131+
132+
// 9. Verify error tooltip
133+
await executionPlanButton.hover();
134+
await page.waitForTimeout(500); // Wait for tooltip animation
135+
const tooltipText = await page.textContent('.g-tooltip');
136+
expect(tooltipText).toContain('Error');
137+
expect(tooltipText).toContain('Failed to generate SVG');
138+
139+
// 10. Verify dropdown is disabled after error
140+
await executionPlanButton.click();
141+
await expect(openInNewTabOption).not.toBeVisible();
142+
await expect(page.locator('text="Download"')).not.toBeVisible();
143+
});
144+
92145
test('Statistics setting becomes disabled when execution plan experiment is enabled', async ({
93146
page,
94147
}) => {

0 commit comments

Comments
 (0)