Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Enterprise Search] Added an EuiEmptyState to Credentials page #2

Closed
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
[Enterprise Search] Added a Credentials page to App Search (elastic#7…
  • Loading branch information
JasonStoltz authored Oct 8, 2020
commit 44b48a2cc0ad6a8e9d387d978448c69d7cba9c67
Original file line number Diff line number Diff line change
@@ -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():
*
@@ -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
@@ -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