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

[App Search] Move to tabbed single tabbed JSON flyout with upload and paste options and refactor cards #127162

Merged
merged 13 commits into from
Mar 8, 2022
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* 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 {
EuiFlyoutHeader,
EuiLink,
EuiTitle,
EuiFlyoutBody,
EuiFlyoutFooter,
EuiFlexGroup,
EuiFlexItem,
EuiButton,
EuiButtonEmpty,
EuiSpacer,
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';

import { CANCEL_BUTTON_LABEL } from '../../../../shared/constants';

import { FLYOUT_ARIA_LABEL_ID } from '../constants';
import { Errors } from '../creation_response_components';
import { DocumentCreationLogic } from '../index';

import './paste_json_text.scss';

export const ElasticsearchIndex: React.FC = () => (
<>
<FlyoutHeader />
<FlyoutBody />
<FlyoutFooter />
</>
);

export const FlyoutHeader: React.FC = () => {
return (
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2 id={FLYOUT_ARIA_LABEL_ID}>
{i18n.translate(
'xpack.enterpriseSearch.appSearch.documentCreation.elasticsearchIndex.title',
{
defaultMessage: 'Connect an Elasticsearch index',
}
)}
</h2>
</EuiTitle>
</EuiFlyoutHeader>
);
};

export const FlyoutBody: React.FC = () => {
return (
<EuiFlyoutBody banner={<Errors />}>
<EuiText color="subdued">
<p>
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.documentCreation.elasticsearchIndex.description"
defaultMessage="'You can now connect directly to an existing Elasticsearch index to make its data searchable and tunable through Enterprise Search Uls. {learnMoreLink}"
values={{
learnMoreLink: (
<EuiLink target="_blank" href={'TODO'}>
{i18n.translate(
'xpack.enterpriseSearch.appSearch.documentCreation.elasticsearchIndex.link',
{
defaultMessage: 'Learn more about using an existing index',
}
)}
</EuiLink>
),
}}
/>
</p>
</EuiText>
<EuiSpacer />
{'{Form fields go here}'}
</EuiFlyoutBody>
);
};

export const FlyoutFooter: React.FC = () => {
// TODO: replace these
const { textInput, isUploading } = useValues(DocumentCreationLogic);
// TODO: replace 'onSubmitJson'
const { onSubmitJson, closeDocumentCreation } = useActions(DocumentCreationLogic);

return (
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty onClick={closeDocumentCreation}>{CANCEL_BUTTON_LABEL}</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton fill onClick={onSubmitJson} isLoading={isUploading} isDisabled={!textInput}>
{i18n.translate(
'xpack.enterpriseSearch.appSearch.documentCreation.elasticsearchIndex.button',
{
defaultMessage: 'Connect to index',
}
)}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@

export { ShowCreationModes } from './show_creation_modes';
export { ApiCodeExample } from './api_code_example';
export { PasteJsonText } from './paste_json_text';
export { UploadJsonFile } from './upload_json_file';
export { JsonFlyout } from './json_flyout';
export { ElasticsearchIndex } from './elasticsearch_index';
export { PasteJsonTextTabContent, PasteJsonTextFooterContent } from './paste_json_text';
export { UploadJsonFileTabContent, UploadJsonFileFooterContent } from './upload_json_file';
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* 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.
*/

.enterpriseSearchTabbedFlyoutHeader.euiFlyoutHeader {
padding-bottom: 0;
}
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
* 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 { EuiFlyoutHeader, EuiFlyoutBody, EuiFlyoutFooter, EuiTabs, EuiTab } from '@elastic/eui';

import {
JsonFlyout,
PasteJsonTextTabContent,
UploadJsonFileTabContent,
PasteJsonTextFooterContent,
UploadJsonFileFooterContent,
} from './';

describe('JsonFlyout', () => {
const values = {
activeJsonTab: 'uploadTab',
};
const actions = {
closeDocumentCreation: jest.fn(),
setActiveJsonTab: jest.fn(),
};

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

it('renders a flyout components', () => {
const wrapper = shallow(<JsonFlyout />);

expect(wrapper.find(EuiFlyoutHeader)).toHaveLength(1);
expect(wrapper.find(EuiFlyoutBody)).toHaveLength(1);
expect(wrapper.find(EuiFlyoutFooter)).toHaveLength(1);
});

it('renders Upload json components and calls method with correct param', () => {
const wrapper = shallow(<JsonFlyout />);
const tabs = wrapper.find(EuiTabs).find(EuiTab);

expect(tabs).toHaveLength(2);

tabs.at(1).simulate('click');

expect(actions.setActiveJsonTab).toHaveBeenCalledWith('pasteTab');
expect(wrapper.find(UploadJsonFileTabContent)).toHaveLength(1);
expect(wrapper.find(UploadJsonFileFooterContent)).toHaveLength(1);
});

it('renders Paste json components and calls method with correct param', () => {
setMockValues({ ...values, activeJsonTab: 'pasteTab' });
const wrapper = shallow(<JsonFlyout />);
const tabs = wrapper.find(EuiTabs).find(EuiTab);

expect(tabs).toHaveLength(2);

tabs.at(0).simulate('click');

expect(actions.setActiveJsonTab).toHaveBeenCalledWith('uploadTab');
expect(wrapper.find(PasteJsonTextTabContent)).toHaveLength(1);
expect(wrapper.find(PasteJsonTextFooterContent)).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* 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 {
EuiFlyoutHeader,
EuiTitle,
EuiFlyoutBody,
EuiFlyoutFooter,
EuiSpacer,
EuiTabs,
EuiTab,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import { FLYOUT_ARIA_LABEL_ID } from '../constants';
import { Errors } from '../creation_response_components';
import { DocumentCreationLogic, ActiveJsonTab } from '../index';

import {
PasteJsonTextTabContent,
UploadJsonFileTabContent,
PasteJsonTextFooterContent,
UploadJsonFileFooterContent,
} from './';

import './json_flyout.scss';

export const JsonFlyout: React.FC = () => {
const { activeJsonTab } = useValues(DocumentCreationLogic);
const { setActiveJsonTab } = useActions(DocumentCreationLogic);

const tabs = [
{
id: 'uploadTab',
name: i18n.translate(
'xpack.enterpriseSearch.appSearch.documentCreation.jsonFlyout.uploadTabName',
{
defaultMessage: 'Upload',
}
),
content: <UploadJsonFileTabContent />,
},
{
id: 'pasteTab',
name: i18n.translate(
'xpack.enterpriseSearch.appSearch.documentCreation.jsonFlyout.pasteTabName',
{
defaultMessage: 'Paste',
}
),
content: <PasteJsonTextTabContent />,
},
];

return (
<>
<EuiFlyoutHeader hasBorder className="enterpriseSearchTabbedFlyoutHeader">
<EuiTitle size="m">
<h2 id={FLYOUT_ARIA_LABEL_ID}>
{i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.jsonFlyout.title', {
defaultMessage: 'Paste or upload JSON',
})}
</h2>
</EuiTitle>
<EuiSpacer />
<EuiTabs bottomBorder={false}>
{tabs.map((tab, index) => (
<EuiTab
key={index}
onClick={() => setActiveJsonTab(tab.id as ActiveJsonTab)}
isSelected={tab.id === activeJsonTab}
>
{tab.name}
</EuiTab>
))}
</EuiTabs>
</EuiFlyoutHeader>
<EuiFlyoutBody banner={<Errors />}>
{tabs.find((tab) => tab.id === activeJsonTab)?.content}
</EuiFlyoutBody>
<EuiFlyoutFooter>
{activeJsonTab === 'uploadTab' ? (
<UploadJsonFileFooterContent />
) : (
<PasteJsonTextFooterContent />
)}
</EuiFlyoutFooter>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ import { EuiTextArea, EuiButtonEmpty, EuiButton } from '@elastic/eui';

import { rerender } from '../../../../test_helpers';

import { Errors } from '../creation_response_components';

import { PasteJsonText, FlyoutHeader, FlyoutBody, FlyoutFooter } from './paste_json_text';
import { PasteJsonTextTabContent, PasteJsonTextFooterContent } from './paste_json_text';

describe('PasteJsonText', () => {
const values = {
Expand All @@ -42,60 +40,36 @@ describe('PasteJsonText', () => {
setMockActions(actions);
});

it('renders', () => {
const wrapper = shallow(<PasteJsonText />);
expect(wrapper.find(FlyoutHeader)).toHaveLength(1);
expect(wrapper.find(FlyoutBody)).toHaveLength(1);
expect(wrapper.find(FlyoutFooter)).toHaveLength(1);
});

describe('FlyoutHeader', () => {
it('renders', () => {
const wrapper = shallow(<FlyoutHeader />);
expect(wrapper.find('h2').text()).toEqual('Create documents');
});
});

describe('FlyoutBody', () => {
describe('PasteJsonTextTabContent', () => {
it('renders and updates the textarea value', () => {
setMockValues({ ...values, textInput: 'lorem ipsum' });
const wrapper = shallow(<FlyoutBody />);
const wrapper = shallow(<PasteJsonTextTabContent />);
const textarea = wrapper.find(EuiTextArea);

expect(textarea.prop('value')).toEqual('lorem ipsum');

textarea.simulate('change', { target: { value: 'dolor sit amet' } });
expect(actions.setTextInput).toHaveBeenCalledWith('dolor sit amet');
});

it('shows an error banner and sets invalid form props if errors exist', () => {
const wrapper = shallow(<FlyoutBody />);
expect(wrapper.find(EuiTextArea).prop('isInvalid')).toBe(false);

setMockValues({ ...values, errors: ['some error'] });
rerender(wrapper);
expect(wrapper.find(EuiTextArea).prop('isInvalid')).toBe(true);
expect(wrapper.prop('banner').type).toEqual(Errors);
});
});

describe('FlyoutFooter', () => {
describe('PasteJsonTextFooterContent', () => {
it('closes the modal', () => {
const wrapper = shallow(<FlyoutFooter />);
const wrapper = shallow(<PasteJsonTextFooterContent />);

wrapper.find(EuiButtonEmpty).simulate('click');
expect(actions.closeDocumentCreation).toHaveBeenCalled();
});

it('submits json', () => {
const wrapper = shallow(<FlyoutFooter />);
const wrapper = shallow(<PasteJsonTextFooterContent />);

wrapper.find(EuiButton).simulate('click');
expect(actions.onSubmitJson).toHaveBeenCalled();
});

it('disables/enables the Continue button based on whether text has been entered', () => {
const wrapper = shallow(<FlyoutFooter />);
const wrapper = shallow(<PasteJsonTextFooterContent />);
expect(wrapper.find(EuiButton).prop('isDisabled')).toBe(false);

setMockValues({ ...values, textInput: '' });
Expand All @@ -104,7 +78,7 @@ describe('PasteJsonText', () => {
});

it('sets isLoading based on isUploading', () => {
const wrapper = shallow(<FlyoutFooter />);
const wrapper = shallow(<PasteJsonTextFooterContent />);
expect(wrapper.find(EuiButton).prop('isLoading')).toBe(false);

setMockValues({ ...values, isUploading: true });
Expand Down
Loading