Azure Monitor: Fastpass Fixes -- Add trap focus for modals in grafana/ui and other small a11y fixes for Azure Monitor. (#41449)

pull/42246/head
Sarah Zinger 4 years ago committed by GitHub
parent 24d4c8a9d1
commit fc8d93e231
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 19
      packages/grafana-ui/src/components/Modal/Modal.tsx
  2. 58
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AzureCredentialsForm.test.tsx
  3. 14
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/AzureCredentialsForm.tsx
  4. 626
      public/app/plugins/datasource/grafana-azure-monitor-datasource/components/__snapshots__/AzureCredentialsForm.test.tsx.snap

@ -7,6 +7,7 @@ import { getModalStyles } from './getModalStyles';
import { ModalHeader } from './ModalHeader';
import { IconButton } from '../IconButton/IconButton';
import { HorizontalGroup } from '../Layout/Layout';
import { FocusScope } from '@react-aria/focus';
export interface Props {
/** @deprecated no longer used */
@ -75,16 +76,18 @@ export function Modal(props: PropsWithChildren<Props>) {
className={styles.modalBackdrop}
onClick={onClickBackdrop || (closeOnBackdropClick ? onDismiss : undefined)}
/>
<div className={cx(styles.modal, className)}>
<div className={headerClass}>
{typeof title === 'string' && <DefaultModalHeader {...props} title={title} />}
{typeof title !== 'string' && title}
<div className={styles.modalHeaderClose}>
<IconButton aria-label="Close dialogue" surface="header" name="times" size="xl" onClick={onDismiss} />
<FocusScope contain autoFocus restoreFocus>
<div className={cx(styles.modal, className)}>
<div className={headerClass}>
{typeof title === 'string' && <DefaultModalHeader {...props} title={title} />}
{typeof title !== 'string' && title}
<div className={styles.modalHeaderClose}>
<IconButton aria-label="Close dialogue" surface="header" name="times" size="xl" onClick={onDismiss} />
</div>
</div>
<div className={cx(styles.modalContent, contentClassName)}>{children}</div>
</div>
<div className={cx(styles.modalContent, contentClassName)}>{children}</div>
</div>
</FocusScope>
</Portal>
);
}

@ -1,8 +1,6 @@
import React from 'react';
import { shallow } from 'enzyme';
import { render, screen, waitFor } from '@testing-library/react';
import AzureCredentialsForm, { Props } from './AzureCredentialsForm';
import { LegacyForms, Button } from '@grafana/ui';
const { Input, Select } = LegacyForms;
const setup = (propsFunc?: (props: Props) => Props) => {
let props: Props = {
@ -22,24 +20,25 @@ const setup = (propsFunc?: (props: Props) => Props) => {
{ value: 'chinaazuremonitor', label: 'Azure China' },
],
onCredentialsChange: jest.fn(),
getSubscriptions: jest.fn(),
getSubscriptions: jest.fn(() => Promise.resolve([])),
};
if (propsFunc) {
props = propsFunc(props);
}
return shallow(<AzureCredentialsForm {...props} />);
return render(<AzureCredentialsForm {...props} />);
};
describe('Render', () => {
it('should render component', () => {
const wrapper = setup();
expect(wrapper).toMatchSnapshot();
setup();
expect(screen.getByText('Azure Cloud')).toBeInTheDocument();
});
it('should disable azure monitor secret input', () => {
const wrapper = setup((props) => ({
it('should disable azure monitor secret input', async () => {
setup((props) => ({
...props,
credentials: {
authType: 'clientsecret',
@ -49,11 +48,12 @@ describe('Render', () => {
clientSecret: Symbol(),
},
}));
expect(wrapper).toMatchSnapshot();
await waitFor(() => screen.getByTestId('client-secret'));
expect(screen.getByTestId('client-secret')).toBeDisabled();
});
it('should enable azure monitor load subscriptions button', () => {
const wrapper = setup((props) => ({
it('should enable azure monitor load subscriptions button', async () => {
setup((props) => ({
...props,
credentials: {
authType: 'clientsecret',
@ -63,46 +63,34 @@ describe('Render', () => {
clientSecret: 'e7f3f661-a933-4b3f-8176-51c4f982ec48',
},
}));
expect(wrapper).toMatchSnapshot();
await waitFor(() => expect(screen.getByText('Load Subscriptions')).toBeInTheDocument());
});
describe('when disabled', () => {
it('should disable inputs', () => {
const wrapper = setup((props) => ({
it('should disable inputs', async () => {
setup((props) => ({
...props,
disabled: true,
}));
const inputs = wrapper.find(Input);
expect(inputs.length).toBeGreaterThan(1);
inputs.forEach((input) => {
expect(input.prop('disabled')).toBe(true);
});
});
it('should remove buttons', () => {
const wrapper = setup((props) => ({
...props,
disabled: true,
}));
expect(wrapper.find(Button).exists()).toBe(false);
await waitFor(() => screen.getByLabelText('Azure Cloud'));
expect(screen.getByLabelText('Azure Cloud')).toBeDisabled();
});
it('should disable cloud selector', () => {
const wrapper = setup((props) => ({
it('should remove buttons', async () => {
setup((props) => ({
...props,
disabled: true,
}));
const selects = wrapper.find(Select);
selects.forEach((s) => expect(s.prop('isDisabled')).toBe(true));
await waitFor(() => expect(screen.queryByText('Load Subscriptions')).not.toBeInTheDocument());
});
it('should render a children component', () => {
const wrapper = setup((props) => ({
it('should render children components', () => {
setup((props) => ({
...props,
children: <button>click me</button>,
}));
const button = wrapper.find('button');
expect(button.text()).toBe('click me');
expect(screen.getByText('click me')).toBeInTheDocument();
});
});
});

@ -1,9 +1,9 @@
import React, { ChangeEvent, FunctionComponent, useEffect, useReducer, useState } from 'react';
import { SelectableValue } from '@grafana/data';
import { InlineFormLabel, LegacyForms, Button } from '@grafana/ui';
import { InlineFormLabel, LegacyForms, Button, Select } from '@grafana/ui';
import { AzureAuthType, AzureCredentials } from '../types';
import { isCredentialsComplete } from '../credentials';
const { Select, Input } = LegacyForms;
const { Input } = LegacyForms;
export interface Props {
managedIdentityEnabled: boolean;
@ -162,7 +162,7 @@ export const AzureCredentialsForm: FunctionComponent<Props> = (props: Props) =>
value={authTypeOptions.find((opt) => opt.value === credentials.authType)}
options={authTypeOptions}
onChange={onAuthTypeChange}
isDisabled={disabled}
disabled={disabled}
/>
</div>
</div>
@ -176,12 +176,13 @@ export const AzureCredentialsForm: FunctionComponent<Props> = (props: Props) =>
Azure Cloud
</InlineFormLabel>
<Select
aria-label="Azure Cloud"
menuShouldPortal
className="width-15"
value={azureCloudOptions.find((opt) => opt.value === credentials.azureCloud)}
options={azureCloudOptions}
onChange={onAzureCloudChange}
isDisabled={disabled}
disabled={disabled}
/>
</div>
</div>
@ -219,7 +220,7 @@ export const AzureCredentialsForm: FunctionComponent<Props> = (props: Props) =>
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-12">Client Secret</InlineFormLabel>
<Input className="width-25" placeholder="configured" disabled={true} />
<Input data-testid="client-secret" className="width-25" placeholder="configured" disabled={true} />
</div>
<div className="gf-form">
<div className="max-width-30 gf-form-inline">
@ -254,6 +255,7 @@ export const AzureCredentialsForm: FunctionComponent<Props> = (props: Props) =>
<InlineFormLabel className="width-12">Default Subscription</InlineFormLabel>
<div className="width-30">
<Select
aria-label="Default Subscription"
menuShouldPortal
value={
credentials.defaultSubscriptionId
@ -262,7 +264,7 @@ export const AzureCredentialsForm: FunctionComponent<Props> = (props: Props) =>
}
options={subscriptions}
onChange={onSubscriptionChange}
isDisabled={disabled}
disabled={disabled}
/>
</div>
</div>

@ -1,626 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should disable azure monitor secret input 1`] = `
<div
className="gf-form-group"
>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
tooltip="Choose an Azure Cloud"
>
Azure Cloud
</FormLabel>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className="width-15"
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
menuShouldPortal={true}
onChange={[Function]}
openMenuOnFocus={false}
options={
Array [
Object {
"label": "Azure",
"value": "azuremonitor",
},
Object {
"label": "Azure US Government",
"value": "govazuremonitor",
},
Object {
"label": "Azure Germany",
"value": "germanyazuremonitor",
},
Object {
"label": "Azure China",
"value": "chinaazuremonitor",
},
]
}
tabSelectsValue={true}
value={
Object {
"label": "Azure",
"value": "azuremonitor",
}
}
/>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Directory (tenant) ID
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
onChange={[Function]}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value="e7f3f661-a933-3h3f-0294-31c4f962ec48"
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Application (client) ID
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
onChange={[Function]}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value="34509fad-c0r9-45df-9e25-f1ee34af6900"
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Client Secret
</FormLabel>
<Input
className="width-25"
disabled={true}
placeholder="configured"
/>
</div>
<div
className="gf-form"
>
<div
className="max-width-30 gf-form-inline"
>
<Button
onClick={[Function]}
type="button"
variant="secondary"
>
reset
</Button>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Default Subscription
</FormLabel>
<div
className="width-30"
>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className=""
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
menuShouldPortal={true}
onChange={[Function]}
openMenuOnFocus={false}
options={Array []}
tabSelectsValue={true}
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<div
className="max-width-30 gf-form-inline"
>
<Button
disabled={false}
onClick={[Function]}
size="sm"
type="button"
variant="secondary"
>
Load Subscriptions
</Button>
</div>
</div>
</div>
</div>
`;
exports[`Render should enable azure monitor load subscriptions button 1`] = `
<div
className="gf-form-group"
>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
tooltip="Choose an Azure Cloud"
>
Azure Cloud
</FormLabel>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className="width-15"
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
menuShouldPortal={true}
onChange={[Function]}
openMenuOnFocus={false}
options={
Array [
Object {
"label": "Azure",
"value": "azuremonitor",
},
Object {
"label": "Azure US Government",
"value": "govazuremonitor",
},
Object {
"label": "Azure Germany",
"value": "germanyazuremonitor",
},
Object {
"label": "Azure China",
"value": "chinaazuremonitor",
},
]
}
tabSelectsValue={true}
value={
Object {
"label": "Azure",
"value": "azuremonitor",
}
}
/>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Directory (tenant) ID
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
onChange={[Function]}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value="e7f3f661-a933-3h3f-0294-31c4f962ec48"
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Application (client) ID
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
onChange={[Function]}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value="34509fad-c0r9-45df-9e25-f1ee34af6900"
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Client Secret
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
onChange={[Function]}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value="e7f3f661-a933-4b3f-8176-51c4f982ec48"
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Default Subscription
</FormLabel>
<div
className="width-30"
>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className=""
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
menuShouldPortal={true}
onChange={[Function]}
openMenuOnFocus={false}
options={Array []}
tabSelectsValue={true}
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<div
className="max-width-30 gf-form-inline"
>
<Button
disabled={false}
onClick={[Function]}
size="sm"
type="button"
variant="secondary"
>
Load Subscriptions
</Button>
</div>
</div>
</div>
</div>
`;
exports[`Render should render component 1`] = `
<div
className="gf-form-group"
>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
tooltip="Choose an Azure Cloud"
>
Azure Cloud
</FormLabel>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className="width-15"
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
menuShouldPortal={true}
onChange={[Function]}
openMenuOnFocus={false}
options={
Array [
Object {
"label": "Azure",
"value": "azuremonitor",
},
Object {
"label": "Azure US Government",
"value": "govazuremonitor",
},
Object {
"label": "Azure Germany",
"value": "germanyazuremonitor",
},
Object {
"label": "Azure China",
"value": "chinaazuremonitor",
},
]
}
tabSelectsValue={true}
value={
Object {
"label": "Azure",
"value": "azuremonitor",
}
}
/>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Directory (tenant) ID
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
onChange={[Function]}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value="e7f3f661-a933-3h3f-0294-31c4f962ec48"
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Application (client) ID
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
onChange={[Function]}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value="34509fad-c0r9-45df-9e25-f1ee34af6900"
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Client Secret
</FormLabel>
<div
className="width-15"
>
<Input
className="width-30"
onChange={[Function]}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value=""
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<FormLabel
className="width-12"
>
Default Subscription
</FormLabel>
<div
className="width-30"
>
<Select
allowCustomValue={false}
autoFocus={false}
backspaceRemovesValue={true}
className=""
components={
Object {
"Group": [Function],
"IndicatorsContainer": [Function],
"MenuList": [Function],
"Option": [Function],
"SingleValue": [Function],
}
}
isClearable={false}
isDisabled={false}
isLoading={false}
isMulti={false}
isSearchable={true}
maxMenuHeight={300}
menuShouldPortal={true}
onChange={[Function]}
openMenuOnFocus={false}
options={Array []}
tabSelectsValue={true}
/>
</div>
</div>
</div>
<div
className="gf-form-inline"
>
<div
className="gf-form"
>
<div
className="max-width-30 gf-form-inline"
>
<Button
disabled={true}
onClick={[Function]}
size="sm"
type="button"
variant="secondary"
>
Load Subscriptions
</Button>
</div>
</div>
</div>
</div>
`;
Loading…
Cancel
Save