Skip to content

Commit

Permalink
[Enterprise Search] Added a Credentials page to App Search (#79749)
Browse files Browse the repository at this point in the history
  • Loading branch information
JasonStoltz authored Oct 8, 2020
1 parent 8bea172 commit 44b48a2
Show file tree
Hide file tree
Showing 20 changed files with 952 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ jest.mock('react', () => ({
useEffect: jest.fn((fn) => fn()), // Calls on mount/every update - use mount for more complex behavior
}));

// Helper for calling the returned useEffect unmount handler
import { useEffect } from 'react';
export const unmountHandler = () => (useEffect as jest.Mock).mock.calls[0][0]()();

/**
* Example usage within a component test using shallow():
*
Expand All @@ -19,3 +23,14 @@ jest.mock('react', () => ({
*
* // ... etc.
*/
/**
* Example unmount() usage:
*
* import { unmountHandler } from '../../../__mocks__/shallow_useeffect.mock';
*
* it('unmounts', () => {
* shallow(SomeComponent);
* unmountHandler();
* // expect something to have been done on unmount (NOTE: the component is not actually unmounted)
* });
*/
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,79 @@
*/
import { i18n } from '@kbn/i18n';

export const ADMIN = 'admin';
export const PRIVATE = 'private';
export const SEARCH = 'search';
export enum ApiTokenTypes {
Admin = 'admin',
Private = 'private',
Search = 'search',
}

export const TOKEN_TYPE_DESCRIPTION = {
[SEARCH]: i18n.translate('xpack.enterpriseSearch.appSearch.tokens.search.description', {
defaultMessage: 'Public Search Keys are used for search endpoints only.',
}),
[PRIVATE]: i18n.translate('xpack.enterpriseSearch.appSearch.tokens.private.description', {
defaultMessage:
'Private API Keys are used for read and/or write access on one or more Engines.',
}),
[ADMIN]: i18n.translate('xpack.enterpriseSearch.appSearch.tokens.admin.description', {
defaultMessage: 'Private Admin Keys are used to interact with the Credentials API.',
}),
export const SEARCH_DISPLAY = i18n.translate(
'xpack.enterpriseSearch.appSearch.tokens.permissions.display.search',
{
defaultMessage: 'search',
}
);
export const ALL = i18n.translate(
'xpack.enterpriseSearch.appSearch.tokens.permissions.display.all',
{
defaultMessage: 'all',
}
);
export const READ_WRITE = i18n.translate(
'xpack.enterpriseSearch.appSearch.tokens.permissions.display.readwrite',
{
defaultMessage: 'read/write',
}
);
export const READ_ONLY = i18n.translate(
'xpack.enterpriseSearch.appSearch.tokens.permissions.display.readonly',
{
defaultMessage: 'read-only',
}
);
export const WRITE_ONLY = i18n.translate(
'xpack.enterpriseSearch.appSearch.tokens.permissions.display.writeonly',
{
defaultMessage: 'write-only',
}
);

export const TOKEN_TYPE_DESCRIPTION: { [key: string]: string } = {
[ApiTokenTypes.Search]: i18n.translate(
'xpack.enterpriseSearch.appSearch.tokens.search.description',
{
defaultMessage: 'Public Search Keys are used for search endpoints only.',
}
),
[ApiTokenTypes.Private]: i18n.translate(
'xpack.enterpriseSearch.appSearch.tokens.private.description',
{
defaultMessage:
'Private API Keys are used for read and/or write access on one or more Engines.',
}
),
[ApiTokenTypes.Admin]: i18n.translate(
'xpack.enterpriseSearch.appSearch.tokens.admin.description',
{
defaultMessage: 'Private Admin Keys are used to interact with the Credentials API.',
}
),
};

export const TOKEN_TYPE_DISPLAY_NAMES = {
[SEARCH]: i18n.translate('xpack.enterpriseSearch.appSearch.tokens.search.name', {
export const TOKEN_TYPE_DISPLAY_NAMES: { [key: string]: string } = {
[ApiTokenTypes.Search]: i18n.translate('xpack.enterpriseSearch.appSearch.tokens.search.name', {
defaultMessage: 'Public Search Key',
}),
[PRIVATE]: i18n.translate('xpack.enterpriseSearch.appSearch.tokens.private.name', {
[ApiTokenTypes.Private]: i18n.translate('xpack.enterpriseSearch.appSearch.tokens.private.name', {
defaultMessage: 'Private API Key',
}),
[ADMIN]: i18n.translate('xpack.enterpriseSearch.appSearch.tokens.admin.name', {
[ApiTokenTypes.Admin]: i18n.translate('xpack.enterpriseSearch.appSearch.tokens.admin.name', {
defaultMessage: 'Private Admin Key',
}),
};

export const TOKEN_TYPE_INFO = [
{ value: SEARCH, text: TOKEN_TYPE_DISPLAY_NAMES[SEARCH] },
{ value: PRIVATE, text: TOKEN_TYPE_DISPLAY_NAMES[PRIVATE] },
{ value: ADMIN, text: TOKEN_TYPE_DISPLAY_NAMES[ADMIN] },
{ value: ApiTokenTypes.Search, text: TOKEN_TYPE_DISPLAY_NAMES[ApiTokenTypes.Search] },
{ value: ApiTokenTypes.Private, text: TOKEN_TYPE_DISPLAY_NAMES[ApiTokenTypes.Private] },
{ value: ApiTokenTypes.Admin, text: TOKEN_TYPE_DISPLAY_NAMES[ApiTokenTypes.Admin] },
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* 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 { unmountHandler } from '../../../__mocks__/shallow_useeffect.mock';

import React from 'react';
import { shallow } from 'enzyme';

import { Credentials } from './credentials';
import { EuiCopy, EuiPageContentBody } from '@elastic/eui';

import { externalUrl } from '../../../shared/enterprise_search_url';

describe('Credentials', () => {
// Kea mocks
const values = {
dataLoading: false,
};
const actions = {
initializeCredentialsData: jest.fn(),
resetCredentials: jest.fn(),
showCredentialsForm: jest.fn(),
};

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

it('renders', () => {
const wrapper = shallow(<Credentials />);
expect(wrapper.find(EuiPageContentBody)).toHaveLength(1);
});

it('initializes data on mount', () => {
shallow(<Credentials />);
expect(actions.initializeCredentialsData).toHaveBeenCalledTimes(1);
});

it('calls resetCredentials on unmount', () => {
shallow(<Credentials />);
unmountHandler();
expect(actions.resetCredentials).toHaveBeenCalledTimes(1);
});

it('renders nothing if data is still loading', () => {
setMockValues({ dataLoading: true });
const wrapper = shallow(<Credentials />);
expect(wrapper.find(EuiPageContentBody)).toHaveLength(0);
});

it('renders the API endpoint and a button to copy it', () => {
externalUrl.enterpriseSearchUrl = 'http://localhost:3002';
const copyMock = jest.fn();
const wrapper = shallow(<Credentials />);
// We wrap children in a div so that `shallow` can render it.
const copyEl = shallow(<div>{wrapper.find(EuiCopy).props().children(copyMock)}</div>);
expect(copyEl.find('EuiButtonIcon').props().onClick).toEqual(copyMock);
expect(copyEl.text().replace('<EuiButtonIcon />', '')).toEqual('http://localhost:3002');
});

it('will show the Crendentials Flyout when the Create API Key button is pressed', () => {
const wrapper = shallow(<Credentials />);
const button: any = wrapper.find('[data-test-subj="CreateAPIKeyButton"]');
button.props().onClick();
expect(actions.showCredentialsForm).toHaveBeenCalledTimes(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* 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, { useEffect } from 'react';
import { useActions, useValues } from 'kea';

import {
EuiPageHeader,
EuiPageHeaderSection,
EuiTitle,
EuiPageContentBody,
EuiPanel,
EuiCopy,
EuiButtonIcon,
EuiSpacer,
EuiButton,
EuiPageContentHeader,
EuiPageContentHeaderSection,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
import { CredentialsLogic } from './credentials_logic';
import { externalUrl } from '../../../shared/enterprise_search_url/external_url';
import { CredentialsList } from './credentials_list';

export const Credentials: React.FC = () => {
const { initializeCredentialsData, resetCredentials, showCredentialsForm } = useActions(
CredentialsLogic
);

const { dataLoading } = useValues(CredentialsLogic);

useEffect(() => {
initializeCredentialsData();
return () => {
resetCredentials();
};
}, []);

// TODO
// if (dataLoading) { return <Loading /> }
if (dataLoading) {
return null;
}
return (
<>
<SetPageChrome
trail={[
i18n.translate('xpack.enterpriseSearch.appSearch.credentials.title', {
defaultMessage: 'Credentials',
}),
]}
/>
<EuiPageHeader>
<EuiPageHeaderSection>
<EuiTitle size="l">
<h1>
{i18n.translate('xpack.enterpriseSearch.appSearch.credentials.title', {
defaultMessage: 'Credentials',
})}
</h1>
</EuiTitle>
</EuiPageHeaderSection>
</EuiPageHeader>
<EuiPageContentBody>
<EuiPanel className="eui-textCenter">
<EuiTitle size="s">
<h2>
{i18n.translate('xpack.enterpriseSearch.appSearch.credentials.apiEndpoint', {
defaultMessage: 'Endpoint',
})}
</h2>
</EuiTitle>
<EuiCopy
textToCopy={externalUrl.enterpriseSearchUrl}
afterMessage={i18n.translate('xpack.enterpriseSearch.appSearch.credentials.copied', {
defaultMessage: 'Copied',
})}
>
{(copy) => (
<>
<EuiButtonIcon
onClick={copy}
iconType="copyClipboard"
aria-label={i18n.translate(
'xpack.enterpriseSearch.appSearch.credentials.copyApiEndpoint',
{
defaultMessage: 'Copy API Endpoint to clipboard.',
}
)}
/>
{externalUrl.enterpriseSearchUrl}
</>
)}
</EuiCopy>
</EuiPanel>
<EuiSpacer size="xxl" />
<EuiPageContentHeader responsive={false}>
<EuiPageContentHeaderSection>
<EuiTitle size="m">
<h2>
{i18n.translate('xpack.enterpriseSearch.appSearch.credentials.apiKeys', {
defaultMessage: 'API Keys',
})}
</h2>
</EuiTitle>
</EuiPageContentHeaderSection>
<EuiPageContentHeaderSection>
<EuiButton
color="primary"
data-test-subj="CreateAPIKeyButton"
fill={true}
onClick={() => showCredentialsForm()}
>
{i18n.translate('xpack.enterpriseSearch.appSearch.credentials.createKey', {
defaultMessage: 'Create a key',
})}
</EuiButton>
</EuiPageContentHeaderSection>
</EuiPageContentHeader>
<EuiSpacer size="s" />
<EuiPanel>
<CredentialsList />
</EuiPanel>
</EuiPageContentBody>
</>
);
};
Loading

0 comments on commit 44b48a2

Please sign in to comment.