Skip to content

fix: change trace and svg mutations to lazy query #1640

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 9 commits into from
Nov 18, 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 @@ -23,7 +23,7 @@ interface PlanToSvgButtonProps {
export function PlanToSvgButton({plan, database}: PlanToSvgButtonProps) {
const [error, setError] = React.useState<string | null>(null);
const [blobUrl, setBlobUrl] = React.useState<string | null>(null);
const [getPlanToSvg, {isLoading}] = planToSvgApi.usePlanToSvgQueryMutation();
const [getPlanToSvg, {isLoading}] = planToSvgApi.useLazyPlanToSvgQueryQuery();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thats actual changes


const handleClick = React.useCallback(() => {
getPlanToSvg({plan, database})
Expand Down
2 changes: 1 addition & 1 deletion src/containers/Tenant/Query/ExecuteResult/TraceButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function TraceButton({traceId, isTraceReady}: TraceUrlButtonProps) {
const checkTraceUrl = traceCheck?.url ? replaceParams(traceCheck.url, {traceId}) : '';
const traceUrl = traceView?.url ? replaceParams(traceView.url, {traceId}) : '';

const [checkTrace, {isLoading, isUninitialized}] = traceApi.useCheckTraceMutation();
const [checkTrace, {isLoading, isUninitialized}] = traceApi.useLazyCheckTraceQuery();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thats actual changes


React.useEffect(() => {
let checkTraceMutation: {abort: () => void} | null;
Expand Down
2 changes: 1 addition & 1 deletion src/store/reducers/planToSvg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface PlanToSvgQueryParams {

export const planToSvgApi = api.injectEndpoints({
endpoints: (build) => ({
planToSvgQuery: build.mutation<string, PlanToSvgQueryParams>({
planToSvgQuery: build.query<string, PlanToSvgQueryParams>({
queryFn: async ({plan, database}, {signal}) => {
try {
const response = await window.api.planToSvg(
Expand Down
2 changes: 1 addition & 1 deletion src/store/reducers/trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface CheckTraceParams {

export const traceApi = api.injectEndpoints({
endpoints: (build) => ({
checkTrace: build.mutation({
checkTrace: build.query({
queryFn: async ({url}: CheckTraceParams, {signal, dispatch}) => {
try {
const response = await window.api.checkTrace({url}, {signal});
Expand Down
8 changes: 2 additions & 6 deletions tests/suites/nodes/nodes.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {expect, test} from '@playwright/test';

import {toggleExperiment} from '../../utils/toggleExperiment';
import {NodesPage} from '../nodes/NodesPage';
import {PaginatedTable} from '../paginatedTable/paginatedTable';

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

// Wil be removed since it's an experiment
await page.evaluate(() => {
localStorage.setItem('useBackendParamsForTables', 'true');
location.reload();
});

await page.waitForLoadState('networkidle');
await toggleExperiment(page, 'on', 'Use paginated tables');
});

test('Table loads and displays data', async ({page}) => {
Expand Down
147 changes: 147 additions & 0 deletions tests/suites/sidebar/Sidebar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import type {Locator, Page} from '@playwright/test';

export class Sidebar {
private sidebarContainer: Locator;
private logoButton: Locator;
private footer: Locator;
private settingsButton: Locator;
private documentationButton: Locator;
private accountButton: Locator;
private collapseButton: Locator;
private drawer: Locator;
private drawerMenu: Locator;
private experimentsSection: Locator;

constructor(page: Page) {
this.sidebarContainer = page.locator('.gn-aside-header__aside-content');
this.logoButton = this.sidebarContainer.locator('.gn-logo__btn-logo');
this.footer = this.sidebarContainer.locator('.gn-aside-header__footer');
this.drawer = page.locator('.gn-drawer');
this.drawerMenu = page.locator('.gn-settings-menu');
this.experimentsSection = this.drawerMenu
.locator('.gn-settings-menu__item')
.filter({hasText: 'Experiments'});

// Footer buttons with specific icons
const footerItems = this.sidebarContainer.locator('.gn-footer-item');
this.documentationButton = footerItems.filter({hasText: 'Documentation'});
this.settingsButton = footerItems
.filter({hasText: 'Settings'})
.locator('.gn-composite-bar-item__btn-icon');
this.accountButton = footerItems.filter({hasText: 'Account'});

this.collapseButton = this.sidebarContainer.locator('.gn-collapse-button');
}

async waitForSidebarToLoad() {
await this.sidebarContainer.waitFor({state: 'visible'});
}

async isSidebarVisible() {
return this.sidebarContainer.isVisible();
}

async isLogoButtonVisible() {
return this.logoButton.isVisible();
}

async isSettingsButtonVisible() {
return this.settingsButton.isVisible();
}

async isDocumentationButtonVisible() {
return this.documentationButton.isVisible();
}

async isAccountButtonVisible() {
return this.accountButton.isVisible();
}

async clickLogoButton() {
await this.logoButton.click();
}

async clickSettings() {
await this.settingsButton.click();
}

async clickDocumentation() {
await this.documentationButton.click();
}

async clickAccount() {
await this.accountButton.click();
}

async toggleCollapse() {
await this.collapseButton.click();
}

async isCollapsed() {
const button = await this.collapseButton;
const title = await button.getAttribute('title');
return title === 'Expand';
}

async getFooterItemsCount(): Promise<number> {
return this.footer.locator('.gn-composite-bar-item').count();
}

async isFooterItemVisible(index: number) {
const items = this.footer.locator('.gn-composite-bar-item');
return items.nth(index).isVisible();
}

async clickFooterItem(index: number) {
const items = this.footer.locator('.gn-composite-bar-item');
await items.nth(index).click();
}

async getFooterItemText(index: number): Promise<string> {
const items = this.footer.locator('.gn-composite-bar-item');
const item = items.nth(index);
return item.locator('.gn-composite-bar-item__title-text').innerText();
}

async isDrawerVisible() {
return this.drawer.isVisible();
}

async getDrawerMenuItems(): Promise<string[]> {
const items = this.drawerMenu.locator('.gn-settings-menu__item >> span');
const count = await items.count();
const texts: string[] = [];
for (let i = 0; i < count; i++) {
texts.push(await items.nth(i).innerText());
}
return texts;
}

async clickExperimentsSection() {
await this.experimentsSection.click();
}

async toggleExperimentByTitle(title: string) {
const experimentItem = this.drawer
.locator('.gn-settings__item-title')
.filter({hasText: title});
// Click the label element which wraps the switch, avoiding the slider that intercepts events
const switchLabel = experimentItem.locator(
'xpath=../../..//label[contains(@class, "g-control-label")]',
);
await switchLabel.click();
}

async getFirstExperimentTitle(): Promise<string> {
const experimentItem = this.drawer.locator('.gn-settings__item-title').first();
return experimentItem.innerText();
}

async isExperimentEnabled(title: string): Promise<boolean> {
const experimentItem = this.drawer
.locator('.gn-settings__item-title')
.filter({hasText: title});
const switchControl = experimentItem.locator('xpath=../../..//input[@type="checkbox"]');
return switchControl.isChecked();
}
}
115 changes: 115 additions & 0 deletions tests/suites/sidebar/sidebar.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {expect, test} from '@playwright/test';

import {PageModel} from '../../models/PageModel';
import {toggleExperiment} from '../../utils/toggleExperiment';

import {Sidebar} from './Sidebar';

test.describe('Test Sidebar', async () => {
test.beforeEach(async ({page}) => {
const basePage = new PageModel(page);
const response = await basePage.goto();
expect(response?.ok()).toBe(true);
});

test('Sidebar is visible and loads correctly', async ({page}) => {
const sidebar = new Sidebar(page);
await sidebar.waitForSidebarToLoad();
await expect(sidebar.isSidebarVisible()).resolves.toBe(true);
});

test('Logo button is visible and clickable', async ({page}) => {
const sidebar = new Sidebar(page);
await sidebar.waitForSidebarToLoad();
await expect(sidebar.isLogoButtonVisible()).resolves.toBe(true);
await sidebar.clickLogoButton();
});

test('Settings button is visible and clickable', async ({page}) => {
const sidebar = new Sidebar(page);
await sidebar.waitForSidebarToLoad();
await expect(sidebar.isSettingsButtonVisible()).resolves.toBe(true);
await sidebar.clickSettings();
});

test('Settings button click opens drawer with correct sections', async ({page}) => {
const sidebar = new Sidebar(page);
await sidebar.waitForSidebarToLoad();

// Initially drawer should not be visible
await expect(sidebar.isDrawerVisible()).resolves.toBe(false);

// Click settings button
await sidebar.clickSettings();
await page.waitForTimeout(500); // Wait for animation

// Drawer should become visible
await expect(sidebar.isDrawerVisible()).resolves.toBe(true);

// Verify drawer menu items
const menuItems = await sidebar.getDrawerMenuItems();
expect(menuItems).toEqual(['General', 'Editor', 'Experiments', 'About']);
});

test('Documentation button is visible and clickable', async ({page}) => {
const sidebar = new Sidebar(page);
await sidebar.waitForSidebarToLoad();
await expect(sidebar.isDocumentationButtonVisible()).resolves.toBe(true);
await sidebar.clickDocumentation();
});

test('Account button is visible and clickable', async ({page}) => {
const sidebar = new Sidebar(page);
await sidebar.waitForSidebarToLoad();
await expect(sidebar.isAccountButtonVisible()).resolves.toBe(true);
await sidebar.clickAccount();
});

test('Sidebar can be collapsed and expanded', async ({page}) => {
const sidebar = new Sidebar(page);
await sidebar.waitForSidebarToLoad();

// Initially collapsed
await expect(sidebar.isCollapsed()).resolves.toBe(true);

// Expand
await sidebar.toggleCollapse();
await page.waitForTimeout(500); // Wait for animation
await expect(sidebar.isCollapsed()).resolves.toBe(false);

// Collapse
await sidebar.toggleCollapse();
await page.waitForTimeout(500); // Wait for animation
await expect(sidebar.isCollapsed()).resolves.toBe(true);
});

test('Footer items are visible', async ({page}) => {
const sidebar = new Sidebar(page);
await sidebar.waitForSidebarToLoad();

const itemsCount = await sidebar.getFooterItemsCount();
expect(itemsCount).toBeGreaterThan(0);
});

test('Can toggle experiments in settings', async ({page}) => {
const sidebar = new Sidebar(page);
await sidebar.clickSettings();
await page.waitForTimeout(500); // Wait for animation
await sidebar.clickExperimentsSection();
const experimentTitle = await sidebar.getFirstExperimentTitle();

await toggleExperiment(page, 'on', experimentTitle);
await sidebar.clickSettings();
await page.waitForTimeout(500); // Wait for animation
await sidebar.clickExperimentsSection();
const newState = await sidebar.isExperimentEnabled(experimentTitle);
expect(newState).toBe(true);

await toggleExperiment(page, 'off', experimentTitle);
await sidebar.clickSettings();
await page.waitForTimeout(500); // Wait for animation
await sidebar.clickExperimentsSection();
const finalState = await sidebar.isExperimentEnabled(experimentTitle);
expect(finalState).toBe(false);
});
});
2 changes: 1 addition & 1 deletion tests/suites/tenant/TenantPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {Locator, Page} from '@playwright/test';
import {PageModel} from '../../models/PageModel';
import {tenantPage} from '../../utils/constants';

export const VISIBILITY_TIMEOUT = 5000;
export const VISIBILITY_TIMEOUT = 10000;

export enum NavigationTabs {
Query = 'Query',
Expand Down
54 changes: 54 additions & 0 deletions tests/suites/tenant/queryEditor/planToSvg.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {expect, test} from '@playwright/test';

import {tenantName} from '../../../utils/constants';
import {toggleExperiment} from '../../../utils/toggleExperiment';
import {TenantPage} from '../TenantPage';

import {ButtonNames, QueryEditor} from './QueryEditor';

test.describe('Test Plan to SVG functionality', async () => {
const testQuery = 'SELECT 1;'; // Simple query that will generate a plan

test.beforeEach(async ({page}) => {
const pageQueryParams = {
schema: tenantName,
database: tenantName,
general: 'query',
};

const tenantPage = new TenantPage(page);
await tenantPage.goto(pageQueryParams);
});

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

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

// 2. Set stats level to Full
await queryEditor.clickGearButton();
await queryEditor.settingsDialog.changeStatsLevel('Full');
await queryEditor.settingsDialog.clickButton(ButtonNames.Save);

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

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

// 5. Check if Execution Plan button appears and click it
const executionPlanButton = page.locator('button:has-text("Execution plan")');
await expect(executionPlanButton).toBeVisible();
await executionPlanButton.click();
await page.waitForTimeout(1000); // Wait for new tab to open

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