A11y: Fix fastpass issues for /org/ pages (#39902)

* A11y: Fix fastpass issues for /org/ pages
See #39429
This commit is contained in:
kay delaney 2021-10-01 15:58:18 +01:00 committed by GitHub
parent 54afe20b44
commit 816d70a7e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 35 additions and 34 deletions

View File

@ -80,7 +80,7 @@ export function Modal(props: PropsWithChildren<Props>) {
{typeof title === 'string' && <DefaultModalHeader {...props} title={title} />} {typeof title === 'string' && <DefaultModalHeader {...props} title={title} />}
{typeof title !== 'string' && title} {typeof title !== 'string' && title}
<div className={styles.modalHeaderClose}> <div className={styles.modalHeaderClose}>
<IconButton surface="header" name="times" size="xl" onClick={onDismiss} /> <IconButton aria-label="Close dialogue" surface="header" name="times" size="xl" onClick={onDismiss} />
</div> </div>
</div> </div>
<div className={cx(styles.modalContent, contentClassName)}>{children}</div> <div className={cx(styles.modalContent, contentClassName)}>{children}</div>

View File

@ -1,11 +1,15 @@
import React, { ChangeEvent, FC, FormEvent, useEffect, useState } from 'react'; import React, { ChangeEvent, FC, FormEvent, useEffect, useState } from 'react';
import { EventsWithValidation, InlineFormLabel, LegacyForms, ValidationEvents, Button } from '@grafana/ui'; import { EventsWithValidation, LegacyForms, ValidationEvents, Button, Select, InlineField } from '@grafana/ui';
import { NewApiKey, OrgRole } from '../../types'; import { NewApiKey, OrgRole } from '../../types';
import { rangeUtil } from '@grafana/data'; import { rangeUtil, SelectableValue } from '@grafana/data';
import { SlideDown } from '../../core/components/Animations/SlideDown'; import { SlideDown } from '../../core/components/Animations/SlideDown';
import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; import { CloseButton } from 'app/core/components/CloseButton/CloseButton';
const { Input } = LegacyForms; const { Input } = LegacyForms;
const ROLE_OPTIONS: Array<SelectableValue<OrgRole>> = Object.keys(OrgRole).map((role) => ({
label: role,
value: role as OrgRole,
}));
interface Props { interface Props {
show: boolean; show: boolean;
@ -56,8 +60,8 @@ export const ApiKeysForm: FC<Props> = ({ show, onClose, onKeyAdded }) => {
const onNameChange = (event: ChangeEvent<HTMLInputElement>) => { const onNameChange = (event: ChangeEvent<HTMLInputElement>) => {
setName(event.currentTarget.value); setName(event.currentTarget.value);
}; };
const onRoleChange = (event: ChangeEvent<HTMLSelectElement>) => { const onRoleChange = (role: SelectableValue<OrgRole>) => {
setRole(event.currentTarget.value as OrgRole); setRole(role.value!);
}; };
const onSecondsToLiveChange = (event: ChangeEvent<HTMLInputElement>) => { const onSecondsToLiveChange = (event: ChangeEvent<HTMLInputElement>) => {
setSecondsToLive(event.currentTarget.value); setSecondsToLive(event.currentTarget.value);
@ -75,29 +79,27 @@ export const ApiKeysForm: FC<Props> = ({ show, onClose, onKeyAdded }) => {
<Input type="text" className="gf-form-input" value={name} placeholder="Name" onChange={onNameChange} /> <Input type="text" className="gf-form-input" value={name} placeholder="Name" onChange={onNameChange} />
</div> </div>
<div className="gf-form"> <div className="gf-form">
<span className="gf-form-label">Role</span> <InlineField label="Role">
<span className="gf-form-select-wrapper"> <Select
<select className="gf-form-input gf-size-auto" value={role} onChange={onRoleChange}> inputId="role-select"
{Object.keys(OrgRole).map((role) => { value={role}
return ( onChange={onRoleChange}
<option key={role} label={role} value={role}> options={ROLE_OPTIONS}
{role} menuShouldPortal
</option> />
); </InlineField>
})}
</select>
</span>
</div> </div>
<div className="gf-form max-width-21"> <div className="gf-form max-width-21">
<InlineFormLabel tooltip={tooltipText}>Time to live</InlineFormLabel> <InlineField tooltip={tooltipText} label="Time to live">
<Input <Input
type="text" id="time-to-live-input"
className="gf-form-input" type="text"
placeholder="1d" placeholder="1d"
validationEvents={timeRangeValidationEvents} validationEvents={timeRangeValidationEvents}
value={secondsToLive} value={secondsToLive}
onChange={onSecondsToLiveChange} onChange={onSecondsToLiveChange}
/> />
</InlineField>
</div> </div>
<div className="gf-form"> <div className="gf-form">
<Button>Add</Button> <Button>Add</Button>

View File

@ -124,8 +124,8 @@ describe('ApiKeysPage', () => {
deleteApiKeyMock.mockClear(); deleteApiKeyMock.mockClear();
expect(within(firstRow).getByRole('cell', { name: /cancel delete/i })).toBeInTheDocument(); expect(within(firstRow).getByRole('cell', { name: /cancel delete/i })).toBeInTheDocument();
userEvent.click(within(firstRow).getByRole('cell', { name: /cancel delete/i })); userEvent.click(within(firstRow).getByRole('cell', { name: /cancel delete/i }));
expect(within(firstRow).getByRole('button', { name: /delete/i })).toBeInTheDocument(); expect(within(firstRow).getByRole('button', { name: /delete$/i })).toBeInTheDocument();
userEvent.click(within(firstRow).getByRole('button', { name: /delete/i })); userEvent.click(within(firstRow).getByRole('button', { name: /delete$/i }));
expect(deleteApiKeyMock).toHaveBeenCalledTimes(1); expect(deleteApiKeyMock).toHaveBeenCalledTimes(1);
expect(deleteApiKeyMock).toHaveBeenCalledWith(1, false); expect(deleteApiKeyMock).toHaveBeenCalledWith(1, false);
@ -134,8 +134,8 @@ describe('ApiKeysPage', () => {
deleteApiKeyMock.mockClear(); deleteApiKeyMock.mockClear();
expect(within(secondRow).getByRole('cell', { name: /cancel delete/i })).toBeInTheDocument(); expect(within(secondRow).getByRole('cell', { name: /cancel delete/i })).toBeInTheDocument();
userEvent.click(within(secondRow).getByRole('cell', { name: /cancel delete/i })); userEvent.click(within(secondRow).getByRole('cell', { name: /cancel delete/i }));
expect(within(secondRow).getByRole('button', { name: /delete/i })).toBeInTheDocument(); expect(within(secondRow).getByRole('button', { name: /delete$/i })).toBeInTheDocument();
userEvent.click(within(secondRow).getByRole('button', { name: /delete/i })); userEvent.click(within(secondRow).getByRole('button', { name: /delete$/i }));
expect(deleteApiKeyMock).toHaveBeenCalledTimes(1); expect(deleteApiKeyMock).toHaveBeenCalledTimes(1);
expect(deleteApiKeyMock).toHaveBeenCalledWith(2, true); expect(deleteApiKeyMock).toHaveBeenCalledWith(2, true);
}); });
@ -194,7 +194,6 @@ function toggleShowExpired() {
async function addAndVerifyApiKey(addApiKeyMock: jest.Mock, includeExpired: boolean) { async function addAndVerifyApiKey(addApiKeyMock: jest.Mock, includeExpired: boolean) {
expect(screen.getByRole('heading', { name: /add api key/i })).toBeInTheDocument(); expect(screen.getByRole('heading', { name: /add api key/i })).toBeInTheDocument();
expect(screen.getByPlaceholderText(/name/i)).toBeInTheDocument(); expect(screen.getByPlaceholderText(/name/i)).toBeInTheDocument();
expect(screen.getByRole('combobox')).toBeInTheDocument();
expect(screen.getByPlaceholderText(/1d/i)).toBeInTheDocument(); expect(screen.getByPlaceholderText(/1d/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /^add$/i })).toBeInTheDocument(); expect(screen.getByRole('button', { name: /^add$/i })).toBeInTheDocument();

View File

@ -30,7 +30,7 @@ export const ApiKeysTable: FC<Props> = ({ apiKeys, timeZone, onDelete }) => {
<td>{key.role}</td> <td>{key.role}</td>
<td>{formatDate(key.expiration, timeZone)}</td> <td>{formatDate(key.expiration, timeZone)}</td>
<td> <td>
<DeleteButton size="sm" onConfirm={() => onDelete(key)} /> <DeleteButton aria-label="Delete API key" size="sm" onConfirm={() => onDelete(key)} />
</td> </td>
</tr> </tr>
); );

View File

@ -16,7 +16,7 @@ const OrgProfile: FC<Props> = ({ onSubmit, orgName }) => {
{({ register }) => ( {({ register }) => (
<FieldSet label="Organization profile"> <FieldSet label="Organization profile">
<Field label="Organization name"> <Field label="Organization name">
<Input type="text" {...register('orgName', { required: true })} /> <Input id="org-name-input" type="text" {...register('orgName', { required: true })} />
</Field> </Field>
<Button type="submit">Update organization name</Button> <Button type="submit">Update organization name</Button>

View File

@ -72,7 +72,7 @@ export const UserInviteForm: FC<Props> = ({}) => {
/> />
</Field> </Field>
<Field label="Send invite email"> <Field label="Send invite email">
<Switch {...register('sendEmail')} /> <Switch id="send-email-switch" {...register('sendEmail')} />
</Field> </Field>
<HorizontalGroup> <HorizontalGroup>
<Button type="submit">Submit</Button> <Button type="submit">Submit</Button>