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

[Workplace Search] Add API Keys view to replace Access tokens #120147

Merged
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';

import { shallow } from 'enzyme';

import { EuiButtonIcon } from '@elastic/eui';

import { ApiKey } from './api_key';

describe('ApiKey', () => {
beforeEach(() => {
jest.clearAllMocks();
});

const props = {
copy: jest.fn(),
toggleIsHidden: jest.fn(),
isHidden: true,
text: 'some-api-key',
};

it('renders', () => {
const wrapper = shallow(<ApiKey {...props} />);
expect(wrapper.find(EuiButtonIcon).length).toEqual(2);
});

it('will call copy when the first button is clicked', () => {
const wrapper = shallow(<ApiKey {...props} />);
wrapper.find(EuiButtonIcon).first().simulate('click');
expect(props.copy).toHaveBeenCalled();
});

it('will call hide when the second button is clicked', () => {
const wrapper = shallow(<ApiKey {...props} />);
wrapper.find(EuiButtonIcon).last().simulate('click');
expect(props.toggleIsHidden).toHaveBeenCalled();
});

it('will render the "eye" icon when isHidden is true', () => {
const wrapper = shallow(<ApiKey {...props} />);
expect(wrapper.find(EuiButtonIcon).last().prop('iconType')).toBe('eye');
});

it('will render the "eyeClosed" icon when isHidden is false', () => {
const wrapper = shallow(<ApiKey {...{ ...props, isHidden: false }} />);
expect(wrapper.find(EuiButtonIcon).last().prop('iconType')).toBe('eyeClosed');
});

it('will render the provided text', () => {
const wrapper = shallow(<ApiKey {...props} />);
expect(wrapper.text()).toContain('some-api-key');
});
});
Original file line number Diff line number Diff line change
@@ -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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';

import { EuiButtonIcon } from '@elastic/eui';

import { SHOW_API_KEY_LABEL, HIDE_API_KEY_LABEL, COPY_API_KEY_BUTTON_LABEL } from '../constants';

interface Props {
copy: () => void;
toggleIsHidden: () => void;
isHidden: boolean;
text: React.ReactNode;
}

