mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
A11y: Fix various fastpass accessibility issues (#41154)
This commit is contained in:
@@ -6,14 +6,16 @@ interface Props {
|
||||
value: OrgRole;
|
||||
disabled?: boolean;
|
||||
'aria-label'?: string;
|
||||
inputId?: string;
|
||||
onChange: (role: OrgRole) => void;
|
||||
}
|
||||
|
||||
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
|
||||
menuShouldPortal
|
||||
inputId={inputId}
|
||||
value={value}
|
||||
options={options}
|
||||
onChange={(val) => onChange(val.value as OrgRole)}
|
||||
|
@@ -45,15 +45,15 @@ const UserCreatePage: React.FC<UserCreatePageProps> = ({ navModel }) => {
|
||||
invalid={!!errors.name}
|
||||
error={errors.name ? 'Name is required' : undefined}
|
||||
>
|
||||
<Input {...register('name', { required: true })} />
|
||||
<Input id="name-input" {...register('name', { required: true })} />
|
||||
</Field>
|
||||
|
||||
<Field label="Email">
|
||||
<Input {...register('email')} />
|
||||
<Input id="email-input" {...register('email')} />
|
||||
</Field>
|
||||
|
||||
<Field label="Username">
|
||||
<Input {...register('login')} />
|
||||
<Input id="username-input" {...register('login')} />
|
||||
</Field>
|
||||
<Field
|
||||
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}
|
||||
>
|
||||
<Input
|
||||
id="password-input"
|
||||
{...register('password', {
|
||||
validate: (value) => value.trim() !== '' && value.length >= 4,
|
||||
})}
|
||||
|
@@ -151,12 +151,15 @@ class UnThemedOrgRow extends PureComponent<OrgRowProps, OrgRowState> {
|
||||
const canChangeRole = contextSrv.hasPermission(AccessControlAction.OrgUsersRoleUpdate);
|
||||
const canRemoveFromOrg = contextSrv.hasPermission(AccessControlAction.OrgUsersRemove);
|
||||
|
||||
const inputId = `${org.name}-input`;
|
||||
return (
|
||||
<tr>
|
||||
<td className={labelClass}>{org.name}</td>
|
||||
<td className={labelClass}>
|
||||
<label htmlFor={inputId}>{org.name}</label>
|
||||
</td>
|
||||
{isChangingRole ? (
|
||||
<td>
|
||||
<OrgRolePicker value={currentRole} onChange={this.onOrgRoleChange} />
|
||||
<OrgRolePicker inputId={inputId} value={currentRole} onChange={this.onOrgRoleChange} />
|
||||
</td>
|
||||
) : (
|
||||
<td className="width-25">{org.role}</td>
|
||||
@@ -257,10 +260,10 @@ export class AddToOrgModal extends PureComponent<AddToOrgModalProps, AddToOrgMod
|
||||
onDismiss={this.onCancel}
|
||||
>
|
||||
<Field label="Organization">
|
||||
<OrgPicker onSelected={this.onOrgSelect} />
|
||||
<OrgPicker inputId="new-org-input" onSelected={this.onOrgSelect} />
|
||||
</Field>
|
||||
<Field label="Role">
|
||||
<OrgRolePicker value={role} onChange={this.onOrgRoleChange} />
|
||||
<OrgRolePicker inputId="new-org-role-input" value={role} onChange={this.onOrgRoleChange} />
|
||||
</Field>
|
||||
<Modal.ButtonRow>
|
||||
<HorizontalGroup spacing="md" justify="center">
|
||||
|
@@ -275,12 +275,16 @@ export class UserProfileRow extends PureComponent<UserProfileRowProps, UserProfi
|
||||
return <LockedRow label={label} value={value} lockMessage={lockMessage} />;
|
||||
}
|
||||
|
||||
const inputId = `${label}-input`;
|
||||
return (
|
||||
<tr>
|
||||
<td className={labelClass}>{label}</td>
|
||||
<td className={labelClass}>
|
||||
<label htmlFor={inputId}>{label}</label>
|
||||
</td>
|
||||
<td className="width-25" colSpan={2}>
|
||||
{this.state.editing ? (
|
||||
<Input
|
||||
id={inputId}
|
||||
type={inputType}
|
||||
defaultValue={value}
|
||||
onBlur={this.onInputBlur}
|
||||
|
@@ -11,7 +11,7 @@ import {
|
||||
} from 'app/plugins/datasource/alertmanager/types';
|
||||
import { configureStore } from 'app/store/configureStore';
|
||||
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 { fetchAlertManagerConfig, fetchStatus, updateAlertManagerConfig } from './api/alertmanager';
|
||||
import { mockDataSource, MockDataSourceSrv, someCloudAlertManagerConfig, someCloudAlertManagerStatus } from './mocks';
|
||||
@@ -76,8 +76,8 @@ const ui = {
|
||||
editButton: byRole('button', { name: 'Edit' }),
|
||||
saveButton: byRole('button', { name: 'Save' }),
|
||||
|
||||
editRouteButton: byTestId('edit-route'),
|
||||
deleteRouteButton: byTestId('delete-route'),
|
||||
editRouteButton: byLabelText('Edit route'),
|
||||
deleteRouteButton: byLabelText('Delete route'),
|
||||
newPolicyButton: byRole('button', { name: /New policy/ }),
|
||||
|
||||
receiverSelect: byTestId('am-receiver-select'),
|
||||
|
@@ -14,7 +14,12 @@ export const CollapseToggle: FC<Props> = ({ isCollapsed, onToggle, className, te
|
||||
const styles = useStyles(getStyles);
|
||||
|
||||
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'} />
|
||||
{text}
|
||||
</button>
|
||||
|
@@ -96,6 +96,7 @@ export const DynamicTable = <T extends object>({
|
||||
{isExpandable && (
|
||||
<div className={cx(styles.cell, styles.expandCell)}>
|
||||
<IconButton
|
||||
aria-label={`${isItemExpanded ? 'Collapse' : 'Expand'} row`}
|
||||
size="xl"
|
||||
data-testid="collapse-toggle"
|
||||
className={styles.expandButton}
|
||||
|
@@ -42,11 +42,12 @@ export const AmRootRouteForm: FC<AmRootRouteFormProps> = ({
|
||||
<InputControl
|
||||
render={({ field: { onChange, ref, ...field } }) => (
|
||||
<Select
|
||||
menuShouldPortal
|
||||
aria-label="Default contact point"
|
||||
{...field}
|
||||
className={styles.input}
|
||||
onChange={(value) => onChange(mapSelectValueToString(value))}
|
||||
options={receivers}
|
||||
menuShouldPortal
|
||||
/>
|
||||
)}
|
||||
control={control}
|
||||
@@ -72,6 +73,7 @@ export const AmRootRouteForm: FC<AmRootRouteFormProps> = ({
|
||||
<InputControl
|
||||
render={({ field: { onChange, ref, ...field } }) => (
|
||||
<MultiSelect
|
||||
aria-label="Group by"
|
||||
menuShouldPortal
|
||||
{...field}
|
||||
allowCustomValue
|
||||
@@ -129,6 +131,7 @@ export const AmRootRouteForm: FC<AmRootRouteFormProps> = ({
|
||||
className={styles.input}
|
||||
onChange={(value) => onChange(mapSelectValueToString(value))}
|
||||
options={timeOptions}
|
||||
aria-label="Group wait type"
|
||||
/>
|
||||
)}
|
||||
control={control}
|
||||
@@ -169,6 +172,7 @@ export const AmRootRouteForm: FC<AmRootRouteFormProps> = ({
|
||||
className={styles.input}
|
||||
onChange={(value) => onChange(mapSelectValueToString(value))}
|
||||
options={timeOptions}
|
||||
aria-label="Group interval type"
|
||||
/>
|
||||
)}
|
||||
control={control}
|
||||
@@ -205,6 +209,7 @@ export const AmRootRouteForm: FC<AmRootRouteFormProps> = ({
|
||||
menuPlacement="top"
|
||||
onChange={(value) => onChange(mapSelectValueToString(value))}
|
||||
options={timeOptions}
|
||||
aria-label="Repeat interval type"
|
||||
/>
|
||||
)}
|
||||
control={control}
|
||||
|
@@ -79,6 +79,7 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
|
||||
className={styles.matchersOperator}
|
||||
onChange={(value) => onChange(value?.value)}
|
||||
options={matcherFieldOptions}
|
||||
aria-label="Operator"
|
||||
/>
|
||||
)}
|
||||
defaultValue={field.operator}
|
||||
@@ -127,11 +128,12 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
|
||||
<InputControl
|
||||
render={({ field: { onChange, ref, ...field } }) => (
|
||||
<Select
|
||||
menuShouldPortal
|
||||
aria-label="Contact point"
|
||||
{...field}
|
||||
className={formStyles.input}
|
||||
onChange={(value) => onChange(mapSelectValueToString(value))}
|
||||
options={receivers}
|
||||
menuShouldPortal
|
||||
/>
|
||||
)}
|
||||
control={control}
|
||||
@@ -139,10 +141,11 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Continue matching subsequent sibling nodes">
|
||||
<Switch {...register('continue')} />
|
||||
<Switch id="continue-toggle" {...register('continue')} />
|
||||
</Field>
|
||||
<Field label="Override grouping">
|
||||
<Switch
|
||||
id="override-grouping-toggle"
|
||||
value={overrideGrouping}
|
||||
onChange={() => setOverrideGrouping((overrideGrouping) => !overrideGrouping)}
|
||||
/>
|
||||
@@ -152,6 +155,7 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
|
||||
<InputControl
|
||||
render={({ field: { onChange, ref, ...field } }) => (
|
||||
<MultiSelect
|
||||
aria-label="Group by"
|
||||
menuShouldPortal
|
||||
{...field}
|
||||
allowCustomValue
|
||||
@@ -173,6 +177,7 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
|
||||
)}
|
||||
<Field label="Override general timings">
|
||||
<Switch
|
||||
id="override-timings-toggle"
|
||||
value={overrideTimings}
|
||||
onChange={() => setOverrideTimings((overrideTimings) => !overrideTimings)}
|
||||
/>
|
||||
@@ -189,7 +194,13 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
|
||||
<div className={cx(formStyles.container, formStyles.timingContainer)}>
|
||||
<InputControl
|
||||
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}
|
||||
name="groupWaitValue"
|
||||
@@ -205,6 +216,7 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
|
||||
className={formStyles.input}
|
||||
onChange={(value) => onChange(mapSelectValueToString(value))}
|
||||
options={timeOptions}
|
||||
aria-label="Group wait type"
|
||||
/>
|
||||
)}
|
||||
control={control}
|
||||
@@ -223,7 +235,13 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
|
||||
<div className={cx(formStyles.container, formStyles.timingContainer)}>
|
||||
<InputControl
|
||||
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}
|
||||
name="groupIntervalValue"
|
||||
@@ -239,6 +257,7 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
|
||||
className={formStyles.input}
|
||||
onChange={(value) => onChange(mapSelectValueToString(value))}
|
||||
options={timeOptions}
|
||||
aria-label="Group interval type"
|
||||
/>
|
||||
)}
|
||||
control={control}
|
||||
@@ -257,7 +276,13 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
|
||||
<div className={cx(formStyles.container, formStyles.timingContainer)}>
|
||||
<InputControl
|
||||
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}
|
||||
name="repeatIntervalValue"
|
||||
@@ -274,6 +299,7 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
|
||||
menuPlacement="top"
|
||||
onChange={(value) => onChange(mapSelectValueToString(value))}
|
||||
options={timeOptions}
|
||||
aria-label="Repeat interval type"
|
||||
/>
|
||||
)}
|
||||
control={control}
|
||||
|
@@ -76,7 +76,7 @@ export const AmRoutesTable: FC<AmRoutesTableProps> = ({
|
||||
return (
|
||||
<HorizontalGroup>
|
||||
<Button
|
||||
data-testid="edit-route"
|
||||
aria-label="Edit route"
|
||||
icon="pen"
|
||||
onClick={expandWithCustomContent}
|
||||
size="sm"
|
||||
@@ -86,7 +86,7 @@ export const AmRoutesTable: FC<AmRoutesTableProps> = ({
|
||||
Edit
|
||||
</Button>
|
||||
<IconButton
|
||||
data-testid="delete-route"
|
||||
aria-label="Delete route"
|
||||
name="trash-alt"
|
||||
onClick={() => {
|
||||
const newRoutes = [...routes];
|
||||
|
@@ -72,17 +72,19 @@ export function ChannelSubForm<R extends ChannelValues>({
|
||||
const mandatoryOptions = notifier?.options.filter((o) => o.required);
|
||||
const optionalOptions = notifier?.options.filter((o) => !o.required);
|
||||
|
||||
const contactPointTypeInputId = `contact-point-type-${pathPrefix}`;
|
||||
return (
|
||||
<div className={styles.wrapper} data-testid="item-container">
|
||||
<div className={styles.topRow}>
|
||||
<div>
|
||||
<Field label="Contact point type" data-testid={`${pathPrefix}type`}>
|
||||
<Field label="Contact point type" htmlFor={contactPointTypeInputId} data-testid={`${pathPrefix}type`}>
|
||||
<InputControl
|
||||
name={name('type')}
|
||||
defaultValue={defaultValues.type}
|
||||
render={({ field: { ref, onChange, ...field } }) => (
|
||||
<Select
|
||||
disabled={readOnly}
|
||||
inputId={contactPointTypeInputId}
|
||||
menuShouldPortal
|
||||
{...field}
|
||||
width={37}
|
||||
|
@@ -89,6 +89,7 @@ export const AlertTypeStep: FC<Props> = ({ editingExistingRule }) => {
|
||||
render={({ field: { onChange, ref, ...field } }) => (
|
||||
<Select
|
||||
menuShouldPortal
|
||||
aria-label="Rule type"
|
||||
{...field}
|
||||
options={alertTypeOptions}
|
||||
onChange={(v: SelectableValue) => onChange(v?.value)}
|
||||
|
@@ -10,9 +10,10 @@ interface Props {
|
||||
value?: string;
|
||||
width?: number;
|
||||
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(
|
||||
(): SelectableValue[] =>
|
||||
Object.values(Annotation)
|
||||
@@ -23,6 +24,7 @@ export const AnnotationKeyInput: FC<Props> = ({ value, existingKeys, ...rest })
|
||||
|
||||
return (
|
||||
<SelectWithAdd
|
||||
aria-label={ariaLabel}
|
||||
value={value}
|
||||
options={annotationOptions}
|
||||
custom={!!value && !(Object.values(Annotation) as string[]).includes(value)}
|
||||
|
@@ -42,7 +42,12 @@ const AnnotationsField: FC = () => {
|
||||
<InputControl
|
||||
name={`annotations[${index}].key`}
|
||||
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}
|
||||
rules={{ required: { value: !!annotations[index]?.value, message: 'Required.' } }}
|
||||
|
@@ -48,6 +48,7 @@ export const ConditionField: FC = () => {
|
||||
render={({ field: { onChange, ref, ...field } }) => (
|
||||
<Select
|
||||
menuShouldPortal
|
||||
aria-label="Condition"
|
||||
{...field}
|
||||
width={42}
|
||||
options={options}
|
||||
|
@@ -51,12 +51,19 @@ export const GrafanaConditionsStep: FC = () => {
|
||||
formState: { errors },
|
||||
} = useFormContext<RuleFormValues>();
|
||||
|
||||
const evaluateEveryId = 'eval-every-input';
|
||||
const evaluateForId = 'eval-for-input';
|
||||
|
||||
return (
|
||||
<RuleEditorSection stepNo={3} title="Define alert conditions">
|
||||
<ConditionField />
|
||||
<Field label="Evaluate">
|
||||
<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
|
||||
</InlineLabel>
|
||||
<Field
|
||||
@@ -65,9 +72,10 @@ export const GrafanaConditionsStep: FC = () => {
|
||||
invalid={!!errors.evaluateEvery?.message}
|
||||
validationMessageHorizontalOverflow={true}
|
||||
>
|
||||
<Input width={8} {...register('evaluateEvery', evaluateEveryValidationOptions)} />
|
||||
<Input id={evaluateEveryId} width={8} {...register('evaluateEvery', evaluateEveryValidationOptions)} />
|
||||
</Field>
|
||||
<InlineLabel
|
||||
htmlFor={evaluateForId}
|
||||
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.'
|
||||
>
|
||||
@@ -79,7 +87,7 @@ export const GrafanaConditionsStep: FC = () => {
|
||||
invalid={!!errors.evaluateFor?.message}
|
||||
validationMessageHorizontalOverflow={true}
|
||||
>
|
||||
<Input width={8} {...register('evaluateFor', forValidationOptions)} />
|
||||
<Input id={evaluateForId} width={8} {...register('evaluateFor', forValidationOptions)} />
|
||||
</Field>
|
||||
</div>
|
||||
</Field>
|
||||
@@ -92,11 +100,12 @@ export const GrafanaConditionsStep: FC = () => {
|
||||
/>
|
||||
{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
|
||||
render={({ field: { onChange, ref, ...field } }) => (
|
||||
<GrafanaAlertStatePicker
|
||||
{...field}
|
||||
inputId="no-data-state-input"
|
||||
width={42}
|
||||
includeNoData={true}
|
||||
onChange={(value) => onChange(value?.value)}
|
||||
@@ -105,11 +114,12 @@ export const GrafanaConditionsStep: FC = () => {
|
||||
name="noDataState"
|
||||
/>
|
||||
</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
|
||||
render={({ field: { onChange, ref, ...field } }) => (
|
||||
<GrafanaAlertStatePicker
|
||||
{...field}
|
||||
inputId="exec-err-state-input"
|
||||
width={42}
|
||||
includeNoData={false}
|
||||
onChange={(value) => onChange(value?.value)}
|
||||
|
@@ -13,6 +13,7 @@ interface Props {
|
||||
onCustomChange?: (custom: boolean) => void;
|
||||
width?: number;
|
||||
disabled?: boolean;
|
||||
'aria-label'?: string;
|
||||
}
|
||||
|
||||
export const SelectWithAdd: FC<Props> = ({
|
||||
@@ -26,6 +27,7 @@ export const SelectWithAdd: FC<Props> = ({
|
||||
onCustomChange,
|
||||
disabled = false,
|
||||
addLabel = '+ Add new',
|
||||
'aria-label': ariaLabel,
|
||||
}) => {
|
||||
const [isCustom, setIsCustom] = useState(custom);
|
||||
|
||||
@@ -43,6 +45,7 @@ export const SelectWithAdd: FC<Props> = ({
|
||||
if (isCustom) {
|
||||
return (
|
||||
<Input
|
||||
aria-label={ariaLabel}
|
||||
width={width}
|
||||
autoFocus={!custom}
|
||||
value={value || ''}
|
||||
@@ -56,6 +59,7 @@ export const SelectWithAdd: FC<Props> = ({
|
||||
return (
|
||||
<Select
|
||||
menuShouldPortal
|
||||
aria-label={ariaLabel}
|
||||
width={width}
|
||||
options={_options}
|
||||
value={value}
|
||||
|
@@ -29,12 +29,13 @@ export const ActionIcon: FC<Props> = ({
|
||||
}) => {
|
||||
const iconEl = <Icon className={cx(useStyles(getStyle), className)} onClick={onClick} name={icon} {...rest} />;
|
||||
|
||||
const ariaLabel = typeof tooltip === 'string' ? tooltip : undefined;
|
||||
return (
|
||||
<Tooltip content={tooltip} placement={tooltipPlacement}>
|
||||
{(() => {
|
||||
if (to) {
|
||||
return (
|
||||
<Link to={to} target={target}>
|
||||
<Link aria-label={ariaLabel} to={to} target={target}>
|
||||
{iconEl}
|
||||
</Link>
|
||||
);
|
||||
|
@@ -52,7 +52,7 @@ export function RuleDetailsDataSources(props: Props): JSX.Element | null {
|
||||
<div key={name}>
|
||||
{icon && (
|
||||
<>
|
||||
<img className={styles.dataSourceIcon} src={icon} />{' '}
|
||||
<img alt={`${name} datasource logo`} className={styles.dataSourceIcon} src={icon} />{' '}
|
||||
</>
|
||||
)}
|
||||
{name}
|
||||
|
Reference in New Issue
Block a user