Skip to content

Commit

Permalink
[App Search] Empty Crawler Single Domain view (#107694)
Browse files Browse the repository at this point in the history
* New route to retreive data for a single domain

* New CrawlerSingleDomainLogic logic

* New CrawlerSingleDomain view component

* Add CrawlerSingleDomain to CrawlerRouter

* Use different default text for page title while loading

* Apply suggestions from code review

Co-authored-by: Orhan Toy <toyorhan@gmail.com>

Co-authored-by: Orhan Toy <toyorhan@gmail.com>
  • Loading branch information
byronhulcher and orhantoy authored Aug 5, 2021
1 parent c695a13 commit a51787d
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,41 @@
import React from 'react';
import { Switch } from 'react-router-dom';

import { shallow } from 'enzyme';
import { shallow, ShallowWrapper } from 'enzyme';

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

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

describe('CrawlerRouter', () => {
let wrapper: ShallowWrapper;
const OLD_ENV = process.env;

beforeEach(() => {
jest.clearAllMocks();
wrapper = shallow(<CrawlerRouter />);
});

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

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 />);
rerender(wrapper);

expect(wrapper.find(CrawlerOverview)).toHaveLength(1);
});

it('renders a crawler single domain view', () => {
expect(wrapper.find(CrawlerSingleDomain)).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,21 @@
import React from 'react';
import { Route, Switch } from 'react-router-dom';

import { ENGINE_CRAWLER_PATH } from '../../routes';
import { ENGINE_CRAWLER_DOMAIN_PATH, ENGINE_CRAWLER_PATH } from '../../routes';

import { CrawlerLanding } from './crawler_landing';
import { CrawlerOverview } from './crawler_overview';
import { CrawlerSingleDomain } from './crawler_single_domain';

export const CrawlerRouter: React.FC = () => {
return (
<Switch>
<Route exact path={ENGINE_CRAWLER_PATH}>
{process.env.NODE_ENV === 'development' ? <CrawlerOverview /> : <CrawlerLanding />}
</Route>
<Route exact path={ENGINE_CRAWLER_DOMAIN_PATH}>
<CrawlerSingleDomain />
</Route>
</Switch>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 '../../../__mocks__/shallow_useeffect.mock';
import '../../__mocks__/engine_logic.mock';
import { mockUseParams } from '../../../__mocks__/react_router';

import React from 'react';

import { shallow } from 'enzyme';

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

import { CrawlerSingleDomain } from './crawler_single_domain';

const MOCK_VALUES = {
dataLoading: false,
domain: {
url: 'https://elastic.co',
},
};

const MOCK_ACTIONS = {
fetchDomainData: jest.fn(),
};

describe('CrawlerSingleDomain', () => {
beforeEach(() => {
jest.clearAllMocks();
mockUseParams.mockReturnValue({ domainId: '507f1f77bcf86cd799439011' });
setMockActions(MOCK_ACTIONS);
setMockValues(MOCK_VALUES);
});

it('renders', () => {
const wrapper = shallow(<CrawlerSingleDomain />);

expect(wrapper.find(EuiCode).render().text()).toContain('https://elastic.co');
expect(wrapper.prop('pageHeader')).toEqual({ pageTitle: 'https://elastic.co' });
});

it('uses a placeholder for the page title and page chrome if a domain has not been', () => {
setMockValues({
...MOCK_VALUES,
domain: null,
});

const wrapper = shallow(<CrawlerSingleDomain />);

expect(wrapper.prop('pageHeader')).toEqual({ pageTitle: 'Loading...' });
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 { useParams } from 'react-router-dom';

import { useActions, useValues } from 'kea';

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

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

import { getEngineBreadcrumbs } from '../engine';
import { AppSearchPageTemplate } from '../layout';

import { CRAWLER_TITLE } from './constants';
import { CrawlerSingleDomainLogic } from './crawler_single_domain_logic';

export const CrawlerSingleDomain: React.FC = () => {
const { domainId } = useParams() as { domainId: string };

const { dataLoading, domain } = useValues(CrawlerSingleDomainLogic);

const { fetchDomainData } = useActions(CrawlerSingleDomainLogic);

const displayDomainUrl = domain
? domain.url
: i18n.translate('xpack.enterpriseSearch.appSearch.crawler.singleDomain.loadingTitle', {
defaultMessage: 'Loading...',
});

useEffect(() => {
fetchDomainData(domainId);
}, []);

return (
<AppSearchPageTemplate
pageChrome={getEngineBreadcrumbs([CRAWLER_TITLE, displayDomainUrl])}
pageHeader={{ pageTitle: displayDomainUrl }}
isLoading={dataLoading}
>
<EuiCode>{JSON.stringify(domain, null, 2)}</EuiCode>
</AppSearchPageTemplate>
);
};
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 {
LogicMounter,
mockHttpValues,
mockFlashMessageHelpers,
} from '../../../__mocks__/kea_logic';
import '../../__mocks__/engine_logic.mock';

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

import { CrawlerSingleDomainLogic, CrawlerSingleDomainValues } from './crawler_single_domain_logic';
import { CrawlerDomain } from './types';

const DEFAULT_VALUES: CrawlerSingleDomainValues = {
dataLoading: true,
domain: null,
};

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

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

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

describe('actions', () => {
describe('onReceiveDomainData', () => {
const domain = {
id: '507f1f77bcf86cd799439011',
};

beforeEach(() => {
CrawlerSingleDomainLogic.actions.onReceiveDomainData(domain as CrawlerDomain);
});

it('should set the domain', () => {
expect(CrawlerSingleDomainLogic.values.domain).toEqual(domain);
});
});
});

describe('listeners', () => {
describe('fetchDomainData', () => {
it('updates logic with data that has been converted from server to client', async () => {
jest.spyOn(CrawlerSingleDomainLogic.actions, 'onReceiveDomainData');
http.get.mockReturnValueOnce(
Promise.resolve({
id: '507f1f77bcf86cd799439011',
name: 'https://elastic.co',
created_on: 'Mon, 31 Aug 2020 17:00:00 +0000',
document_count: 13,
sitemaps: [],
entry_points: [],
crawl_rules: [],
})
);

CrawlerSingleDomainLogic.actions.fetchDomainData('507f1f77bcf86cd799439011');
await nextTick();

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

it('displays any errors to the user', async () => {
http.get.mockReturnValueOnce(Promise.reject('error'));

CrawlerSingleDomainLogic.actions.fetchDomainData('507f1f77bcf86cd799439011');
await nextTick();

expect(flashAPIErrors).toHaveBeenCalledWith('error');
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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 { CrawlerDomain } from './types';
import { crawlerDomainServerToClient } from './utils';

export interface CrawlerSingleDomainValues {
dataLoading: boolean;
domain: CrawlerDomain | null;
}

interface CrawlerSingleDomainActions {
fetchDomainData(domainId: string): { domainId: string };
onReceiveDomainData(domain: CrawlerDomain): { domain: CrawlerDomain };
}

export const CrawlerSingleDomainLogic = kea<
MakeLogicType<CrawlerSingleDomainValues, CrawlerSingleDomainActions>
>({
path: ['enterprise_search', 'app_search', 'crawler', 'crawler_single_domain'],
actions: {
fetchDomainData: (domainId) => ({ domainId }),
onReceiveDomainData: (domain) => ({ domain }),
},
reducers: {
dataLoading: [
true,
{
onReceiveDomainData: () => false,
},
],
domain: [
null,
{
onReceiveDomainData: (_, { domain }) => domain,
},
],
},
listeners: ({ actions }) => ({
fetchDomainData: async ({ domainId }) => {
const { http } = HttpLogic.values;
const { engineName } = EngineLogic.values;

try {
const response = await http.get(
`/api/app_search/engines/${engineName}/crawler/domains/${domainId}`
);

const domainData = crawlerDomainServerToClient(response);

actions.onReceiveDomainData(domainData);
} catch (e) {
flashAPIErrors(e);
}
},
}),
});
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,44 @@ describe('crawler routes', () => {
});
});

describe('GET /api/app_search/engines/{name}/crawler/domains/{id}', () => {
let mockRouter: MockRouter;

beforeEach(() => {
jest.clearAllMocks();
mockRouter = new MockRouter({
method: 'get',
path: '/api/app_search/engines/{name}/crawler/domains/{id}',
});

registerCrawlerRoutes({
...mockDependencies,
router: mockRouter.router,
});
});

it('creates a request to enterprise search', () => {
expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
path: '/api/as/v0/engines/:name/crawler/domains/:id',
});
});

it('validates correctly with name and id', () => {
const request = { params: { name: 'some-engine', id: '1234' } };
mockRouter.shouldValidate(request);
});

it('fails validation without name', () => {
const request = { params: { id: '1234' } };
mockRouter.shouldThrow(request);
});

it('fails validation without id', () => {
const request = { params: { name: 'test-engine' } };
mockRouter.shouldThrow(request);
});
});

describe('POST /api/app_search/crawler/validate_url', () => {
let mockRouter: MockRouter;

Expand Down
Loading

0 comments on commit a51787d

Please sign in to comment.