InfluxDB: fix accessibility problems (#42553)

* influxdb: influxql: query editor a11y fixes

* configure datasource page a11y fixes

* fixed unit test

* better a11y

* better a11y for the query editor

* simplify code

* updated tests

* removed explicit aria-label
This commit is contained in:
Gábor Farkas 2021-12-15 15:57:08 +01:00 committed by GitHub
parent 1569529808
commit 50c6c7a528
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 83 additions and 170 deletions

View File

@ -12,11 +12,14 @@ import { InlineFieldRow } from '../Forms/InlineFieldRow';
*/
export const SegmentSection = ({
label,
htmlFor,
children,
fill,
}: {
// Name of the section
label: string;
// htmlFor for the label
htmlFor?: string;
// List of components in the section
children: React.ReactNode;
// Fill the space at the end
@ -26,7 +29,7 @@ export const SegmentSection = ({
return (
<>
<InlineFieldRow>
<InlineLabel width={12} className={styles.label}>
<InlineLabel htmlFor={htmlFor} width={12} className={styles.label}>
{label}
</InlineLabel>
{children}

View File

@ -2,6 +2,17 @@ import React from 'react';
import { shallow } from 'enzyme';
import ConfigEditor, { Props } from './ConfigEditor';
jest.mock('lodash', () => {
const uniqueId = (prefix: string) => `${prefix}42`;
const orig = jest.requireActual('lodash');
return {
...orig,
uniqueId,
};
});
const setup = (propOverrides?: object) => {
const props: Props = {
options: {

View File

@ -1,4 +1,5 @@
import React, { PureComponent } from 'react';
import { uniqueId } from 'lodash';
import {
DataSourcePluginOptionsEditorProps,
SelectableValue,
@ -9,8 +10,8 @@ import {
onUpdateDatasourceSecureJsonDataOption,
updateDatasourcePluginJsonDataOption,
} from '@grafana/data';
import { Alert, DataSourceHttpSettings, InfoBox, InlineField, InlineFormLabel, LegacyForms } from '@grafana/ui';
const { Select, Input, SecretFormField } = LegacyForms;
import { Alert, DataSourceHttpSettings, InfoBox, InlineField, InlineFormLabel, LegacyForms, Select } from '@grafana/ui';
const { Input, SecretFormField } = LegacyForms;
import { InfluxOptions, InfluxSecureJsonData, InfluxVersion } from '../types';
const httpModes = [
@ -41,9 +42,12 @@ export class ConfigEditor extends PureComponent<Props, State> {
maxSeries: '',
};
htmlPrefix: string;
constructor(props: Props) {
super(props);
this.state.maxSeries = props.options.jsonData.maxSeries?.toString() || '';
this.htmlPrefix = uniqueId('influxdb-config');
}
// 1x
@ -83,14 +87,18 @@ export class ConfigEditor extends PureComponent<Props, State> {
const { options } = this.props;
const { secureJsonFields } = options;
const secureJsonData = (options.secureJsonData || {}) as InfluxSecureJsonData;
const { htmlPrefix } = this;
return (
<>
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-10">Organization</InlineFormLabel>
<InlineFormLabel htmlFor={`${htmlPrefix}-org`} className="width-10">
Organization
</InlineFormLabel>
<div className="width-10">
<Input
id={`${htmlPrefix}-org`}
className="width-20"
value={options.jsonData.organization || ''}
onChange={onUpdateDatasourceJsonDataOption(this.props, 'organization')}
@ -152,6 +160,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
const { options } = this.props;
const { secureJsonFields } = options;
const secureJsonData = (options.secureJsonData || {}) as InfluxSecureJsonData;
const { htmlPrefix } = this;
return (
<>
@ -169,9 +178,12 @@ export class ConfigEditor extends PureComponent<Props, State> {
</InfoBox>
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-10">Database</InlineFormLabel>
<InlineFormLabel htmlFor={`${htmlPrefix}-db`} className="width-10">
Database
</InlineFormLabel>
<div className="width-20">
<Input
id={`${htmlPrefix}-db`}
className="width-20"
value={options.database || ''}
onChange={onUpdateDatasourceOption(this.props, 'database')}
@ -181,9 +193,12 @@ export class ConfigEditor extends PureComponent<Props, State> {
</div>
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-10">User</InlineFormLabel>
<InlineFormLabel htmlFor={`${htmlPrefix}-user`} className="width-10">
User
</InlineFormLabel>
<div className="width-10">
<Input
id={`${htmlPrefix}-user`}
className="width-20"
value={options.user || ''}
onChange={onUpdateDatasourceOption(this.props, 'user')}
@ -207,6 +222,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel
htmlFor={`${htmlPrefix}-http-method`}
className="width-10"
tooltip="You can use either GET or POST HTTP method to query your InfluxDB database. The POST
method allows you to perform heavy requests (with a lots of WHERE clause) while the GET method
@ -215,6 +231,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
HTTP Method
</InlineFormLabel>
<Select
inputId={`${htmlPrefix}-http-method`}
menuShouldPortal
className="width-10"
value={httpModes.find((httpMode) => httpMode.value === options.jsonData.httpMode)}
@ -258,6 +275,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
<div className="gf-form-inline">
<div className="gf-form">
<Select
aria-label="Query language"
menuShouldPortal
className="width-30"
value={options.jsonData.version === InfluxVersion.Flux ? versions[1] : versions[0]}

View File

@ -18,6 +18,7 @@ export const QueryEditorModeSwitcher = ({ isRaw, onChange }: Props): JSX.Element
return (
<>
<Button
aria-label="Switch to visual editor"
icon="pen"
variant="secondary"
type="button"
@ -44,6 +45,7 @@ export const QueryEditorModeSwitcher = ({ isRaw, onChange }: Props): JSX.Element
} else {
return (
<Button
aria-label="Switch to text editor"
icon="pen"
variant="secondary"
type="button"

View File

@ -29,6 +29,7 @@ import { getNewSelectPartOptions, getNewGroupByPartOptions, makePartList } from
import { InlineLabel, SegmentSection, useStyles2 } from '@grafana/ui';
import { GrafanaTheme2 } from '@grafana/data';
import { css } from '@emotion/css';
import { useUniqueId } from '../useUniqueId';
type Props = {
query: InfluxQuery;
@ -53,6 +54,10 @@ function withTemplateVariableOptions(optionsPromise: Promise<string[]>): Promise
}
export const Editor = (props: Props): JSX.Element => {
const uniqueId = useUniqueId();
const formatAsId = `influxdb-qe-format-as-${uniqueId}`;
const orderByTimeId = `influxdb-qe-order-by${uniqueId}`;
const styles = useStyles2(getStyles);
const query = normalizeQuery(props.query);
const { datasource } = props;
@ -172,10 +177,11 @@ export const Editor = (props: Props): JSX.Element => {
onAppliedChange({ ...query, tz });
}}
/>
<InlineLabel width="auto" className={styles.inlineLabel}>
<InlineLabel htmlFor={orderByTimeId} width="auto" className={styles.inlineLabel}>
ORDER BY TIME
</InlineLabel>
<OrderByTimeSection
inputId={orderByTimeId}
value={query.orderByTime === 'DESC' ? 'DESC' : 'ASC' /* FIXME: make this shared with influx_query_model */}
onChange={(v) => {
onAppliedChange({ ...query, orderByTime: v });
@ -206,8 +212,9 @@ export const Editor = (props: Props): JSX.Element => {
}}
/>
</SegmentSection>
<SegmentSection label="FORMAT AS" fill={true}>
<SegmentSection htmlFor={formatAsId} label="FORMAT AS" fill={true}>
<FormatAsSection
inputId={formatAsId}
format={query.resultFormat ?? DEFAULT_RESULT_FORMAT}
onChange={(format) => {
onAppliedChange({ ...query, resultFormat: format });

View File

@ -7,15 +7,17 @@ import { RESULT_FORMATS } from '../constants';
import { paddingRightClass } from './styles';
type Props = {
inputId?: string;
format: ResultFormat;
onChange: (newFormat: ResultFormat) => void;
};
const className = cx('width-8', paddingRightClass);
export const FormatAsSection = ({ format, onChange }: Props): JSX.Element => {
export const FormatAsSection = ({ format, inputId, onChange }: Props): JSX.Element => {
return (
<Select<ResultFormat>
inputId={inputId}
className={className}
onChange={(v) => {
onChange(unwrap(v.value));

View File

@ -17,12 +17,14 @@ const className = cx('width-9', paddingRightClass);
type Props = {
value: Mode;
onChange: (value: Mode) => void;
inputId?: string;
};
export const OrderByTimeSection = ({ value, onChange }: Props): JSX.Element => {
export const OrderByTimeSection = ({ value, onChange, inputId }: Props): JSX.Element => {
return (
<>
<Select<Mode>
inputId={inputId}
className={className}
onChange={(v) => {
onChange(unwrap(v.value));

View File

@ -17,19 +17,8 @@ exports[`Render should disable basic auth password input 1`] = `
className="gf-form"
>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
aria-label="Query language"
className="width-30"
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
defaultValue={
Object {
"description": "The InfluxDB SQL-like query language.",
@ -37,15 +26,8 @@ exports[`Render should disable basic auth password input 1`] = `
"value": "InfluxQL",
}
}
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
menuShouldPortal={true}
onChange={[Function]}
openMenuOnFocus={false}
options={
Array [
Object {
@ -60,7 +42,6 @@ exports[`Render should disable basic auth password input 1`] = `
},
]
}
tabSelectsValue={true}
value={
Object {
"description": "The InfluxDB SQL-like query language.",
@ -141,6 +122,7 @@ exports[`Render should disable basic auth password input 1`] = `
>
<FormLabel
className="width-10"
htmlFor="influxdb-config42-db"
>
Database
</FormLabel>
@ -149,6 +131,7 @@ exports[`Render should disable basic auth password input 1`] = `
>
<Input
className="width-20"
id="influxdb-config42-db"
onChange={[Function]}
value=""
/>
@ -163,6 +146,7 @@ exports[`Render should disable basic auth password input 1`] = `
>
<FormLabel
className="width-10"
htmlFor="influxdb-config42-user"
>
User
</FormLabel>
@ -171,6 +155,7 @@ exports[`Render should disable basic auth password input 1`] = `
>
<Input
className="width-20"
id="influxdb-config42-user"
onChange={[Function]}
value=""
/>
@ -201,6 +186,7 @@ exports[`Render should disable basic auth password input 1`] = `
>
<FormLabel
className="width-10"
htmlFor="influxdb-config42-http-method"
tooltip="You can use either GET or POST HTTP method to query your InfluxDB database. The POST
method allows you to perform heavy requests (with a lots of WHERE clause) while the GET method
will restrict you and return an error if the query is too large."
@ -208,29 +194,11 @@ exports[`Render should disable basic auth password input 1`] = `
HTTP Method
</FormLabel>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className="width-10"
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
defaultValue="POST"
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
inputId="influxdb-config42-http-method"
menuShouldPortal={true}
onChange={[Function]}
openMenuOnFocus={false}
options={
Array [
Object {
@ -243,7 +211,6 @@ exports[`Render should disable basic auth password input 1`] = `
},
]
}
tabSelectsValue={true}
value={
Object {
"label": "POST",
@ -316,19 +283,8 @@ exports[`Render should hide basic auth fields when switch off 1`] = `
className="gf-form"
>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
aria-label="Query language"
className="width-30"
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
defaultValue={
Object {
"description": "The InfluxDB SQL-like query language.",
@ -336,15 +292,8 @@ exports[`Render should hide basic auth fields when switch off 1`] = `
"value": "InfluxQL",
}
}
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
menuShouldPortal={true}
onChange={[Function]}
openMenuOnFocus={false}
options={
Array [
Object {
@ -359,7 +308,6 @@ exports[`Render should hide basic auth fields when switch off 1`] = `
},
]
}
tabSelectsValue={true}
value={
Object {
"description": "The InfluxDB SQL-like query language.",
@ -440,6 +388,7 @@ exports[`Render should hide basic auth fields when switch off 1`] = `
>
<FormLabel
className="width-10"
htmlFor="influxdb-config42-db"
>
Database
</FormLabel>
@ -448,6 +397,7 @@ exports[`Render should hide basic auth fields when switch off 1`] = `
>
<Input
className="width-20"
id="influxdb-config42-db"
onChange={[Function]}
value=""
/>
@ -462,6 +412,7 @@ exports[`Render should hide basic auth fields when switch off 1`] = `
>
<FormLabel
className="width-10"
htmlFor="influxdb-config42-user"
>
User
</FormLabel>
@ -470,6 +421,7 @@ exports[`Render should hide basic auth fields when switch off 1`] = `
>
<Input
className="width-20"
id="influxdb-config42-user"
onChange={[Function]}
value=""
/>
@ -500,6 +452,7 @@ exports[`Render should hide basic auth fields when switch off 1`] = `
>
<FormLabel
className="width-10"
htmlFor="influxdb-config42-http-method"
tooltip="You can use either GET or POST HTTP method to query your InfluxDB database. The POST
method allows you to perform heavy requests (with a lots of WHERE clause) while the GET method
will restrict you and return an error if the query is too large."
@ -507,29 +460,11 @@ exports[`Render should hide basic auth fields when switch off 1`] = `
HTTP Method
</FormLabel>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className="width-10"
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
defaultValue="POST"
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
inputId="influxdb-config42-http-method"
menuShouldPortal={true}
onChange={[Function]}
openMenuOnFocus={false}
options={
Array [
Object {
@ -542,7 +477,6 @@ exports[`Render should hide basic auth fields when switch off 1`] = `
},
]
}
tabSelectsValue={true}
value={
Object {
"label": "POST",
@ -615,19 +549,8 @@ exports[`Render should hide white listed cookies input when browser access chose
className="gf-form"
>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
aria-label="Query language"
className="width-30"
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
defaultValue={
Object {
"description": "The InfluxDB SQL-like query language.",
@ -635,15 +558,8 @@ exports[`Render should hide white listed cookies input when browser access chose
"value": "InfluxQL",
}
}
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
menuShouldPortal={true}
onChange={[Function]}
openMenuOnFocus={false}
options={
Array [
Object {
@ -658,7 +574,6 @@ exports[`Render should hide white listed cookies input when browser access chose
},
]
}
tabSelectsValue={true}
value={
Object {
"description": "The InfluxDB SQL-like query language.",
@ -739,6 +654,7 @@ exports[`Render should hide white listed cookies input when browser access chose
>
<FormLabel
className="width-10"
htmlFor="influxdb-config42-db"
>
Database
</FormLabel>
@ -747,6 +663,7 @@ exports[`Render should hide white listed cookies input when browser access chose
>
<Input
className="width-20"
id="influxdb-config42-db"
onChange={[Function]}
value=""
/>
@ -761,6 +678,7 @@ exports[`Render should hide white listed cookies input when browser access chose
>
<FormLabel
className="width-10"
htmlFor="influxdb-config42-user"
>
User
</FormLabel>
@ -769,6 +687,7 @@ exports[`Render should hide white listed cookies input when browser access chose
>
<Input
className="width-20"
id="influxdb-config42-user"
onChange={[Function]}
value=""
/>
@ -799,6 +718,7 @@ exports[`Render should hide white listed cookies input when browser access chose
>
<FormLabel
className="width-10"
htmlFor="influxdb-config42-http-method"
tooltip="You can use either GET or POST HTTP method to query your InfluxDB database. The POST
method allows you to perform heavy requests (with a lots of WHERE clause) while the GET method
will restrict you and return an error if the query is too large."
@ -806,29 +726,11 @@ exports[`Render should hide white listed cookies input when browser access chose
HTTP Method
</FormLabel>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className="width-10"
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
defaultValue="POST"
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
inputId="influxdb-config42-http-method"
menuShouldPortal={true}
onChange={[Function]}
openMenuOnFocus={false}
options={
Array [
Object {
@ -841,7 +743,6 @@ exports[`Render should hide white listed cookies input when browser access chose
},
]
}
tabSelectsValue={true}
value={
Object {
"label": "POST",
@ -914,19 +815,8 @@ exports[`Render should render component 1`] = `
className="gf-form"
>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
aria-label="Query language"
className="width-30"
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
defaultValue={
Object {
"description": "The InfluxDB SQL-like query language.",
@ -934,15 +824,8 @@ exports[`Render should render component 1`] = `
"value": "InfluxQL",
}
}
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
menuShouldPortal={true}
onChange={[Function]}
openMenuOnFocus={false}
options={
Array [
Object {
@ -957,7 +840,6 @@ exports[`Render should render component 1`] = `
},
]
}
tabSelectsValue={true}
value={
Object {
"description": "The InfluxDB SQL-like query language.",
@ -1038,6 +920,7 @@ exports[`Render should render component 1`] = `
>
<FormLabel
className="width-10"
htmlFor="influxdb-config42-db"
>
Database
</FormLabel>
@ -1046,6 +929,7 @@ exports[`Render should render component 1`] = `
>
<Input
className="width-20"
id="influxdb-config42-db"
onChange={[Function]}
value=""
/>
@ -1060,6 +944,7 @@ exports[`Render should render component 1`] = `
>
<FormLabel
className="width-10"
htmlFor="influxdb-config42-user"
>
User
</FormLabel>
@ -1068,6 +953,7 @@ exports[`Render should render component 1`] = `
>
<Input
className="width-20"
id="influxdb-config42-user"
onChange={[Function]}
value=""
/>
@ -1098,6 +984,7 @@ exports[`Render should render component 1`] = `
>
<FormLabel
className="width-10"
htmlFor="influxdb-config42-http-method"
tooltip="You can use either GET or POST HTTP method to query your InfluxDB database. The POST
method allows you to perform heavy requests (with a lots of WHERE clause) while the GET method
will restrict you and return an error if the query is too large."
@ -1105,29 +992,11 @@ exports[`Render should render component 1`] = `
HTTP Method
</FormLabel>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className="width-10"
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
defaultValue="POST"
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
inputId="influxdb-config42-http-method"
menuShouldPortal={true}
onChange={[Function]}
openMenuOnFocus={false}
options={
Array [
Object {
@ -1140,7 +1009,6 @@ exports[`Render should render component 1`] = `
},
]
}
tabSelectsValue={true}
value={
Object {
"label": "POST",