Skip to content

Commit a94b77f

Browse files
committed
fix: switch and tests
1 parent f42474a commit a94b77f

File tree

5 files changed

+178
-21
lines changed

5 files changed

+178
-21
lines changed

src/containers/Tenant/Query/QuerySettingsDialog/QuerySettingsTimeout.scss

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
margin-right: var(--g-spacing-1);
2323
}
2424

25-
&__title {
25+
&__label-title,
26+
&__switch-title {
2627
flex: 4;
2728
align-items: center;
2829

@@ -32,6 +33,10 @@
3233
white-space: nowrap;
3334
}
3435

36+
&__label-title {
37+
line-height: var(--g-text-header-2-line-height);
38+
}
39+
3540
&__question-icon {
3641
color: var(--g-color-text-secondary);
3742
}

src/containers/Tenant/Query/QuerySettingsDialog/QuerySettingsTimeout.tsx

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {CircleQuestion} from '@gravity-ui/icons';
44
import {Icon, Popover, Switch, TextInput} from '@gravity-ui/uikit';
55

66
import {cn} from '../../../../utils/cn';
7+
import {ENABLE_QUERY_STREAMING} from '../../../../utils/constants';
8+
import {useSetting} from '../../../../utils/hooks';
79

810
import {QUERY_SETTINGS_FIELD_SETTINGS} from './constants';
911
import i18n from './i18n';
@@ -31,6 +33,8 @@ export function QuerySettingsTimeout({
3133
errorMessage,
3234
isDisabled,
3335
}: QuerySettingsTimeoutProps) {
36+
const [isQueryStreamingEnabled] = useSetting(ENABLE_QUERY_STREAMING);
37+
3438
const handleValueChange = React.useCallback(
3539
(event: React.ChangeEvent<HTMLInputElement>) => {
3640
const newValue = event.target.value ? Number(event.target.value) : undefined;
@@ -41,27 +45,35 @@ export function QuerySettingsTimeout({
4145

4246
const isChecked = value !== null;
4347

48+
const queryStreamingLabel = isQueryStreamingEnabled ? (
49+
<div className={b('switch-title')}>
50+
<Switch
51+
disabled={isDisabled}
52+
checked={isChecked}
53+
onUpdate={onToggle}
54+
className={b('switch')}
55+
content={QUERY_SETTINGS_FIELD_SETTINGS.timeout.title}
56+
/>
57+
{isDisabled ? (
58+
<Popover
59+
content={i18n('form.timeout.disabled')}
60+
placement={'bottom-start'}
61+
hasArrow={false}
62+
size="s"
63+
>
64+
<Icon className={b('question-icon')} data={CircleQuestion} />
65+
</Popover>
66+
) : null}
67+
</div>
68+
) : (
69+
<label htmlFor="timeout" className={b('label-title')}>
70+
{QUERY_SETTINGS_FIELD_SETTINGS.timeout.title}
71+
</label>
72+
);
73+
4474
return (
4575
<React.Fragment>
46-
<div className={b('title')}>
47-
<Switch
48-
disabled={isDisabled}
49-
checked={isChecked}
50-
onUpdate={onToggle}
51-
className={b('switch')}
52-
content={QUERY_SETTINGS_FIELD_SETTINGS.timeout.title}
53-
/>
54-
{isDisabled ? (
55-
<Popover
56-
content={i18n('form.timeout.disabled')}
57-
placement={'bottom-start'}
58-
hasArrow={false}
59-
size="s"
60-
>
61-
<Icon className={b('question-icon')} data={CircleQuestion} />
62-
</Popover>
63-
) : null}
64-
</div>
76+
{queryStreamingLabel}
6577
{isChecked && (
6678
<div className={b('control-wrapper')}>
6779
<TextInput

src/utils/query.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,13 +310,15 @@ export const queryModeSchema = z.nativeEnum(QUERY_MODES);
310310
export const transactionModeSchema = z.nativeEnum(TRANSACTION_MODES);
311311
export const statisticsModeSchema = z.nativeEnum(STATISTICS_MODES);
312312
export const tracingLevelSchema = z.nativeEnum(TRACING_LEVELS);
313+
314+
// timeout = null is for timeout switched off state
313315
export const querySettingsValidationSchema = z.object({
314316
timeout: z
315317
.preprocess(
316318
(val) => (val === '' ? undefined : val),
317319
z.coerce.number().positive().or(z.undefined()).or(z.null()),
318320
)
319-
.or(z.string()),
321+
.or(z.literal('')),
320322
limitRows: z.preprocess(
321323
(val) => (val === '' ? undefined : val),
322324
z.coerce.number().gt(0).lte(100_000).or(z.undefined()),

tests/suites/tenant/queryEditor/models/SettingsDialog.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ export class SettingsDialog {
1616
private limitRowsInput: Locator;
1717
private limitRowsErrorIcon: Locator;
1818
private limitRowsErrorPopover: Locator;
19+
private timeoutInput: Locator;
20+
private timeoutSwitch: Locator;
21+
private timeoutSwitchHint: Locator;
22+
private timeoutHintPopover: Locator;
23+
private timeoutLabel: Locator;
1924

2025
private queryModeSelect: Locator;
2126
private transactionModeSelect: Locator;
@@ -32,6 +37,11 @@ export class SettingsDialog {
3237
);
3338
this.limitRowsErrorPopover = this.page.locator('.g-popover__tooltip-content');
3439
this.selectPopup = page.locator('.ydb-query-settings-select__popup');
40+
this.timeoutInput = this.dialog.locator('.ydb-query-settings-timeout__input');
41+
this.timeoutSwitch = this.dialog.locator('.ydb-query-settings-timeout__switch');
42+
this.timeoutSwitchHint = this.dialog.locator('.ydb-query-settings-timeout__question-icon');
43+
this.timeoutHintPopover = this.page.locator('.g-popover__tooltip-content');
44+
this.timeoutLabel = this.dialog.locator('.ydb-query-settings-timeout__label-title');
3545

3646
// Define distinct locators for selects
3747
this.queryModeSelect = this.dialog.locator(
@@ -125,6 +135,38 @@ export class SettingsDialog {
125135
return true;
126136
}
127137

138+
async isTimeoutInputVisible() {
139+
return await this.timeoutInput.isVisible();
140+
}
141+
142+
async clickTimeoutSwitch() {
143+
await this.timeoutSwitch.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT});
144+
await this.timeoutSwitch.click();
145+
await this.page.waitForTimeout(500);
146+
}
147+
148+
async isTimeoutSwitchChecked() {
149+
return await this.timeoutSwitch.locator('input[type="checkbox"]').isChecked();
150+
}
151+
152+
async isTimeoutSwitchDisabled() {
153+
return await this.timeoutSwitch.locator('input[type="checkbox"][disabled]').isVisible();
154+
}
155+
156+
async isTimeoutHintVisible() {
157+
return await this.timeoutSwitchHint.isVisible();
158+
}
159+
160+
async getTimeoutHintText() {
161+
await this.timeoutSwitchHint.hover();
162+
await this.timeoutHintPopover.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT});
163+
return await this.timeoutHintPopover.textContent();
164+
}
165+
166+
async isTimeoutLabelVisible() {
167+
return await this.timeoutLabel.isVisible();
168+
}
169+
128170
async isStatisticsSelectDisabled() {
129171
await this.statisticsModeSelect.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT});
130172
return this.statisticsModeSelect.locator('.g-select-control_disabled').isVisible();

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

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

33
import {QUERY_MODES, TRANSACTION_MODES} from '../../../../src/utils/query';
44
import {tenantName} from '../../../utils/constants';
5+
import {toggleExperiment} from '../../../utils/toggleExperiment';
56
import {TenantPage, VISIBILITY_TIMEOUT} from '../TenantPage';
67
import {longRunningQuery} from '../constants';
78

@@ -152,4 +153,99 @@ test.describe('Test Query Settings', async () => {
152153

153154
await expect(queryEditor.settingsDialog.isHidden()).resolves.toBe(true);
154155
});
156+
157+
test('Timeout input is invisible by default', async ({page}) => {
158+
const queryEditor = new QueryEditor(page);
159+
160+
// Open settings dialog
161+
await queryEditor.clickGearButton();
162+
await expect(queryEditor.settingsDialog.isVisible()).resolves.toBe(true);
163+
164+
// Check that timeout input is invisible
165+
await expect(queryEditor.settingsDialog.isTimeoutInputVisible()).resolves.toBe(false);
166+
167+
// Close dialog
168+
await queryEditor.settingsDialog.clickButton(ButtonNames.Cancel);
169+
await expect(queryEditor.settingsDialog.isHidden()).resolves.toBe(true);
170+
});
171+
172+
test('Clicking timeout switch makes timeout input visible', async ({page}) => {
173+
const queryEditor = new QueryEditor(page);
174+
175+
// Open settings dialog
176+
await queryEditor.clickGearButton();
177+
await expect(queryEditor.settingsDialog.isVisible()).resolves.toBe(true);
178+
179+
// Initially timeout input should be invisible
180+
await expect(queryEditor.settingsDialog.isTimeoutInputVisible()).resolves.toBe(false);
181+
182+
// Click the timeout switch
183+
await queryEditor.settingsDialog.clickTimeoutSwitch();
184+
185+
// Check that timeout input is now visible
186+
await expect(queryEditor.settingsDialog.isTimeoutInputVisible()).resolves.toBe(true);
187+
await expect(queryEditor.settingsDialog.isTimeoutSwitchChecked()).resolves.toBe(true);
188+
189+
// Close dialog
190+
await queryEditor.settingsDialog.clickButton(ButtonNames.Cancel);
191+
await expect(queryEditor.settingsDialog.isHidden()).resolves.toBe(true);
192+
});
193+
194+
test('Timeout switch is checked, disabled, and has hint when non-query mode is selected', async ({
195+
page,
196+
}) => {
197+
const queryEditor = new QueryEditor(page);
198+
199+
// Open settings dialog
200+
await queryEditor.clickGearButton();
201+
await expect(queryEditor.settingsDialog.isVisible()).resolves.toBe(true);
202+
203+
// Initially timeout switch should be enabled and unchecked
204+
await expect(queryEditor.settingsDialog.isTimeoutSwitchDisabled()).resolves.toBe(false);
205+
await expect(queryEditor.settingsDialog.isTimeoutSwitchChecked()).resolves.toBe(false);
206+
207+
// Change to a non-query mode
208+
await queryEditor.settingsDialog.changeQueryMode(QUERY_MODES.scan);
209+
210+
// Verify timeout switch is checked and disabled
211+
await expect(queryEditor.settingsDialog.isTimeoutSwitchChecked()).resolves.toBe(true);
212+
await expect(queryEditor.settingsDialog.isTimeoutSwitchDisabled()).resolves.toBe(true);
213+
214+
// Verify hint is visible and has correct text
215+
await expect(queryEditor.settingsDialog.isTimeoutHintVisible()).resolves.toBe(true);
216+
217+
// Verify the hint text content
218+
const hintText = await queryEditor.settingsDialog.getTimeoutHintText();
219+
expect(hintText).toBeTruthy(); // Should have some text content
220+
221+
// Close dialog
222+
await queryEditor.settingsDialog.clickButton(ButtonNames.Cancel);
223+
await expect(queryEditor.settingsDialog.isHidden()).resolves.toBe(true);
224+
});
225+
226+
test('When Query Streaming is off, timeout has label and input is visible by default', async ({
227+
page,
228+
}) => {
229+
const queryEditor = new QueryEditor(page);
230+
231+
// Turn off Query Streaming experiment
232+
await toggleExperiment(page, 'off', 'Query Streaming');
233+
234+
// Open settings dialog
235+
await queryEditor.clickGearButton();
236+
await expect(queryEditor.settingsDialog.isVisible()).resolves.toBe(true);
237+
238+
// Verify there's a label instead of a switch
239+
await expect(queryEditor.settingsDialog.isTimeoutLabelVisible()).resolves.toBe(true);
240+
241+
// Verify timeout input is visible by default
242+
await expect(queryEditor.settingsDialog.isTimeoutInputVisible()).resolves.toBe(true);
243+
244+
// Close dialog
245+
await queryEditor.settingsDialog.clickButton(ButtonNames.Cancel);
246+
await expect(queryEditor.settingsDialog.isHidden()).resolves.toBe(true);
247+
248+
// Restore Query Streaming experiment
249+
await toggleExperiment(page, 'on', 'Query Streaming');
250+
});
155251
});

0 commit comments

Comments
 (0)