diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx index 7b7d89164662e1..248858867c8c6d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.test.tsx @@ -10,9 +10,11 @@ import React from 'react'; import { shallow } from 'enzyme'; import { CredentialsList } from './credentials_list'; +import { Key } from './key'; import { EuiBasicTable, EuiCopy } from '@elastic/eui'; import { IApiToken } from '../types'; import { ApiTokenTypes } from '../constants'; +import { HiddenText } from '../../../../shared/hidden_text'; describe('Credentials', () => { const apiToken: IApiToken = { @@ -149,21 +151,11 @@ describe('Credentials', () => { }); describe('column 3 (key)', () => { - const testToken = { + const token = { ...apiToken, key: 'abc-123', }; - it('renders the credential and a button to copy it', () => { - const copyMock = jest.fn(); - const column = columns[2]; - const wrapper = shallow(
{column.render(testToken)}
); - const children = wrapper.find(EuiCopy).props().children; - const copyEl = shallow(
{children(copyMock)}
); - expect(copyEl.find('EuiButtonIcon').props().onClick).toEqual(copyMock); - expect(copyEl.text()).toContain('abc-123'); - }); - it('renders nothing if no key is present', () => { const tokenWithNoKey = { key: undefined, @@ -172,6 +164,35 @@ describe('Credentials', () => { const wrapper = shallow(
{column.render(tokenWithNoKey)}
); expect(wrapper.text()).toBe(''); }); + + it('renders an EuiCopy component with the key', () => { + const column = columns[2]; + const wrapper = shallow(
{column.render(token)}
); + expect(wrapper.find(EuiCopy).props().textToCopy).toEqual('abc-123'); + }); + + it('renders a HiddenText component with the key', () => { + const column = columns[2]; + const wrapper = shallow(
{column.render(token)}
) + .find(EuiCopy) + .dive(); + expect(wrapper.find(HiddenText).props().text).toEqual('abc-123'); + }); + + it('renders a Key component', () => { + const column = columns[2]; + const wrapper = shallow(
{column.render(token)}
) + .find(EuiCopy) + .dive() + .find(HiddenText) + .dive(); + expect(wrapper.find(Key).props()).toEqual({ + copy: expect.any(Function), + toggleIsHidden: expect.any(Function), + isHidden: expect.any(Boolean), + text: '*******', + }); + }); }); describe('column 4 (modes)', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx index 065601feeb4d26..568c46b5dfd543 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx @@ -12,6 +12,8 @@ import { useActions, useValues } from 'kea'; import { i18n } from '@kbn/i18n'; import { CredentialsLogic } from '../credentials_logic'; +import { Key } from './key'; +import { HiddenText } from '../../../../shared/hidden_text'; import { IApiToken } from '../types'; import { TOKEN_TYPE_DISPLAY_NAMES } from '../constants'; import { apiTokenSort } from '../utils/api_token_sort'; @@ -48,19 +50,11 @@ export const CredentialsList: React.FC = () => { })} > {(copy) => ( - <> - - {token.key} - + + {({ hiddenText, isHidden, toggle }) => ( + + )} + )} ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.test.tsx new file mode 100644 index 00000000000000..890444c6f3de6c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.test.tsx @@ -0,0 +1,56 @@ +/* + * 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 React from 'react'; +import { shallow } from 'enzyme'; +import { EuiButtonIcon } from '@elastic/eui'; + +import { Key } from './key'; + +describe('Key', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const props = { + copy: jest.fn(), + toggleIsHidden: jest.fn(), + isHidden: true, + text: '*****', + }; + + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.find(EuiButtonIcon).length).toEqual(2); + }); + + it('will call copy when the first button is clicked', () => { + const wrapper = shallow(); + (wrapper.find(EuiButtonIcon).at(0).props() as any).onClick(); + expect(props.copy).toHaveBeenCalled(); + }); + + it('will call hide when the second button is clicked', () => { + const wrapper = shallow(); + (wrapper.find(EuiButtonIcon).at(1).props() as any).onClick(); + expect(props.toggleIsHidden).toHaveBeenCalled(); + }); + + it('will render the "eye" icon when isHidden is true', () => { + const wrapper = shallow(); + expect((wrapper.find(EuiButtonIcon).at(1).props() as any).iconType).toBe('eye'); + }); + + it('will render the "eyeClosed" icon when isHidden is false', () => { + const wrapper = shallow(); + expect((wrapper.find(EuiButtonIcon).at(1).props() as any).iconType).toBe('eyeClosed'); + }); + + it('will render the provided text', () => { + const wrapper = shallow(); + expect(wrapper.text()).toContain('*****'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx new file mode 100644 index 00000000000000..dcc2a85b519a5a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/key.tsx @@ -0,0 +1,42 @@ +/* + * 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 React from 'react'; +import { EuiButtonIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +interface Props { + copy: () => void; + toggleIsHidden: () => void; + isHidden: boolean; + text: string; +} + +export const Key: React.FC = ({ copy, toggleIsHidden, isHidden, text }) => { + const icon = isHidden ? 'eye' : 'eyeClosed'; + return ( + <> + + + {text} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/hidden_text.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/hidden_text.test.tsx new file mode 100644 index 00000000000000..087e2576aa8a16 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/hidden_text.test.tsx @@ -0,0 +1,61 @@ +/* + * 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 React from 'react'; +import { shallow } from 'enzyme'; + +import { HiddenText } from '.'; + +describe('HiddenText', () => { + it('provides the passed "text" in a "hiddenText" field, with all characters replaced with stars by default', () => { + const wrapper = shallow( + + {({ hiddenText, isHidden, toggle }) =>
{hiddenText}
} +
+ ); + expect(wrapper.text()).toEqual('***********'); + }); + + it('provides a "toggle" function, which when called, changes "hiddenText" from stars back to the original text', () => { + let toggleFn = () => {}; + + const wrapper = shallow( + + {({ hiddenText, isHidden, toggle }) => { + toggleFn = toggle; + return
{hiddenText}
; + }} +
+ ); + + expect(wrapper.text()).toEqual('***********'); + toggleFn(); + expect(wrapper.text()).toEqual('hidden_test'); + toggleFn(); + expect(wrapper.text()).toEqual('***********'); + }); + + it('provides a "hidden" boolean, which which tracks whether or not the text is starred or not', () => { + let toggleFn = () => {}; + let isHiddenBool = false; + + shallow( + + {({ hiddenText, isHidden, toggle }) => { + isHiddenBool = isHidden; + toggleFn = toggle; + return
{hiddenText}
; + }} +
+ ); + + expect(isHiddenBool).toEqual(true); + toggleFn(); + expect(isHiddenBool).toEqual(false); + toggleFn(); + expect(isHiddenBool).toEqual(true); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/hidden_text.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/hidden_text.tsx new file mode 100644 index 00000000000000..6ac30788e8e787 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/hidden_text.tsx @@ -0,0 +1,29 @@ +/* + * 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 React, { useState, ReactElement } from 'react'; + +interface ChildrenProps { + toggle: () => void; + isHidden: boolean; + hiddenText: string; +} + +interface Props { + text: string; + children(props: ChildrenProps): ReactElement; +} + +export const HiddenText: React.FC = ({ text, children }) => { + const [isHidden, toggleIsHidden] = useState(true); + const hiddenText = isHidden ? text.replace(/./g, '*') : text; + + return children({ + hiddenText, + isHidden, + toggle: () => toggleIsHidden(!isHidden), + }); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/index.ts new file mode 100644 index 00000000000000..56ac36905078fa --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/hidden_text/index.ts @@ -0,0 +1,7 @@ +/* + * 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 { HiddenText } from './hidden_text';