Skip to content

Commit

Permalink
[App Search] Credentials: implement working flyout form (#81541) (#81657
Browse files Browse the repository at this point in the history
)

* Add key name field

* Add key type field

* Add key read/write fields

* Add key engine access / selection

* Add key update warning callout

* Update flyout body with form components

* [PR feedback] i18n - change appSearch.tokens to appSearch.credentials

* [PR feedback] Remove unnecessary conditional

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
Constance and kibanamachine committed Oct 26, 2020
1 parent 9385b49 commit bab6af3
Show file tree
Hide file tree
Showing 15 changed files with 855 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,5 @@ export const TOKEN_TYPE_INFO = [
];

export const FLYOUT_ARIA_LABEL_ID = 'credentialsFlyoutTitle';

export const DOCS_HREF = 'https://www.elastic.co/guide/en/app-search/current/authentication.html';
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,98 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { setMockValues, setMockActions } from '../../../../__mocks__/kea.mock';

import React from 'react';
import { shallow } from 'enzyme';
import { EuiFlyoutBody } from '@elastic/eui';
import { EuiFlyoutBody, EuiForm } from '@elastic/eui';

import { ApiTokenTypes } from '../constants';
import { defaultApiToken } from '../credentials_logic';

import {
FormKeyName,
FormKeyType,
FormKeyReadWriteAccess,
FormKeyEngineAccess,
FormKeyUpdateWarning,
} from './form_components';
import { CredentialsFlyoutBody } from './body';

