Skip to content

Commit

Permalink
[App Search] New modal to crawl select domains (#124195)
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron Hulcher authored Feb 8, 2022
1 parent 4394293 commit 97230e9
Show file tree
Hide file tree
Showing 19 changed files with 840 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.crawlSelectDomainsModal {
width: 50rem;
max-width: 90%;
}
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 { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic';

import React from 'react';

import { shallow, ShallowWrapper } from 'enzyme';

import { EuiModal, EuiModalFooter, EuiButton, EuiButtonEmpty } from '@elastic/eui';

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

import { CrawlSelectDomainsModal } from './crawl_select_domains_modal';
import { SimplifiedSelectable } from './simplified_selectable';

const MOCK_VALUES = {
// CrawlerLogic
domains: [{ url: 'https://www.elastic.co' }, { url: 'https://www.swiftype.com' }],
// CrawlSelectDomainsModalLogic
selectedDomainUrls: ['https://www.elastic.co'],
isModalVisible: true,
};

const MOCK_ACTIONS = {
// CrawlSelectDomainsModalLogic
hideModal: jest.fn(),
onSelectDomainUrls: jest.fn(),
// CrawlerLogic
startCrawl: jest.fn(),
};

describe('CrawlSelectDomainsModal', () => {
let wrapper: ShallowWrapper;

beforeEach(() => {
jest.clearAllMocks();
setMockValues(MOCK_VALUES);
setMockActions(MOCK_ACTIONS);

wrapper = shallow(<CrawlSelectDomainsModal />);
});

it('is empty when the modal is hidden', () => {
setMockValues({
...MOCK_VALUES,
isModalVisible: false,
});

rerender(wrapper);

expect(wrapper.isEmptyRender()).toBe(true);
});

it('renders as a modal when visible', () => {
expect(wrapper.is(EuiModal)).toBe(true);
});

it('can be closed', () => {
expect(wrapper.prop('onClose')).toEqual(MOCK_ACTIONS.hideModal);
expect(wrapper.find(EuiModalFooter).find(EuiButtonEmpty).prop('onClick')).toEqual(
MOCK_ACTIONS.hideModal
);
});

it('allows the user to select domains', () => {
expect(wrapper.find(SimplifiedSelectable).props()).toEqual({
options: ['https://www.elastic.co', 'https://www.swiftype.com'],
selectedOptions: ['https://www.elastic.co'],
onChange: MOCK_ACTIONS.onSelectDomainUrls,
});
});

describe('submit button', () => {
it('is disabled when no domains are selected', () => {
setMockValues({
...MOCK_VALUES,
selectedDomainUrls: [],
});

rerender(wrapper);

expect(wrapper.find(EuiModalFooter).find(EuiButton).prop('disabled')).toEqual(true);
});

it('starts a crawl and hides the modal', () => {
wrapper.find(EuiModalFooter).find(EuiButton).simulate('click');

expect(MOCK_ACTIONS.startCrawl).toHaveBeenCalledWith({
domain_allowlist: MOCK_VALUES.selectedDomainUrls,
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* 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 {
EuiButton,
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiModal,
EuiModalBody,
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
EuiNotificationBadge,
} from '@elastic/eui';

import { i18n } from '@kbn/i18n';

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

import { CrawlerLogic } from '../../crawler_logic';

import { CrawlSelectDomainsModalLogic } from './crawl_select_domains_modal_logic';
import { SimplifiedSelectable } from './simplified_selectable';

import './crawl_select_domains_modal.scss';

export const CrawlSelectDomainsModal: React.FC = () => {
const { domains } = useValues(CrawlerLogic);
const domainUrls = domains.map((domain) => domain.url);

const crawlSelectDomainsModalLogic = CrawlSelectDomainsModalLogic({ domains });
const { isDataLoading, isModalVisible, selectedDomainUrls } = useValues(
crawlSelectDomainsModalLogic
);
const { hideModal, onSelectDomainUrls } = useActions(crawlSelectDomainsModalLogic);

const { startCrawl } = useActions(CrawlerLogic);

if (!isModalVisible) {
return null;
}

return (
<EuiModal onClose={hideModal} className="crawlSelectDomainsModal">
<EuiModalHeader>
<EuiFlexGroup alignItems="center" gutterSize="m">
<EuiFlexItem>
<EuiModalHeaderTitle>
{i18n.translate(
'xpack.enterpriseSearch.appSearch.crawler.crawlSelectDomainsModal.modalHeaderTitle',
{
defaultMessage: 'Crawl select domains',
}
)}
</EuiModalHeaderTitle>
</EuiFlexItem>
<EuiNotificationBadge
size="m"
color={selectedDomainUrls.length > 0 ? 'accent' : 'subdued'}
>
{selectedDomainUrls.length}
</EuiNotificationBadge>
<EuiFlexItem grow={false}>
{i18n.translate(
'xpack.enterpriseSearch.appSearch.crawler.crawlSelectDomainsModal.selectedDescriptor',
{
defaultMessage: 'selected',
}
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiModalHeader>
<EuiModalBody>
<SimplifiedSelectable
options={domainUrls}
selectedOptions={selectedDomainUrls}
onChange={onSelectDomainUrls}
/>
</EuiModalBody>
<EuiModalFooter>
<EuiButtonEmpty onClick={hideModal}>{CANCEL_BUTTON_LABEL}</EuiButtonEmpty>
<EuiButton
fill
onClick={() => {
startCrawl({ domain_allowlist: selectedDomainUrls });
}}
disabled={selectedDomainUrls.length === 0}
isLoading={isDataLoading}
>
{i18n.translate(
'xpack.enterpriseSearch.appSearch.crawler.crawlSelectDomainsModal.startCrawlButtonLabel',
{
defaultMessage: 'Apply and crawl now',
}
)}
</EuiButton>
</EuiModalFooter>
</EuiModal>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* 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 { LogicMounter } from '../../../../../__mocks__/kea_logic';

import { CrawlerLogic } from '../../crawler_logic';

import { CrawlSelectDomainsModalLogic } from './crawl_select_domains_modal_logic';

describe('CrawlSelectDomainsModalLogic', () => {
const { mount } = new LogicMounter(CrawlSelectDomainsModalLogic);

beforeEach(() => {
jest.clearAllMocks();
mount();
});

it('has expected default values', () => {
expect(CrawlSelectDomainsModalLogic.values).toEqual({
isDataLoading: false,
isModalVisible: false,
selectedDomainUrls: [],
});
});

describe('actions', () => {
describe('hideModal', () => {
it('hides the modal', () => {
CrawlSelectDomainsModalLogic.actions.hideModal();

expect(CrawlSelectDomainsModalLogic.values.isModalVisible).toBe(false);
});
});

describe('showModal', () => {
it('shows the modal', () => {
CrawlSelectDomainsModalLogic.actions.showModal();

expect(CrawlSelectDomainsModalLogic.values.isModalVisible).toBe(true);
});

it('resets the selected options', () => {
mount({
selectedDomainUrls: ['https://www.elastic.co', 'https://www.swiftype.com'],
});

CrawlSelectDomainsModalLogic.actions.showModal();

expect(CrawlSelectDomainsModalLogic.values.selectedDomainUrls).toEqual([]);
});
});

describe('onSelectDomainUrls', () => {
it('saves the urls', () => {
mount({
selectedDomainUrls: [],
});

CrawlSelectDomainsModalLogic.actions.onSelectDomainUrls([
'https://www.elastic.co',
'https://www.swiftype.com',
]);

expect(CrawlSelectDomainsModalLogic.values.selectedDomainUrls).toEqual([
'https://www.elastic.co',
'https://www.swiftype.com',
]);
});
});

describe('[CrawlerLogic.actionTypes.startCrawl]', () => {
it('enables loading state', () => {
mount({
isDataLoading: false,
});

CrawlerLogic.actions.startCrawl();

expect(CrawlSelectDomainsModalLogic.values.isDataLoading).toBe(true);
});
});

describe('[CrawlerLogic.actionTypes.onStartCrawlRequestComplete]', () => {
it('disables loading state and hides the modal', () => {
mount({
isDataLoading: true,
isModalVisible: true,
});

CrawlerLogic.actions.onStartCrawlRequestComplete();

expect(CrawlSelectDomainsModalLogic.values.isDataLoading).toBe(false);
expect(CrawlSelectDomainsModalLogic.values.isModalVisible).toBe(false);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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 { kea, MakeLogicType } from 'kea';

import { CrawlerLogic } from '../../crawler_logic';

import { CrawlerDomain } from '../../types';

export interface CrawlSelectDomainsLogicProps {
domains: CrawlerDomain[];
}

export interface CrawlSelectDomainsLogicValues {
isDataLoading: boolean;
isModalVisible: boolean;
selectedDomainUrls: string[];
}

export interface CrawlSelectDomainsModalLogicActions {
hideModal(): void;
onSelectDomainUrls(domainUrls: string[]): { domainUrls: string[] };
showModal(): void;
}

export const CrawlSelectDomainsModalLogic = kea<
MakeLogicType<
CrawlSelectDomainsLogicValues,
CrawlSelectDomainsModalLogicActions,
CrawlSelectDomainsLogicProps
>
>({
path: ['enterprise_search', 'app_search', 'crawler', 'crawl_select_domains_modal'],
actions: () => ({
hideModal: true,
onSelectDomainUrls: (domainUrls) => ({ domainUrls }),
showModal: true,
}),
reducers: () => ({
isDataLoading: [
false,
{
[CrawlerLogic.actionTypes.startCrawl]: () => true,
[CrawlerLogic.actionTypes.onStartCrawlRequestComplete]: () => false,
},
],
isModalVisible: [
false,
{
showModal: () => true,
hideModal: () => false,
[CrawlerLogic.actionTypes.onStartCrawlRequestComplete]: () => false,
},
],
selectedDomainUrls: [
[],
{
showModal: () => [],
onSelectDomainUrls: (_, { domainUrls }) => domainUrls,
},
],
}),
});
Loading

0 comments on commit 97230e9

Please sign in to comment.