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';