describe('CredentialsFlyoutBody', () => {
const values = {
activeApiToken: defaultApiToken,
activeApiTokenExists: false,
};
const actions = {
onApiTokenChange: jest.fn(),
};

beforeEach(() => {
jest.clearAllMocks();
setMockValues(values);
setMockActions(actions);
});

it('renders', () => {
const wrapper = shallow(<CredentialsFlyoutBody />);

expect(wrapper.find(EuiFlyoutBody)).toHaveLength(1);
expect(wrapper.find(EuiForm)).toHaveLength(1);
});

it('shows the expected form components on default private key creation', () => {
const wrapper = shallow(<CredentialsFlyoutBody />);

expect(wrapper.find(FormKeyName)).toHaveLength(1);
expect(wrapper.find(FormKeyType)).toHaveLength(1);
expect(wrapper.find(FormKeyReadWriteAccess)).toHaveLength(1);
expect(wrapper.find(FormKeyEngineAccess)).toHaveLength(1);
expect(wrapper.find(FormKeyUpdateWarning)).toHaveLength(0);
});

it('does not show read-write access options for search keys', () => {
setMockValues({
...values,
activeApiToken: {
...defaultApiToken,
type: ApiTokenTypes.Search,
},
});
const wrapper = shallow(<CredentialsFlyoutBody />);

expect(wrapper.find(FormKeyReadWriteAccess)).toHaveLength(0);
expect(wrapper.find(FormKeyEngineAccess)).toHaveLength(1);
});

it('does not show read-write or engine access options for admin keys', () => {
setMockValues({
...values,
activeApiToken: {
...defaultApiToken,
type: ApiTokenTypes.Admin,
},
});
const wrapper = shallow(<CredentialsFlyoutBody />);

expect(wrapper.find(FormKeyReadWriteAccess)).toHaveLength(0);
expect(wrapper.find(FormKeyEngineAccess)).toHaveLength(0);
});

it('shows a warning if updating an existing key', () => {
setMockValues({ ...values, activeApiTokenExists: true });
const wrapper = shallow(<CredentialsFlyoutBody />);

expect(wrapper.find(FormKeyUpdateWarning)).toHaveLength(1);
});

it('calls onApiTokenChange on form submit', () => {
const wrapper = shallow(<CredentialsFlyoutBody />);

const preventDefault = jest.fn();
wrapper.find(EuiForm).simulate('submit', { preventDefault });

expect(preventDefault).toHaveBeenCalled();
expect(actions.onApiTokenChange).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,41 @@
*/

import React from 'react';
import { EuiFlyoutBody } from '@elastic/eui';
import { useValues, useActions } from 'kea';
import { EuiFlyoutBody, EuiForm } from '@elastic/eui';

import { FlashMessages } from '../../../../shared/flash_messages';
import { CredentialsLogic } from '../credentials_logic';
import { ApiTokenTypes } from '../constants';

import {
FormKeyName,
FormKeyType,
FormKeyReadWriteAccess,
FormKeyEngineAccess,
FormKeyUpdateWarning,
} from './form_components';

export const CredentialsFlyoutBody: React.FC = () => {
const { onApiTokenChange } = useActions(CredentialsLogic);
const { activeApiToken, activeApiTokenExists } = useValues(CredentialsLogic);

return (
<EuiFlyoutBody>
<FlashMessages />
Details go here
<EuiForm
onSubmit={(e) => {
e.preventDefault();
onApiTokenChange();
}}
component="form"
>
<FormKeyName />
<FormKeyType />
{activeApiToken.type === ApiTokenTypes.Private && <FormKeyReadWriteAccess />}
{activeApiToken.type !== ApiTokenTypes.Admin && <FormKeyEngineAccess />}
</EuiForm>
{activeApiTokenExists && <FormKeyUpdateWarning />}
</EuiFlyoutBody>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export { FormKeyName } from './key_name';
export { FormKeyType } from './key_type';
export { FormKeyReadWriteAccess } from './key_read_write_access';
export { FormKeyEngineAccess } from './key_engine_access';
export { FormKeyUpdateWarning } from './key_update_warning';
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { setMockValues, setMockActions } from '../../../../../__mocks__/kea.mock';

import React from 'react';
import { shallow } from 'enzyme';
import { EuiRadio, EuiCheckbox } from '@elastic/eui';

import { FormKeyEngineAccess, EngineSelection } from './key_engine_access';

describe('FormKeyEngineAccess', () => {
const values = {
myRole: { canAccessAllEngines: true },
fullEngineAccessChecked: true,
};
const actions = {
setAccessAllEngines: jest.fn(),
};

beforeEach(() => {
jest.clearAllMocks();
setMockValues(values);
setMockActions(actions);
});

it('renders', () => {
const wrapper = shallow(<FormKeyEngineAccess />);

expect(wrapper.find(EuiRadio)).toHaveLength(2);
expect(wrapper.find(EngineSelection)).toHaveLength(0);
});

it('hides the full access radio option if the user does not have access to all engines', () => {
setMockValues({
...values,
myRole: { canAccessAllEngines: false },
});
const wrapper = shallow(<FormKeyEngineAccess />);

expect(wrapper.find('#all_engines').prop('hidden')).toEqual(true);
});

it('controls the checked values for access radios', () => {
setMockValues({
...values,
fullEngineAccessChecked: true,
});
const wrapper = shallow(<FormKeyEngineAccess />);

expect(wrapper.find('#all_engines').prop('checked')).toEqual(true);
expect(wrapper.find('#all_engines').prop('value')).toEqual('true');
expect(wrapper.find('#specific_engines').prop('checked')).toEqual(false);
expect(wrapper.find('#specific_engines').prop('value')).toEqual('false');

setMockValues({
...values,
fullEngineAccessChecked: false,
});
wrapper.setProps({}); // Re-render

expect(wrapper.find('#all_engines').prop('checked')).toEqual(false);
expect(wrapper.find('#all_engines').prop('value')).toEqual('false');
expect(wrapper.find('#specific_engines').prop('checked')).toEqual(true);
expect(wrapper.find('#specific_engines').prop('value')).toEqual('true');
});

it('calls setAccessAllEngines when the radios are changed', () => {
const wrapper = shallow(<FormKeyEngineAccess />);

wrapper.find('#all_engines').simulate('change');
expect(actions.setAccessAllEngines).toHaveBeenCalledWith(true);

wrapper.find('#specific_engines').simulate('change');
expect(actions.setAccessAllEngines).toHaveBeenCalledWith(false);
});

it('displays the engine selection panel if the limited access radio is selected', () => {
setMockValues({
...values,
fullEngineAccessChecked: false,
});
const wrapper = shallow(<FormKeyEngineAccess />);

expect(wrapper.find(EngineSelection)).toHaveLength(1);
});
});

describe('EngineSelection', () => {
const values = {
activeApiToken: { engines: [] },
engines: [{ name: 'engine1' }, { name: 'engine2' }, { name: 'engine3' }],
};
const actions = {
onEngineSelect: jest.fn(),
};

beforeEach(() => {
jest.clearAllMocks();
setMockValues(values);
setMockActions(actions);
});

it('renders', () => {
const wrapper = shallow(<EngineSelection />);

expect(wrapper.find('h4').text()).toEqual('Select Engines');
expect(wrapper.find(EuiCheckbox)).toHaveLength(3);
expect(wrapper.find(EuiCheckbox).first().prop('label')).toEqual('engine1');
});

it('controls the engines checked state', () => {
setMockValues({
...values,
activeApiToken: { engines: ['engine3'] },
});
const wrapper = shallow(<EngineSelection />);

expect(wrapper.find(EuiCheckbox).first().prop('checked')).toEqual(false);
expect(wrapper.find(EuiCheckbox).last().prop('checked')).toEqual(true);
});

it('calls onEngineSelect when the checkboxes are changed', () => {
const wrapper = shallow(<EngineSelection />);

wrapper.find(EuiCheckbox).first().simulate('change');
expect(actions.onEngineSelect).toHaveBeenCalledWith('engine1');

wrapper.find(EuiCheckbox).last().simulate('change');
expect(actions.onEngineSelect).toHaveBeenCalledWith('engine3');
});
});
Loading

0 comments on commit bab6af3

Please sign in to comment.