A11y: Fix various fastpass accessibility issues (#41154)

This commit is contained in:
kay delaney
2021-11-02 15:27:07 +00:00
committed by GitHub
parent 14ddb2939c
commit 3c5003373c
19 changed files with 104 additions and 31 deletions

View File

@@ -6,14 +6,16 @@ interface Props {
value: OrgRole; value: OrgRole;
disabled?: boolean; disabled?: boolean;
'aria-label'?: string; 'aria-label'?: string;
inputId?: string;
onChange: (role: OrgRole) => void; onChange: (role: OrgRole) => void;
} }
const options = Object.keys(OrgRole).map((key) => ({ label: key, value: key })); const options = Object.keys(OrgRole).map((key) => ({ label: key, value: key }));
export const OrgRolePicker: FC<Props> = ({ value, onChange, 'aria-label': ariaLabel, ...restProps }) => ( export const OrgRolePicker: FC<Props> = ({ value, onChange, 'aria-label': ariaLabel, inputId, ...restProps }) => (
<Select <Select
menuShouldPortal menuShouldPortal
inputId={inputId}
value={value} value={value}
options={options} options={options}
onChange={(val) => onChange(val.value as OrgRole)} onChange={(val) => onChange(val.value as OrgRole)}

View File

@@ -45,15 +45,15 @@ const UserCreatePage: React.FC<UserCreatePageProps> = ({ navModel }) => {
invalid={!!errors.name} invalid={!!errors.name}
error={errors.name ? 'Name is required' : undefined} error={errors.name ? 'Name is required' : undefined}
> >
<Input {...register('name', { required: true })} /> <Input id="name-input" {...register('name', { required: true })} />
</Field> </Field>
<Field label="Email"> <Field label="Email">
<Input {...register('email')} /> <Input id="email-input" {...register('email')} />
</Field> </Field>
<Field label="Username"> <Field label="Username">
<Input {...register('login')} /> <Input id="username-input" {...register('login')} />
</Field> </Field>
<Field <Field
label="Password" label="Password"
@@ -62,6 +62,7 @@ const UserCreatePage: React.FC<UserCreatePageProps> = ({ navModel }) => {
error={errors.password ? 'Password is required and must contain at least 4 characters' : undefined} error={errors.password ? 'Password is required and must contain at least 4 characters' : undefined}
> >
<Input <Input
id="password-input"
{...register('password', { {...register('password', {
validate: (value) => value.trim() !== '' && value.length >= 4, validate: (value) => value.trim() !== '' && value.length >= 4,
})} })}

View File

@@ -151,12 +151,15 @@ class UnThemedOrgRow extends PureComponent<OrgRowProps, OrgRowState> {
const canChangeRole = contextSrv.hasPermission(AccessControlAction.OrgUsersRoleUpdate); const canChangeRole = contextSrv.hasPermission(AccessControlAction.OrgUsersRoleUpdate);
const canRemoveFromOrg = contextSrv.hasPermission(AccessControlAction.OrgUsersRemove); const canRemoveFromOrg = contextSrv.hasPermission(AccessControlAction.OrgUsersRemove);
const inputId = `${org.name}-input`;
return ( return (
<tr> <tr>
<td className={labelClass}>{org.name}</td> <td className={labelClass}>
<label htmlFor={inputId}>{org.name}</label>
</td>
{isChangingRole ? ( {isChangingRole ? (
<td> <td>
<OrgRolePicker value={currentRole} onChange={this.onOrgRoleChange} /> <OrgRolePicker inputId={inputId} value={currentRole} onChange={this.onOrgRoleChange} />
</td> </td>
) : ( ) : (
<td className="width-25">{org.role}</td> <td className="width-25">{org.role}</td>
@@ -257,10 +260,10 @@ export class AddToOrgModal extends PureComponent<AddToOrgModalProps, AddToOrgMod
onDismiss={this.onCancel} onDismiss={this.onCancel}
> >
<Field label="Organization"> <Field label="Organization">
<OrgPicker onSelected={this.onOrgSelect} /> <OrgPicker inputId="new-org-input" onSelected={this.onOrgSelect} />
</Field> </Field>
<Field label="Role"> <Field label="Role">
<OrgRolePicker value={role} onChange={this.onOrgRoleChange} /> <OrgRolePicker inputId="new-org-role-input" value={role} onChange={this.onOrgRoleChange} />
</Field> </Field>
<Modal.ButtonRow> <Modal.ButtonRow>
<HorizontalGroup spacing="md" justify="center"> <HorizontalGroup spacing="md" justify="center">

View File

@@ -275,12 +275,16 @@ export class UserProfileRow extends PureComponent<UserProfileRowProps, UserProfi
return <LockedRow label={label} value={value} lockMessage={lockMessage} />; return <LockedRow label={label} value={value} lockMessage={lockMessage} />;
} }
const inputId = `${label}-input`;
return ( return (
<tr> <tr>
<td className={labelClass}>{label}</td> <td className={labelClass}>
<label htmlFor={inputId}>{label}</label>
</td>
<td className="width-25" colSpan={2}> <td className="width-25" colSpan={2}>
{this.state.editing ? ( {this.state.editing ? (
<Input <Input
id={inputId}
type={inputType} type={inputType}
defaultValue={value} defaultValue={value}
onBlur={this.onInputBlur} onBlur={this.onInputBlur}

View File

@@ -11,7 +11,7 @@ import {
} from 'app/plugins/datasource/alertmanager/types'; } from 'app/plugins/datasource/alertmanager/types';
import { configureStore } from 'app/store/configureStore'; import { configureStore } from 'app/store/configureStore';
import { typeAsJestMock } from 'test/helpers/typeAsJestMock'; import { typeAsJestMock } from 'test/helpers/typeAsJestMock';
import { byRole, byTestId, byText } from 'testing-library-selector'; import { byLabelText, byRole, byTestId, byText } from 'testing-library-selector';
import AmRoutes from './AmRoutes'; import AmRoutes from './AmRoutes';
import { fetchAlertManagerConfig, fetchStatus, updateAlertManagerConfig } from './api/alertmanager'; import { fetchAlertManagerConfig, fetchStatus, updateAlertManagerConfig } from './api/alertmanager';
import { mockDataSource, MockDataSourceSrv, someCloudAlertManagerConfig, someCloudAlertManagerStatus } from './mocks'; import { mockDataSource, MockDataSourceSrv, someCloudAlertManagerConfig, someCloudAlertManagerStatus } from './mocks';
@@ -76,8 +76,8 @@ const ui = {
editButton: byRole('button', { name: 'Edit' }), editButton: byRole('button', { name: 'Edit' }),
saveButton: byRole('button', { name: 'Save' }), saveButton: byRole('button', { name: 'Save' }),
editRouteButton: byTestId('edit-route'), editRouteButton: byLabelText('Edit route'),
deleteRouteButton: byTestId('delete-route'), deleteRouteButton: byLabelText('Delete route'),
newPolicyButton: byRole('button', { name: /New policy/ }), newPolicyButton: byRole('button', { name: /New policy/ }),
receiverSelect: byTestId('am-receiver-select'), receiverSelect: byTestId('am-receiver-select'),

View File

@@ -14,7 +14,12 @@ export const CollapseToggle: FC<Props> = ({ isCollapsed, onToggle, className, te
const styles = useStyles(getStyles); const styles = useStyles(getStyles);
return ( return (
<button className={cx(styles.expandButton, className)} onClick={() => onToggle(!isCollapsed)} {...restOfProps}> <button
aria-label={`${isCollapsed ? 'Expand' : 'Collapse'} alert group`}
className={cx(styles.expandButton, className)}
onClick={() => onToggle(!isCollapsed)}
{...restOfProps}
>
<Icon size={size} name={isCollapsed ? 'angle-right' : 'angle-down'} /> <Icon size={size} name={isCollapsed ? 'angle-right' : 'angle-down'} />
{text} {text}
</button> </button>

View File

@@ -96,6 +96,7 @@ export const DynamicTable = <T extends object>({
{isExpandable && ( {isExpandable && (
<div className={cx(styles.cell, styles.expandCell)}> <div className={cx(styles.cell, styles.expandCell)}>
<IconButton <IconButton
aria-label={`${isItemExpanded ? 'Collapse' : 'Expand'} row`}
size="xl" size="xl"
data-testid="collapse-toggle" data-testid="collapse-toggle"
className={styles.expandButton} className={styles.expandButton}

View File

@@ -42,11 +42,12 @@ export const AmRootRouteForm: FC<AmRootRouteFormProps> = ({
<InputControl <InputControl
render={({ field: { onChange, ref, ...field } }) => ( render={({ field: { onChange, ref, ...field } }) => (
<Select <Select
menuShouldPortal aria-label="Default contact point"
{...field} {...field}
className={styles.input} className={styles.input}
onChange={(value) => onChange(mapSelectValueToString(value))} onChange={(value) => onChange(mapSelectValueToString(value))}
options={receivers} options={receivers}
menuShouldPortal
/> />
)} )}
control={control} control={control}
@@ -72,6 +73,7 @@ export const AmRootRouteForm: FC<AmRootRouteFormProps> = ({
<InputControl <InputControl
render={({ field: { onChange, ref, ...field } }) => ( render={({ field: { onChange, ref, ...field } }) => (
<MultiSelect <MultiSelect
aria-label="Group by"
menuShouldPortal menuShouldPortal
{...field} {...field}
allowCustomValue allowCustomValue
@@ -129,6 +131,7 @@ export const AmRootRouteForm: FC<AmRootRouteFormProps> = ({
className={styles.input} className={styles.input}
onChange={(value) => onChange(mapSelectValueToString(value))} onChange={(value) => onChange(mapSelectValueToString(value))}
options={timeOptions} options={timeOptions}
aria-label="Group wait type"
/> />
)} )}
control={control} control={control}
@@ -169,6 +172,7 @@ export const AmRootRouteForm: FC<AmRootRouteFormProps> = ({
className={styles.input} className={styles.input}
onChange={(value) => onChange(mapSelectValueToString(value))} onChange={(value) => onChange(mapSelectValueToString(value))}
options={timeOptions} options={timeOptions}
aria-label="Group interval type"
/> />
)} )}
control={control} control={control}
@@ -205,6 +209,7 @@ export const AmRootRouteForm: FC<AmRootRouteFormProps> = ({
menuPlacement="top" menuPlacement="top"
onChange={(value) => onChange(mapSelectValueToString(value))} onChange={(value) => onChange(mapSelectValueToString(value))}
options={timeOptions} options={timeOptions}
aria-label="Repeat interval type"
/> />
)} )}
control={control} control={control}

View File

@@ -79,6 +79,7 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
className={styles.matchersOperator} className={styles.matchersOperator}
onChange={(value) => onChange(value?.value)} onChange={(value) => onChange(value?.value)}
options={matcherFieldOptions} options={matcherFieldOptions}
aria-label="Operator"
/> />
)} )}
defaultValue={field.operator} defaultValue={field.operator}
@@ -127,11 +128,12 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
<InputControl <InputControl
render={({ field: { onChange, ref, ...field } }) => ( render={({ field: { onChange, ref, ...field } }) => (
<Select <Select
menuShouldPortal aria-label="Contact point"
{...field} {...field}
className={formStyles.input} className={formStyles.input}
onChange={(value) => onChange(mapSelectValueToString(value))} onChange={(value) => onChange(mapSelectValueToString(value))}
options={receivers} options={receivers}
menuShouldPortal
/> />
)} )}
control={control} control={control}
@@ -139,10 +141,11 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
/> />
</Field> </Field>
<Field label="Continue matching subsequent sibling nodes"> <Field label="Continue matching subsequent sibling nodes">
<Switch {...register('continue')} /> <Switch id="continue-toggle" {...register('continue')} />
</Field> </Field>
<Field label="Override grouping"> <Field label="Override grouping">
<Switch <Switch
id="override-grouping-toggle"
value={overrideGrouping} value={overrideGrouping}
onChange={() => setOverrideGrouping((overrideGrouping) => !overrideGrouping)} onChange={() => setOverrideGrouping((overrideGrouping) => !overrideGrouping)}
/> />
@@ -152,6 +155,7 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
<InputControl <InputControl
render={({ field: { onChange, ref, ...field } }) => ( render={({ field: { onChange, ref, ...field } }) => (
<MultiSelect <MultiSelect
aria-label="Group by"
menuShouldPortal menuShouldPortal
{...field} {...field}
allowCustomValue allowCustomValue
@@ -173,6 +177,7 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
)} )}
<Field label="Override general timings"> <Field label="Override general timings">
<Switch <Switch
id="override-timings-toggle"
value={overrideTimings} value={overrideTimings}
onChange={() => setOverrideTimings((overrideTimings) => !overrideTimings)} onChange={() => setOverrideTimings((overrideTimings) => !overrideTimings)}
/> />
@@ -189,7 +194,13 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
<div className={cx(formStyles.container, formStyles.timingContainer)}> <div className={cx(formStyles.container, formStyles.timingContainer)}>
<InputControl <InputControl
render={({ field, fieldState: { invalid } }) => ( render={({ field, fieldState: { invalid } }) => (
<Input {...field} className={formStyles.smallInput} invalid={invalid} placeholder="Time" /> <Input
{...field}
className={formStyles.smallInput}
invalid={invalid}
placeholder="Time"
aria-label="Group wait value"
/>
)} )}
control={control} control={control}
name="groupWaitValue" name="groupWaitValue"
@@ -205,6 +216,7 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
className={formStyles.input} className={formStyles.input}
onChange={(value) => onChange(mapSelectValueToString(value))} onChange={(value) => onChange(mapSelectValueToString(value))}
options={timeOptions} options={timeOptions}
aria-label="Group wait type"
/> />
)} )}
control={control} control={control}
@@ -223,7 +235,13 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
<div className={cx(formStyles.container, formStyles.timingContainer)}> <div className={cx(formStyles.container, formStyles.timingContainer)}>
<InputControl <InputControl
render={({ field, fieldState: { invalid } }) => ( render={({ field, fieldState: { invalid } }) => (
<Input {...field} className={formStyles.smallInput} invalid={invalid} placeholder="Time" /> <Input
{...field}
className={formStyles.smallInput}
invalid={invalid}
placeholder="Time"
aria-label="Group interval value"
/>
)} )}
control={control} control={control}
name="groupIntervalValue" name="groupIntervalValue"
@@ -239,6 +257,7 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
className={formStyles.input} className={formStyles.input}
onChange={(value) => onChange(mapSelectValueToString(value))} onChange={(value) => onChange(mapSelectValueToString(value))}
options={timeOptions} options={timeOptions}
aria-label="Group interval type"
/> />
)} )}
control={control} control={control}
@@ -257,7 +276,13 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
<div className={cx(formStyles.container, formStyles.timingContainer)}> <div className={cx(formStyles.container, formStyles.timingContainer)}>
<InputControl <InputControl
render={({ field, fieldState: { invalid } }) => ( render={({ field, fieldState: { invalid } }) => (
<Input {...field} className={formStyles.smallInput} invalid={invalid} placeholder="Time" /> <Input
{...field}
className={formStyles.smallInput}
invalid={invalid}
placeholder="Time"
aria-label="Repeat interval value"
/>
)} )}
control={control} control={control}
name="repeatIntervalValue" name="repeatIntervalValue"
@@ -274,6 +299,7 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
menuPlacement="top" menuPlacement="top"
onChange={(value) => onChange(mapSelectValueToString(value))} onChange={(value) => onChange(mapSelectValueToString(value))}
options={timeOptions} options={timeOptions}
aria-label="Repeat interval type"
/> />
)} )}
control={control} control={control}

View File

@@ -76,7 +76,7 @@ export const AmRoutesTable: FC<AmRoutesTableProps> = ({
return ( return (
<HorizontalGroup> <HorizontalGroup>
<Button <Button
data-testid="edit-route" aria-label="Edit route"
icon="pen" icon="pen"
onClick={expandWithCustomContent} onClick={expandWithCustomContent}
size="sm" size="sm"
@@ -86,7 +86,7 @@ export const AmRoutesTable: FC<AmRoutesTableProps> = ({
Edit Edit
</Button> </Button>
<IconButton <IconButton
data-testid="delete-route" aria-label="Delete route"
name="trash-alt" name="trash-alt"
onClick={() => { onClick={() => {
const newRoutes = [...routes]; const newRoutes = [...routes];

View File

@@ -72,17 +72,19 @@ export function ChannelSubForm<R extends ChannelValues>({
const mandatoryOptions = notifier?.options.filter((o) => o.required); const mandatoryOptions = notifier?.options.filter((o) => o.required);
const optionalOptions = notifier?.options.filter((o) => !o.required); const optionalOptions = notifier?.options.filter((o) => !o.required);
const contactPointTypeInputId = `contact-point-type-${pathPrefix}`;
return ( return (
<div className={styles.wrapper} data-testid="item-container"> <div className={styles.wrapper} data-testid="item-container">
<div className={styles.topRow}> <div className={styles.topRow}>
<div> <div>
<Field label="Contact point type" data-testid={`${pathPrefix}type`}> <Field label="Contact point type" htmlFor={contactPointTypeInputId} data-testid={`${pathPrefix}type`}>
<InputControl <InputControl
name={name('type')} name={name('type')}
defaultValue={defaultValues.type} defaultValue={defaultValues.type}
render={({ field: { ref, onChange, ...field } }) => ( render={({ field: { ref, onChange, ...field } }) => (
<Select <Select
disabled={readOnly} disabled={readOnly}
inputId={contactPointTypeInputId}
menuShouldPortal menuShouldPortal
{...field} {...field}
width={37} width={37}

View File

@@ -89,6 +89,7 @@ export const AlertTypeStep: FC<Props> = ({ editingExistingRule }) => {
render={({ field: { onChange, ref, ...field } }) => ( render={({ field: { onChange, ref, ...field } }) => (
<Select <Select
menuShouldPortal menuShouldPortal
aria-label="Rule type"
{...field} {...field}
options={alertTypeOptions} options={alertTypeOptions}
onChange={(v: SelectableValue) => onChange(v?.value)} onChange={(v: SelectableValue) => onChange(v?.value)}

View File

@@ -10,9 +10,10 @@ interface Props {
value?: string; value?: string;
width?: number; width?: number;
className?: string; className?: string;
'aria-label'?: string;
} }
export const AnnotationKeyInput: FC<Props> = ({ value, existingKeys, ...rest }) => { export const AnnotationKeyInput: FC<Props> = ({ value, existingKeys, 'aria-label': ariaLabel, ...rest }) => {
const annotationOptions = useMemo( const annotationOptions = useMemo(
(): SelectableValue[] => (): SelectableValue[] =>
Object.values(Annotation) Object.values(Annotation)
@@ -23,6 +24,7 @@ export const AnnotationKeyInput: FC<Props> = ({ value, existingKeys, ...rest })
return ( return (
<SelectWithAdd <SelectWithAdd
aria-label={ariaLabel}
value={value} value={value}
options={annotationOptions} options={annotationOptions}
custom={!!value && !(Object.values(Annotation) as string[]).includes(value)} custom={!!value && !(Object.values(Annotation) as string[]).includes(value)}

View File

@@ -42,7 +42,12 @@ const AnnotationsField: FC = () => {
<InputControl <InputControl
name={`annotations[${index}].key`} name={`annotations[${index}].key`}
render={({ field: { ref, ...field } }) => ( render={({ field: { ref, ...field } }) => (
<AnnotationKeyInput {...field} existingKeys={existingKeys(index)} width={18} /> <AnnotationKeyInput
{...field}
aria-label={`Annotation detail ${index + 1}`}
existingKeys={existingKeys(index)}
width={18}
/>
)} )}
control={control} control={control}
rules={{ required: { value: !!annotations[index]?.value, message: 'Required.' } }} rules={{ required: { value: !!annotations[index]?.value, message: 'Required.' } }}

View File

@@ -48,6 +48,7 @@ export const ConditionField: FC = () => {
render={({ field: { onChange, ref, ...field } }) => ( render={({ field: { onChange, ref, ...field } }) => (
<Select <Select
menuShouldPortal menuShouldPortal
aria-label="Condition"
{...field} {...field}
width={42} width={42}
options={options} options={options}

View File

@@ -51,12 +51,19 @@ export const GrafanaConditionsStep: FC = () => {
formState: { errors }, formState: { errors },
} = useFormContext<RuleFormValues>(); } = useFormContext<RuleFormValues>();
const evaluateEveryId = 'eval-every-input';
const evaluateForId = 'eval-for-input';
return ( return (
<RuleEditorSection stepNo={3} title="Define alert conditions"> <RuleEditorSection stepNo={3} title="Define alert conditions">
<ConditionField /> <ConditionField />
<Field label="Evaluate"> <Field label="Evaluate">
<div className={styles.flexRow}> <div className={styles.flexRow}>
<InlineLabel width={16} tooltip="How often the alert will be evaluated to see if it fires"> <InlineLabel
htmlFor={evaluateEveryId}
width={16}
tooltip="How often the alert will be evaluated to see if it fires"
>
Evaluate every Evaluate every
</InlineLabel> </InlineLabel>
<Field <Field
@@ -65,9 +72,10 @@ export const GrafanaConditionsStep: FC = () => {
invalid={!!errors.evaluateEvery?.message} invalid={!!errors.evaluateEvery?.message}
validationMessageHorizontalOverflow={true} validationMessageHorizontalOverflow={true}
> >
<Input width={8} {...register('evaluateEvery', evaluateEveryValidationOptions)} /> <Input id={evaluateEveryId} width={8} {...register('evaluateEvery', evaluateEveryValidationOptions)} />
</Field> </Field>
<InlineLabel <InlineLabel
htmlFor={evaluateForId}
width={7} width={7}
tooltip='Once condition is breached, alert will go into pending state. If it is pending for longer than the "for" value, it will become a firing alert.' tooltip='Once condition is breached, alert will go into pending state. If it is pending for longer than the "for" value, it will become a firing alert.'
> >
@@ -79,7 +87,7 @@ export const GrafanaConditionsStep: FC = () => {
invalid={!!errors.evaluateFor?.message} invalid={!!errors.evaluateFor?.message}
validationMessageHorizontalOverflow={true} validationMessageHorizontalOverflow={true}
> >
<Input width={8} {...register('evaluateFor', forValidationOptions)} /> <Input id={evaluateForId} width={8} {...register('evaluateFor', forValidationOptions)} />
</Field> </Field>
</div> </div>
</Field> </Field>
@@ -92,11 +100,12 @@ export const GrafanaConditionsStep: FC = () => {
/> />
{showErrorHandling && ( {showErrorHandling && (
<> <>
<Field label="Alert state if no data or all values are null"> <Field htmlFor="no-data-state-input" label="Alert state if no data or all values are null">
<InputControl <InputControl
render={({ field: { onChange, ref, ...field } }) => ( render={({ field: { onChange, ref, ...field } }) => (
<GrafanaAlertStatePicker <GrafanaAlertStatePicker
{...field} {...field}
inputId="no-data-state-input"
width={42} width={42}
includeNoData={true} includeNoData={true}
onChange={(value) => onChange(value?.value)} onChange={(value) => onChange(value?.value)}
@@ -105,11 +114,12 @@ export const GrafanaConditionsStep: FC = () => {
name="noDataState" name="noDataState"
/> />
</Field> </Field>
<Field label="Alert state if execution error or timeout"> <Field htmlFor="exec-err-state-input" label="Alert state if execution error or timeout">
<InputControl <InputControl
render={({ field: { onChange, ref, ...field } }) => ( render={({ field: { onChange, ref, ...field } }) => (
<GrafanaAlertStatePicker <GrafanaAlertStatePicker
{...field} {...field}
inputId="exec-err-state-input"
width={42} width={42}
includeNoData={false} includeNoData={false}
onChange={(value) => onChange(value?.value)} onChange={(value) => onChange(value?.value)}

View File

@@ -13,6 +13,7 @@ interface Props {
onCustomChange?: (custom: boolean) => void; onCustomChange?: (custom: boolean) => void;
width?: number; width?: number;
disabled?: boolean; disabled?: boolean;
'aria-label'?: string;
} }
export const SelectWithAdd: FC<Props> = ({ export const SelectWithAdd: FC<Props> = ({
@@ -26,6 +27,7 @@ export const SelectWithAdd: FC<Props> = ({
onCustomChange, onCustomChange,
disabled = false, disabled = false,
addLabel = '+ Add new', addLabel = '+ Add new',
'aria-label': ariaLabel,
}) => { }) => {
const [isCustom, setIsCustom] = useState(custom); const [isCustom, setIsCustom] = useState(custom);
@@ -43,6 +45,7 @@ export const SelectWithAdd: FC<Props> = ({
if (isCustom) { if (isCustom) {
return ( return (
<Input <Input
aria-label={ariaLabel}
width={width} width={width}
autoFocus={!custom} autoFocus={!custom}
value={value || ''} value={value || ''}
@@ -56,6 +59,7 @@ export const SelectWithAdd: FC<Props> = ({
return ( return (
<Select <Select
menuShouldPortal menuShouldPortal
aria-label={ariaLabel}
width={width} width={width}
options={_options} options={_options}
value={value} value={value}

View File

@@ -29,12 +29,13 @@ export const ActionIcon: FC<Props> = ({
}) => { }) => {
const iconEl = <Icon className={cx(useStyles(getStyle), className)} onClick={onClick} name={icon} {...rest} />; const iconEl = <Icon className={cx(useStyles(getStyle), className)} onClick={onClick} name={icon} {...rest} />;
const ariaLabel = typeof tooltip === 'string' ? tooltip : undefined;
return ( return (
<Tooltip content={tooltip} placement={tooltipPlacement}> <Tooltip content={tooltip} placement={tooltipPlacement}>
{(() => { {(() => {
if (to) { if (to) {
return ( return (
<Link to={to} target={target}> <Link aria-label={ariaLabel} to={to} target={target}>
{iconEl} {iconEl}
</Link> </Link>
); );

View File

@@ -52,7 +52,7 @@ export function RuleDetailsDataSources(props: Props): JSX.Element | null {
<div key={name}> <div key={name}>
{icon && ( {icon && (
<> <>
<img className={styles.dataSourceIcon} src={icon} />{' '} <img alt={`${name} datasource logo`} className={styles.dataSourceIcon} src={icon} />{' '}
</> </>
)} )}
{name} {name}