mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
A11y: Fix fastpass issues for /org/ pages (#39902)
* A11y: Fix fastpass issues for /org/ pages See #39429
This commit is contained in:
parent
54afe20b44
commit
816d70a7e5
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user