Skip to content

Commit 19d7f56

Browse files
astandrikAnton Standrik
andauthored
fix: change trace and svg mutations to lazy query (#1640)
Co-authored-by: Anton Standrik <astandrik@Antons-MacBook-Air.local>
1 parent 71956ec commit 19d7f56

File tree

14 files changed

+555
-177
lines changed

14 files changed

+555
-177
lines changed

src/containers/Tenant/Query/ExecuteResult/PlanToSvgButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ interface PlanToSvgButtonProps {
2323
export function PlanToSvgButton({plan, database}: PlanToSvgButtonProps) {
2424
const [error, setError] = React.useState<string | null>(null);
2525
const [blobUrl, setBlobUrl] = React.useState<string | null>(null);
26-
const [getPlanToSvg, {isLoading}] = planToSvgApi.usePlanToSvgQueryMutation();
26+
const [getPlanToSvg, {isLoading}] = planToSvgApi.useLazyPlanToSvgQueryQuery();
2727

2828
const handleClick = React.useCallback(() => {
2929
getPlanToSvg({plan, database})

src/containers/Tenant/Query/ExecuteResult/TraceButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function TraceButton({traceId, isTraceReady}: TraceUrlButtonProps) {
2020
const checkTraceUrl = traceCheck?.url ? replaceParams(traceCheck.url, {traceId}) : '';
2121
const traceUrl = traceView?.url ? replaceParams(traceView.url, {traceId}) : '';
2222

23-
const [checkTrace, {isLoading, isUninitialized}] = traceApi.useCheckTraceMutation();
23+
const [checkTrace, {isLoading, isUninitialized}] = traceApi.useLazyCheckTraceQuery();
2424

2525
React.useEffect(() => {
2626
let checkTraceMutation: {abort: () => void} | null;

src/store/reducers/planToSvg.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export interface PlanToSvgQueryParams {
99

1010
export const planToSvgApi = api.injectEndpoints({
1111
endpoints: (build) => ({
12-
planToSvgQuery: build.mutation<string, PlanToSvgQueryParams>({
12+
planToSvgQuery: build.query<string, PlanToSvgQueryParams>({
1313
queryFn: async ({plan, database}, {signal}) => {
1414
try {
1515
const response = await window.api.planToSvg(

src/store/reducers/trace.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ interface CheckTraceParams {
77

88
export const traceApi = api.injectEndpoints({
99
endpoints: (build) => ({
10-
checkTrace: build.mutation({
10+
checkTrace: build.query({
1111
queryFn: async ({url}: CheckTraceParams, {signal, dispatch}) => {
1212
try {
1313
const response = await window.api.checkTrace({url}, {signal});

tests/suites/nodes/nodes.test.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {expect, test} from '@playwright/test';
22

3+
import {toggleExperiment} from '../../utils/toggleExperiment';
34
import {NodesPage} from '../nodes/NodesPage';
45
import {PaginatedTable} from '../paginatedTable/paginatedTable';
56

@@ -28,12 +29,7 @@ test.describe('Test Nodes Paginated Table', async () => {
2829
expect(response?.ok()).toBe(true);
2930

3031
// Wil be removed since it's an experiment
31-
await page.evaluate(() => {
32-
localStorage.setItem('useBackendParamsForTables', 'true');
33-
location.reload();
34-
});
35-
36-
await page.waitForLoadState('networkidle');
32+
await toggleExperiment(page, 'on', 'Use paginated tables');
3733
});
3834

3935
test('Table loads and displays data', async ({page}) => {

tests/suites/sidebar/Sidebar.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import type {Locator, Page} from '@playwright/test';
2+
3+
export class Sidebar {
4+
private sidebarContainer: Locator;
5+
private logoButton: Locator;
6+
private footer: Locator;
7+
private settingsButton: Locator;
8+
private documentationButton: Locator;
9+
private accountButton: Locator;
10+
private collapseButton: Locator;
11+
private drawer: Locator;
12+
private drawerMenu: Locator;
13+
private experimentsSection: Locator;
14+
15+
constructor(page: Page) {
16+
this.sidebarContainer = page.locator('.gn-aside-header__aside-content');
17+
this.logoButton = this.sidebarContainer.locator('.gn-logo__btn-logo');
18+
this.footer = this.sidebarContainer.locator('.gn-aside-header__footer');
19+
this.drawer = page.locator('.gn-drawer');
20+
this.drawerMenu = page.locator('.gn-settings-menu');
21+
this.experimentsSection = this.drawerMenu
22+
.locator('.gn-settings-menu__item')
23+
.filter({hasText: 'Experiments'});
24+
25+
// Footer buttons with specific icons
26+
const footerItems = this.sidebarContainer.locator('.gn-footer-item');
27+
this.documentationButton = footerItems.filter({hasText: 'Documentation'});
28+
this.settingsButton = footerItems
29+
.filter({hasText: 'Settings'})
30+
.locator('.gn-composite-bar-item__btn-icon');
31+
this.accountButton = footerItems.filter({hasText: 'Account'});
32+
33+
this.collapseButton = this.sidebarContainer.locator('.gn-collapse-button');
34+
}
35+
36+
async waitForSidebarToLoad() {
37+
await this.sidebarContainer.waitFor({state: 'visible'});
38+
}
39+
40+
async isSidebarVisible() {
41+
return this.sidebarContainer.isVisible();
42+
}
43+
44+
async isLogoButtonVisible() {
45+
return this.logoButton.isVisible();
46+
}
47+
48+
async isSettingsButtonVisible() {
49+
return this.settingsButton.isVisible();
50+
}
51+
52+
async isDocumentationButtonVisible() {
53+
return this.documentationButton.isVisible();
54+
}
55+
56+
async isAccountButtonVisible() {
57+
return this.accountButton.isVisible();
58+
}
59+
60+
async clickLogoButton() {
61+
await this.logoButton.click();
62+
}
63+
64+
async clickSettings() {
65+
await this.settingsButton.click();
66+
}
67+
68+
async clickDocumentation() {
69+
await this.documentationButton.click();
70+
}
71+
72+
async clickAccount() {
73+
await this.accountButton.click();
74+
}
75+
76+
async toggleCollapse() {
77+
await this.collapseButton.click();
78+
}
79+
80+
async isCollapsed() {
81+
const button = await this.collapseButton;
82+
const title = await button.getAttribute('title');
83+
return title === 'Expand';
84+
}
85+
86+
async getFooterItemsCount(): Promise<number> {
87+
return this.footer.locator('.gn-composite-bar-item').count();
88+
}
89+
90+
async isFooterItemVisible(index: number) {
91+
const items = this.footer.locator('.gn-composite-bar-item');
92+
return items.nth(index).isVisible();
93+
}
94+
95+
async clickFooterItem(index: number) {
96+
const items = this.footer.locator('.gn-composite-bar-item');
97+
await items.nth(index).click();
98+
}
99+
100+
async getFooterItemText(index: number): Promise<string> {
101+
const items = this.footer.locator('.gn-composite-bar-item');
102+
const item = items.nth(index);
103+
return item.locator('.gn-composite-bar-item__title-text').innerText();
104+
}
105+
106+
async isDrawerVisible() {
107+
return this.drawer.isVisible();
108+
}
109+
110+
async getDrawerMenuItems(): Promise<string[]> {
111+
const items = this.drawerMenu.locator('.gn-settings-menu__item >> span');
112+
const count = await items.count();
113+
const texts: string[] = [];
114+
for (let i = 0; i < count; i++) {
115+
texts.push(await items.nth(i).innerText());
116+
}
117+
return texts;
118+
}
119+
120+
async clickExperimentsSection() {
121+
await this.experimentsSection.click();
122+
}
123+
124+
async toggleExperimentByTitle(title: string) {
125+
const experimentItem = this.drawer
126+
.locator('.gn-settings__item-title')
127+
.filter({hasText: title});
128+
// Click the label element which wraps the switch, avoiding the slider that intercepts events
129+
const switchLabel = experimentItem.locator(
130+
'xpath=../../..//label[contains(@class, "g-control-label")]',
131+
);
132+
await switchLabel.click();
133+
}
134+
135+
async getFirstExperimentTitle(): Promise<string> {
136+
const experimentItem = this.drawer.locator('.gn-settings__item-title').first();
137+
return experimentItem.innerText();
138+
}
139+
140+
async isExperimentEnabled(title: string): Promise<boolean> {
141+
const experimentItem = this.drawer
142+
.locator('.gn-settings__item-title')
143+
.filter({hasText: title});
144+
const switchControl = experimentItem.locator('xpath=../../..//input[@type="checkbox"]');
145+
return switchControl.isChecked();
146+
}
147+
}

tests/suites/sidebar/sidebar.test.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import {expect, test} from '@playwright/test';
2+
3+
import {PageModel} from '../../models/PageModel';
4+
import {toggleExperiment} from '../../utils/toggleExperiment';
5+
6+
import {Sidebar} from './Sidebar';
7+
8+
test.describe('Test Sidebar', async () => {
9+
test.beforeEach(async ({page}) => {
10+
const basePage = new PageModel(page);
11+
const response = await basePage.goto();
12+
expect(response?.ok()).toBe(true);
13+
});
14+
15+
test('Sidebar is visible and loads correctly', async ({page}) => {
16+
const sidebar = new Sidebar(page);
17+
await sidebar.waitForSidebarToLoad();
18+
await expect(sidebar.isSidebarVisible()).resolves.toBe(true);
19+
});
20+
21+
test('Logo button is visible and clickable', async ({page}) => {
22+
const sidebar = new Sidebar(page);
23+
await sidebar.waitForSidebarToLoad();
24+
await expect(sidebar.isLogoButtonVisible()).resolves.toBe(true);
25+
await sidebar.clickLogoButton();
26+
});
27+
28+
test('Settings button is visible and clickable', async ({page}) => {
29+
const sidebar = new Sidebar(page);
30+
await sidebar.waitForSidebarToLoad();
31+
await expect(sidebar.isSettingsButtonVisible()).resolves.toBe(true);
32+
await sidebar.clickSettings();
33+
});
34+
35+
test('Settings button click opens drawer with correct sections', async ({page}) => {
36+
const sidebar = new Sidebar(page);
37+
await sidebar.waitForSidebarToLoad();
38+
39+
// Initially drawer should not be visible
40+
await expect(sidebar.isDrawerVisible()).resolves.toBe(false);
41+
42+
// Click settings button
43+
await sidebar.clickSettings();
44+
await page.waitForTimeout(500); // Wait for animation
45+
46+
// Drawer should become visible
47+
await expect(sidebar.isDrawerVisible()).resolves.toBe(true);
48+
49+
// Verify drawer menu items
50+
const menuItems = await sidebar.getDrawerMenuItems();
51+
expect(menuItems).toEqual(['General', 'Editor', 'Experiments', 'About']);
52+
});
53+
54+
test('Documentation button is visible and clickable', async ({page}) => {
55+
const sidebar = new Sidebar(page);
56+
await sidebar.waitForSidebarToLoad();
57+
await expect(sidebar.isDocumentationButtonVisible()).resolves.toBe(true);
58+
await sidebar.clickDocumentation();
59+
});
60+
61+
test('Account button is visible and clickable', async ({page}) => {
62+
const sidebar = new Sidebar(page);
63+
await sidebar.waitForSidebarToLoad();
64+
await expect(sidebar.isAccountButtonVisible()).resolves.toBe(true);
65+
await sidebar.clickAccount();
66+
});
67+
68+
test('Sidebar can be collapsed and expanded', async ({page}) => {
69+
const sidebar = new Sidebar(page);
70+
await sidebar.waitForSidebarToLoad();
71+
72+
// Initially collapsed
73+
await expect(sidebar.isCollapsed()).resolves.toBe(true);
74+
75+
// Expand
76+
await sidebar.toggleCollapse();
77+
await page.waitForTimeout(500); // Wait for animation
78+
await expect(sidebar.isCollapsed()).resolves.toBe(false);
79+
80+
// Collapse
81+
await sidebar.toggleCollapse();
82+
await page.waitForTimeout(500); // Wait for animation
83+
await expect(sidebar.isCollapsed()).resolves.toBe(true);
84+
});
85+
86+
test('Footer items are visible', async ({page}) => {
87+
const sidebar = new Sidebar(page);
88+
await sidebar.waitForSidebarToLoad();
89+
90+
const itemsCount = await sidebar.getFooterItemsCount();
91+
expect(itemsCount).toBeGreaterThan(0);
92+
});
93+
94+
test('Can toggle experiments in settings', async ({page}) => {
95+
const sidebar = new Sidebar(page);
96+
await sidebar.clickSettings();
97+
await page.waitForTimeout(500); // Wait for animation
98+
await sidebar.clickExperimentsSection();
99+
const experimentTitle = await sidebar.getFirstExperimentTitle();
100+
101+
await toggleExperiment(page, 'on', experimentTitle);
102+
await sidebar.clickSettings();
103+
await page.waitForTimeout(500); // Wait for animation
104+
await sidebar.clickExperimentsSection();
105+
const newState = await sidebar.isExperimentEnabled(experimentTitle);
106+
expect(newState).toBe(true);
107+
108+
await toggleExperiment(page, 'off', experimentTitle);
109+
await sidebar.clickSettings();
110+
await page.waitForTimeout(500); // Wait for animation
111+
await sidebar.clickExperimentsSection();
112+
const finalState = await sidebar.isExperimentEnabled(experimentTitle);
113+
expect(finalState).toBe(false);
114+
});
115+
});

tests/suites/tenant/TenantPage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type {Locator, Page} from '@playwright/test';
33
import {PageModel} from '../../models/PageModel';
44
import {tenantPage} from '../../utils/constants';
55

6-
export const VISIBILITY_TIMEOUT = 5000;
6+
export const VISIBILITY_TIMEOUT = 10000;
77

88
export enum NavigationTabs {
99
Query = 'Query',
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import {expect, test} from '@playwright/test';
2+
3+
import {tenantName} from '../../../utils/constants';
4+
import {toggleExperiment} from '../../../utils/toggleExperiment';
5+
import {TenantPage} from '../TenantPage';
6+
7+
import {ButtonNames, QueryEditor} from './QueryEditor';
8+
9+
test.describe('Test Plan to SVG functionality', async () => {
10+
const testQuery = 'SELECT 1;'; // Simple query that will generate a plan
11+
12+
test.beforeEach(async ({page}) => {
13+
const pageQueryParams = {
14+
schema: tenantName,
15+
database: tenantName,
16+
general: 'query',
17+
};
18+
19+
const tenantPage = new TenantPage(page);
20+
await tenantPage.goto(pageQueryParams);
21+
});
22+
23+
test('Plan to SVG experiment shows execution plan in new tab', async ({page}) => {
24+
const queryEditor = new QueryEditor(page);
25+
26+
// 1. Turn on Plan to SVG experiment
27+
await toggleExperiment(page, 'on', 'Plan to SVG');
28+
29+
// 2. Set stats level to Full
30+
await queryEditor.clickGearButton();
31+
await queryEditor.settingsDialog.changeStatsLevel('Full');
32+
await queryEditor.settingsDialog.clickButton(ButtonNames.Save);
33+
34+
// 3. Set query and run it
35+
await queryEditor.setQuery(testQuery);
36+
await queryEditor.clickRunButton();
37+
38+
// 4. Wait for query execution to complete
39+
await expect(async () => {
40+
const status = await queryEditor.getExecutionStatus();
41+
expect(status).toBe('Completed');
42+
}).toPass();
43+
44+
// 5. Check if Execution Plan button appears and click it
45+
const executionPlanButton = page.locator('button:has-text("Execution plan")');
46+
await expect(executionPlanButton).toBeVisible();
47+
await executionPlanButton.click();
48+
await page.waitForTimeout(1000); // Wait for new tab to open
49+
50+
// 6. Verify we're taken to a new tab with SVG content
51+
const svgElement = page.locator('svg').first();
52+
await expect(svgElement).toBeVisible();
53+
});
54+
});

0 commit comments

Comments
 (0)