export const ApiKey: React.FC<Props> = ({ copy, toggleIsHidden, isHidden, text }) => {
const hideIcon = isHidden ? 'eye' : 'eyeClosed';
const hideIconLabel = isHidden ? SHOW_API_KEY_LABEL : HIDE_API_KEY_LABEL;

return (
<>
<EuiButtonIcon
onClick={copy}
iconType="copyClipboard"
aria-label={COPY_API_KEY_BUTTON_LABEL}
/>
<EuiButtonIcon
onClick={toggleIsHidden}
iconType={hideIcon}
aria-label={hideIconLabel}
aria-pressed={!isHidden}
style={{ marginRight: '0.25em' }}
/>
{text}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

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

import React from 'react';

import { shallow } from 'enzyme';

import { EuiFlyout, EuiForm, EuiFieldText, EuiFormRow } from '@elastic/eui';

import { ApiKeyFlyout } from './api_key_flyout';

describe('ApiKeyFlyout', () => {
const setNameInputBlurred = jest.fn();
const setApiKeyName = jest.fn();
const onApiFormSubmit = jest.fn();
const hideApiKeyForm = jest.fn();

const apiKey = {
id: '123',
name: 'test',
};

const values = {
activeApiToken: apiKey,
};

beforeEach(() => {
setMockValues(values);
setMockActions({
setNameInputBlurred,
setApiKeyName,
onApiFormSubmit,
hideApiKeyForm,
});
});

it('renders', () => {
const wrapper = shallow(<ApiKeyFlyout />);
const flyout = wrapper.find(EuiFlyout);

expect(flyout).toHaveLength(1);
expect(flyout.prop('onClose')).toEqual(hideApiKeyForm);
});

it('calls onApiTokenChange on form submit', () => {
const wrapper = shallow(<ApiKeyFlyout />);
const preventDefault = jest.fn();
wrapper.find(EuiForm).simulate('submit', { preventDefault });

expect(preventDefault).toHaveBeenCalled();
expect(onApiFormSubmit).toHaveBeenCalled();
});

it('shows help text if the raw name does not match the expected name', () => {
setMockValues({
...values,
activeApiToken: { name: 'my-api-key' },
activeApiTokenRawName: 'my api key!!',
});
const wrapper = shallow(<ApiKeyFlyout />);

expect(wrapper.find(EuiFormRow).prop('helpText')).toEqual('Your key will be named: my-api-key');
});

it('controls the input value', () => {
setMockValues({
...values,
activeApiTokenRawName: 'test',
});
const wrapper = shallow(<ApiKeyFlyout />);

expect(wrapper.find(EuiFieldText).prop('value')).toEqual('test');
});

it('calls setApiKeyName when the input value is changed', () => {
const wrapper = shallow(<ApiKeyFlyout />);
wrapper.find(EuiFieldText).simulate('change', { target: { value: 'changed' } });

expect(setApiKeyName).toHaveBeenCalledWith('changed');
});

it('calls setNameInputBlurred when the user stops focusing the input', () => {
const wrapper = shallow(<ApiKeyFlyout />);
wrapper.find(EuiFieldText).simulate('blur');

expect(setNameInputBlurred).toHaveBeenCalledWith(true);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';

import { useValues, useActions } from 'kea';

import {
EuiPortal,
EuiFormRow,
EuiFieldText,
EuiFlyout,
EuiFlyoutHeader,
EuiFlyoutBody,
EuiFlyoutFooter,
EuiFlexGroup,
EuiFlexItem,
EuiButtonEmpty,
EuiButton,
EuiForm,
EuiTitle,
} from '@elastic/eui';

import { CLOSE_BUTTON_LABEL, SAVE_BUTTON_LABEL } from '../../../../shared/constants';
import { FlashMessages } from '../../../../shared/flash_messages';

import { ApiKeysLogic } from '../api_keys_logic';
import {
API_KEY_FLYOUT_TITLE,
API_KEY_FORM_LABEL,
API_KEY_FORM_HELP_TEXT,
API_KEY_NAME_PLACEHOLDER,
} from '../constants';

export const ApiKeyFlyout: React.FC = () => {
const { setNameInputBlurred, setApiKeyName, onApiFormSubmit, hideApiKeyForm } =
useActions(ApiKeysLogic);
const {
activeApiToken: { name },
activeApiTokenRawName: rawName,
} = useValues(ApiKeysLogic);

return (
<EuiPortal>
<EuiFlyout onClose={hideApiKeyForm} hideCloseButton ownFocus size="s">
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2>{API_KEY_FLYOUT_TITLE}</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<FlashMessages />
<EuiForm
onSubmit={(e) => {
e.preventDefault();
onApiFormSubmit();
}}
component="form"
>
<EuiFormRow
label={API_KEY_FORM_LABEL}
helpText={!!name && name !== rawName ? API_KEY_FORM_HELP_TEXT(name) : ''}
fullWidth
>
<EuiFieldText
name="raw_name"
id="raw_name"
placeholder={API_KEY_NAME_PLACEHOLDER}
data-test-subj="APIKeyField"
value={rawName}
onChange={(e) => setApiKeyName(e.target.value)}
onBlur={() => setNameInputBlurred(true)}
autoComplete="off"
maxLength={64}
required
fullWidth
autoFocus
/>
</EuiFormRow>
</EuiForm>
</EuiFlyoutBody>
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty iconType="cross" onClick={hideApiKeyForm}>
{CLOSE_BUTTON_LABEL}
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton onClick={onApiFormSubmit} fill data-test-subj="APIKeyActionButton">
{SAVE_BUTTON_LABEL}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
</EuiFlyout>
</EuiPortal>
);
};
Loading