Skip to content

Commit

Permalink
[App Search] Initial logic for Crawler Overview (#101176)
Browse files Browse the repository at this point in the history
* New CrawlerOverview component

* CrawlerRouter should use CrawlerOverview in dev mode

* New CrawlerOverviewLogic

* New crawler route

* Display domains data for CrawlerOverview in EuiCode

* Update types

* Clean up tests for Crawler utils

* Better todo commenting for CrawlerOverview tests

* Remove unused div from CrawlerOverview

* Rename CrawlerOverviewLogic.actios.setCrawlerData to onFetchCrawlerData

* Cleaning up CrawlerOverviewLogic

* Cleaning up CrawlerOverviewLogic tests

* Fix CrawlerPolicies capitalization

* Add Loading UX

* Cleaning up afterEachs across Crawler tests
  • Loading branch information
byronhulcher authored Jun 4, 2021
1 parent 090d0ab commit 9a275de
Show file tree
Hide file tree
Showing 13 changed files with 589 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,11 @@ describe('CrawlerLanding', () => {
let wrapper: ShallowWrapper;

beforeEach(() => {
jest.clearAllMocks();
setMockValues({ ...mockEngineValues });
wrapper = shallow(<CrawlerLanding />);
});

afterEach(() => {
jest.clearAllMocks();
});

it('contains an external documentation link', () => {
const externalDocumentationLink = wrapper.find('[data-test-subj="CrawlerDocumentationLink"]');

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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 { rerender, setMockActions, setMockValues } from '../../../__mocks__';
import '../../../__mocks__/shallow_useeffect.mock';

import React from 'react';

import { shallow, ShallowWrapper } from 'enzyme';

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

import { Loading } from '../../../shared/loading';

import { CrawlerOverview } from './crawler_overview';

const actions = {
fetchCrawlerData: jest.fn(),
};

const values = {
dataLoading: false,
domains: [],
};

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

beforeEach(() => {
jest.clearAllMocks();
setMockValues(values);
setMockActions(actions);
wrapper = shallow(<CrawlerOverview />);
});

it('renders', () => {
expect(wrapper.find(EuiCode)).toHaveLength(1);
});

it('calls fetchCrawlerData on page load', () => {
expect(actions.fetchCrawlerData).toHaveBeenCalledTimes(1);
});

// TODO after DomainsTable is built in a future PR
// it('contains a DomainsTable', () => {})

// TODO after CrawlRequestsTable is built in a future PR
// it('containss a CrawlRequestsTable,() => {})

// TODO after AddDomainForm is built in a future PR
// it('contains an AddDomainForm' () => {})

// TODO after empty state is added in a future PR
// it('has an empty state', () => {} )

it('shows an empty state when data is loading', () => {
setMockValues({ dataLoading: true });
rerender(wrapper);

expect(wrapper.find(Loading)).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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, { useEffect } from 'react';

import { useActions, useValues } from 'kea';

import { EuiCode, EuiPageHeader } from '@elastic/eui';

import { FlashMessages } from '../../../shared/flash_messages';

import { Loading } from '../../../shared/loading';

import { CRAWLER_TITLE } from './constants';
import { CrawlerOverviewLogic } from './crawler_overview_logic';

export const CrawlerOverview: React.FC = () => {
const { dataLoading, domains } = useValues(CrawlerOverviewLogic);

const { fetchCrawlerData } = useActions(CrawlerOverviewLogic);

useEffect(() => {
fetchCrawlerData();
}, []);

if (dataLoading) {
return <Loading />;
}

return (
<>
<EuiPageHeader pageTitle={CRAWLER_TITLE} />
<FlashMessages />
<EuiCode language="json">{JSON.stringify(domains, null, 2)}</EuiCode>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* 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, mockHttpValues, mockFlashMessageHelpers } from '../../../__mocks__';
import '../../__mocks__/engine_logic.mock';

import { nextTick } from '@kbn/test/jest';

import { CrawlerOverviewLogic } from './crawler_overview_logic';
import { CrawlerPolicies, CrawlerRules, CrawlRule } from './types';

const DEFAULT_VALUES = {
dataLoading: true,
domains: [],
};

const DEFAULT_CRAWL_RULE: CrawlRule = {
id: '-',
policy: CrawlerPolicies.allow,
rule: CrawlerRules.regex,
pattern: '.*',
};

describe('CrawlerOverviewLogic', () => {
const { mount } = new LogicMounter(CrawlerOverviewLogic);
const { http } = mockHttpValues;
const { flashAPIErrors } = mockFlashMessageHelpers;

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

it('has expected default values', () => {
expect(CrawlerOverviewLogic.values).toEqual(DEFAULT_VALUES);
});

describe('actions', () => {
describe('onFetchCrawlerData', () => {
const crawlerData = {
domains: [
{
id: '507f1f77bcf86cd799439011',
createdOn: 'Mon, 31 Aug 2020 17:00:00 +0000',
url: 'moviedatabase.com',
documentCount: 13,
sitemaps: [],
entryPoints: [],
crawlRules: [],
defaultCrawlRule: DEFAULT_CRAWL_RULE,
},
],
};

beforeEach(() => {
CrawlerOverviewLogic.actions.onFetchCrawlerData(crawlerData);
});

it('should set all received data as top-level values', () => {
expect(CrawlerOverviewLogic.values.domains).toEqual(crawlerData.domains);
});

it('should set dataLoading to false', () => {
expect(CrawlerOverviewLogic.values.dataLoading).toEqual(false);
});
});
});

describe('listeners', () => {
describe('fetchCrawlerData', () => {
it('calls onFetchCrawlerData with retrieved data that has been converted from server to client', async () => {
jest.spyOn(CrawlerOverviewLogic.actions, 'onFetchCrawlerData');

http.get.mockReturnValue(
Promise.resolve({
domains: [
{
id: '507f1f77bcf86cd799439011',
name: 'moviedatabase.com',
created_on: 'Mon, 31 Aug 2020 17:00:00 +0000',
document_count: 13,
sitemaps: [],
entry_points: [],
crawl_rules: [],
},
],
})
);
CrawlerOverviewLogic.actions.fetchCrawlerData();
await nextTick();

expect(http.get).toHaveBeenCalledWith('/api/app_search/engines/some-engine/crawler');
expect(CrawlerOverviewLogic.actions.onFetchCrawlerData).toHaveBeenCalledWith({
domains: [
{
id: '507f1f77bcf86cd799439011',
createdOn: 'Mon, 31 Aug 2020 17:00:00 +0000',
url: 'moviedatabase.com',
documentCount: 13,
sitemaps: [],
entryPoints: [],
crawlRules: [],
},
],
});
});

it('calls flashApiErrors when there is an error', async () => {
http.get.mockReturnValue(Promise.reject('error'));
CrawlerOverviewLogic.actions.fetchCrawlerData();
await nextTick();

expect(flashAPIErrors).toHaveBeenCalledWith('error');
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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 { flashAPIErrors } from '../../../shared/flash_messages';

import { HttpLogic } from '../../../shared/http';
import { EngineLogic } from '../engine';

import { CrawlerData, CrawlerDataFromServer, CrawlerDomain } from './types';
import { crawlerDataServerToClient } from './utils';

interface CrawlerOverviewValues {
dataLoading: boolean;
domains: CrawlerDomain[];
}

interface CrawlerOverviewActions {
fetchCrawlerData(): void;
onFetchCrawlerData(data: CrawlerData): { data: CrawlerData };
}

export const CrawlerOverviewLogic = kea<
MakeLogicType<CrawlerOverviewValues, CrawlerOverviewActions>
>({
path: ['enterprise_search', 'app_search', 'crawler', 'crawler_overview'],
actions: {
fetchCrawlerData: true,
onFetchCrawlerData: (data) => ({ data }),
},
reducers: {
dataLoading: [
true,
{
onFetchCrawlerData: () => false,
},
],
domains: [
[],
{
onFetchCrawlerData: (_, { data: { domains } }) => domains,
},
],
},
listeners: ({ actions }) => ({
fetchCrawlerData: async () => {
const { http } = HttpLogic.values;
const { engineName } = EngineLogic.values;

try {
const response = await http.get(`/api/app_search/engines/${engineName}/crawler`);
const crawlerData = crawlerDataServerToClient(response as CrawlerDataFromServer);
actions.onFetchCrawlerData(crawlerData);
} catch (e) {
flashAPIErrors(e);
}
},
}),
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,32 @@ import { Switch } from 'react-router-dom';
import { shallow } from 'enzyme';

import { CrawlerLanding } from './crawler_landing';
import { CrawlerOverview } from './crawler_overview';
import { CrawlerRouter } from './crawler_router';

describe('CrawlerRouter', () => {
const OLD_ENV = process.env;

beforeEach(() => {
jest.clearAllMocks();
setMockValues({ ...mockEngineValues });
});

afterEach(() => {
jest.clearAllMocks();
process.env = OLD_ENV;
});

it('renders a landing page', () => {
it('renders a landing page by default', () => {
const wrapper = shallow(<CrawlerRouter />);

expect(wrapper.find(Switch)).toHaveLength(1);
expect(wrapper.find(CrawlerLanding)).toHaveLength(1);
});

it('renders a crawler overview in dev', () => {
process.env.NODE_ENV = 'development';
const wrapper = shallow(<CrawlerRouter />);

expect(wrapper.find(CrawlerOverview)).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ import { getEngineBreadcrumbs } from '../engine';

import { CRAWLER_TITLE } from './constants';
import { CrawlerLanding } from './crawler_landing';
import { CrawlerOverview } from './crawler_overview';

export const CrawlerRouter: React.FC = () => {
return (
<Switch>
<Route>
<SetPageChrome trail={getEngineBreadcrumbs([CRAWLER_TITLE])} />
<CrawlerLanding />
{process.env.NODE_ENV === 'development' ? <CrawlerOverview /> : <CrawlerLanding />}
</Route>
</Switch>
);
Expand Down
Loading

0 comments on commit 9a275de

Please sign in to comment.