[Console] Add a setting to disable the a11y overlay (#158844)

## Summary
Fixes https://github.com/elastic/kibana/issues/16139

This PR adds a setting in Console that allows to disable the a11y
overlay. The default will be set to `enabled` so that the default
behaviour should not change. A user can disable the overlay in their
browser and the setting is saved in local storage. So that other users
are not affected by that change.

The reason to allow disabling the overlay is that it can be flaky and
sometimes it's displayed when not intended. The code relies on
`querySelector` (see this
[file](1b3f23829c/src/plugins/es_ui_shared/__packages_do_not_import__/ace/use_ui_ace_keyboard_mode.tsx (L25)))
so I think that causes the flakiness and that is very difficult to test
reliably.

### Screenshot 
#### A11y overlay (no changes)
<img width="1483" alt="Screenshot 2023-06-01 at 16 34 23"
src="d776625c-92cd-4bd9-8e5e-2f672df351a4">

#### Settings modal with the new option to disable the a11y overlay
<img width="474" alt="Screenshot 2023-06-01 at 16 29 02"
src="8745c7a0-62f4-41a9-9eff-ff8bebd4f767">


#### How to test 
1. Start Kibana and navigate to the Console
2. Press ESC when textarea is focused and now autocomplete popup is
displayed to see the a11y overlay
3. Open the Settings modal and disable the a11y overlay
4. Press ESC in the textarea again to see that no overlay is now
displayed
5. Check that the value is persisted in the local storage

Flaky test runner 
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/2346

### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
This commit is contained in:
Yulia Čech 2023-06-05 15:57:08 +02:00 committed by GitHub
parent f5e79f7626
commit 3c8b26b53d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 120 additions and 7 deletions

View file

@ -81,6 +81,9 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => {
const [isKeyboardShortcutsEnabled, setIsKeyboardShortcutsEnabled] = useState(
props.settings.isKeyboardShortcutsEnabled
);
const [isAccessibilityOverlayEnabled, setIsAccessibilityOverlayEnabled] = useState(
props.settings.isAccessibilityOverlayEnabled
);
const autoCompleteCheckboxes = [
{
@ -142,6 +145,7 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => {
tripleQuotes,
isHistoryEnabled,
isKeyboardShortcutsEnabled,
isAccessibilityOverlayEnabled,
});
}
@ -162,6 +166,11 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => {
[props.editorInstance]
);
const toggleAccessibilityOverlay = useCallback(
(isEnabled: boolean) => setIsAccessibilityOverlayEnabled(isEnabled),
[]
);
const toggleSavingToHistory = useCallback(
(isEnabled: boolean) => setIsHistoryEnabled(isEnabled),
[]
@ -320,6 +329,27 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => {
/>
</EuiFormRow>
<EuiFormRow
label={
<FormattedMessage
id="console.settingsPage.accessibilityOverlayLabel"
defaultMessage="Accessibility overlay"
/>
}
>
<EuiSwitch
data-test-subj="enableA11yOverlay"
checked={isAccessibilityOverlayEnabled}
label={
<FormattedMessage
defaultMessage="Enable accessibility overlay"
id="console.settingsPage.enableAccessibilityOverlayLabel"
/>
}
onChange={(e) => toggleAccessibilityOverlay(e.target.checked)}
/>
</EuiFormRow>
<EuiFormRow
labelType="legend"
label={

View file

@ -87,7 +87,7 @@ function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) {
const editorInstanceRef = useRef<senseEditor.SenseEditor | null>(null);
const [textArea, setTextArea] = useState<HTMLTextAreaElement | null>(null);
useUIAceKeyboardMode(textArea);
useUIAceKeyboardMode(textArea, settings.isAccessibilityOverlayEnabled);
const openDocumentation = useCallback(async () => {
const documentation = await getDocumentation(editorInstanceRef.current!, docLinkVersion);

View file

@ -22,6 +22,7 @@ export const DEFAULT_SETTINGS = Object.freeze({
}),
isHistoryEnabled: true,
isKeyboardShortcutsEnabled: true,
isAccessibilityOverlayEnabled: true,
});
export interface DevToolsSettings {
@ -38,6 +39,7 @@ export interface DevToolsSettings {
tripleQuotes: boolean;
isHistoryEnabled: boolean;
isKeyboardShortcutsEnabled: boolean;
isAccessibilityOverlayEnabled: boolean;
}
enum SettingKeys {
@ -49,6 +51,7 @@ enum SettingKeys {
POLL_INTERVAL = 'poll_interval',
IS_HISTORY_ENABLED = 'is_history_enabled',
IS_KEYBOARD_SHORTCUTS_ENABLED = 'is_keyboard_shortcuts_enabled',
IS_ACCESSIBILITY_OVERLAY_ENABLED = 'is_accessibility_overlay_enabled',
}
export class Settings {
@ -141,6 +144,11 @@ export class Settings {
return true;
}
setIsAccessibilityOverlayEnabled(isEnabled: boolean) {
this.storage.set(SettingKeys.IS_ACCESSIBILITY_OVERLAY_ENABLED, isEnabled);
return true;
}
getIsKeyboardShortcutsDisabled() {
return this.storage.get(
SettingKeys.IS_KEYBOARD_SHORTCUTS_ENABLED,
@ -148,6 +156,13 @@ export class Settings {
);
}
getIsAccessibilityOverlayEnabled() {
return this.storage.get(
SettingKeys.IS_ACCESSIBILITY_OVERLAY_ENABLED,
DEFAULT_SETTINGS.isAccessibilityOverlayEnabled
);
}
toJSON(): DevToolsSettings {
return {
autocomplete: this.getAutocomplete(),
@ -158,6 +173,7 @@ export class Settings {
pollInterval: this.getPollInterval(),
isHistoryEnabled: Boolean(this.getIsHistoryEnabled()),
isKeyboardShortcutsEnabled: Boolean(this.getIsKeyboardShortcutsDisabled()),
isAccessibilityOverlayEnabled: Boolean(this.getIsAccessibilityOverlayEnabled()),
};
}
@ -170,6 +186,7 @@ export class Settings {
pollInterval,
isHistoryEnabled,
isKeyboardShortcutsEnabled,
isAccessibilityOverlayEnabled,
}: DevToolsSettings) {
this.setFontSize(fontSize);
this.setWrapMode(wrapMode);
@ -179,6 +196,7 @@ export class Settings {
this.setPollInterval(pollInterval);
this.setIsHistoryEnabled(isHistoryEnabled);
this.setIsKeyboardShortcutsEnabled(isKeyboardShortcutsEnabled);
this.setIsAccessibilityOverlayEnabled(isAccessibilityOverlayEnabled);
}
}

View file

@ -17,15 +17,19 @@ const OverlayText = () => (
// in this case
//
<>
<EuiText size="s">Press Enter to start editing.</EuiText>
<EuiText size="s" data-test-subj="a11y-overlay">
Press Enter to start editing.
</EuiText>
<EuiText size="s">When you&rsquo;re done, press Escape to stop editing.</EuiText>
</>
);
export function useUIAceKeyboardMode(aceTextAreaElement: HTMLTextAreaElement | null) {
export function useUIAceKeyboardMode(
aceTextAreaElement: HTMLTextAreaElement | null,
isAccessibilityOverlayEnabled: boolean = true
) {
const overlayMountNode = useRef<HTMLDivElement | null>(null);
const autoCompleteVisibleRef = useRef<boolean>(false);
useEffect(() => {
function onDismissOverlay(event: KeyboardEvent) {
if (event.key === keys.ENTER) {
@ -60,7 +64,7 @@ export function useUIAceKeyboardMode(aceTextAreaElement: HTMLTextAreaElement | n
enableOverlay();
}
};
if (aceTextAreaElement) {
if (aceTextAreaElement && isAccessibilityOverlayEnabled) {
// We don't control HTML elements inside of ace so we imperatively create an element
// that acts as a container and insert it just before ace's textarea element
// so that the overlay lives at the correct spot in the DOM hierarchy.
@ -86,7 +90,7 @@ export function useUIAceKeyboardMode(aceTextAreaElement: HTMLTextAreaElement | n
aceTextAreaElement.addEventListener('keydown', aceKeydownListener);
}
return () => {
if (aceTextAreaElement) {
if (aceTextAreaElement && isAccessibilityOverlayEnabled) {
document.removeEventListener('keydown', documentKeyDownListener, { capture: true });
aceTextAreaElement.removeEventListener('keydown', aceKeydownListener);
const textAreaContainer = aceTextAreaElement.parentElement;
@ -95,5 +99,5 @@ export function useUIAceKeyboardMode(aceTextAreaElement: HTMLTextAreaElement | n
}
}
};
}, [aceTextAreaElement]);
}, [aceTextAreaElement, isAccessibilityOverlayEnabled]);
}

View file

@ -0,0 +1,41 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const log = getService('log');
const PageObjects = getPageObjects(['common', 'console']);
describe('console settings', function testSettings() {
this.tags('includeFirefox');
before(async () => {
log.debug('navigateTo console');
await PageObjects.common.navigateToApp('console');
// Ensure that the text area can be interacted with
await PageObjects.console.closeHelpIfExists();
await PageObjects.console.clearTextArea();
});
it('displays the a11y overlay', async () => {
await PageObjects.console.pressEscape();
const isOverlayVisible = await PageObjects.console.isA11yOverlayVisible();
expect(isOverlayVisible).to.be(true);
});
it('disables the a11y overlay via settings', async () => {
await PageObjects.console.openSettings();
await PageObjects.console.toggleA11yOverlaySetting();
await PageObjects.console.pressEscape();
const isOverlayVisible = await PageObjects.console.isA11yOverlayVisible();
expect(isOverlayVisible).to.be(false);
});
});
}

View file

@ -26,6 +26,7 @@ export default function ({ getService, loadTestFile }) {
loadTestFile(require.resolve('./_misc_console_behavior'));
loadTestFile(require.resolve('./_context_menu'));
loadTestFile(require.resolve('./_text_input'));
loadTestFile(require.resolve('./_settings'));
}
});
}

View file

@ -49,6 +49,16 @@ export class ConsolePageObject extends FtrService {
await this.testSubjects.click('consoleSettingsButton');
}
public async toggleA11yOverlaySetting() {
// while the settings form opens/loads this may fail, so retry for a while
await this.retry.try(async () => {
const toggle = await this.testSubjects.find('enableA11yOverlay');
await toggle.click();
});
await this.testSubjects.click('settings-save-button');
}
public async openVariablesModal() {
await this.testSubjects.click('consoleVariablesButton');
}
@ -191,6 +201,11 @@ export class ConsolePageObject extends FtrService {
await textArea.pressKeys(Key.ENTER);
}
public async pressEscape() {
const textArea = await this.testSubjects.find('console-textarea');
await textArea.pressKeys(Key.ESCAPE);
}
public async clearTextArea() {
await this.retry.waitForWithTimeout('text area is cleared', 20000, async () => {
const textArea = await this.testSubjects.find('console-textarea');
@ -403,6 +418,10 @@ export class ConsolePageObject extends FtrService {
return await this.testSubjects.exists('consoleMenuAutoIndent');
}
public async isA11yOverlayVisible() {
return await this.testSubjects.exists('a11y-overlay');
}
public async clickCopyAsCurlButton() {
const button = await this.testSubjects.find('consoleMenuCopyAsCurl');
await button.click();