From 4d7fd0a0ad8b33ba943a5958e86ca49b60ac2e8b Mon Sep 17 00:00:00 2001
From: Jason Stoltzfus
Date: Thu, 12 Aug 2021 12:39:58 -0400
Subject: [PATCH] [App Search] Added an EntryPointsTable (#108316)
---
.../components/entry_points_table.test.tsx | 119 ++++++++++++++
.../crawler/components/entry_points_table.tsx | 150 ++++++++++++++++++
.../entry_points_table_logic.test.ts | 77 +++++++++
.../components/entry_points_table_logic.ts | 59 +++++++
.../crawler/crawler_single_domain.tsx | 14 +-
.../crawler_single_domain_logic.test.ts | 30 ++++
.../crawler/crawler_single_domain_logic.ts | 6 +-
...generic_endpoint_inline_editable_table.tsx | 1 +
.../index.ts | 8 +
.../inline_editable_table.tsx | 2 +
.../applications/shared/tables/types.ts | 2 +-
.../app_search/crawler_entry_points.test.ts | 134 ++++++++++++++++
.../routes/app_search/crawler_entry_points.ts | 79 +++++++++
.../server/routes/app_search/index.ts | 2 +
14 files changed, 679 insertions(+), 4 deletions(-)
create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.test.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.tsx
create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table_logic.test.ts
create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table_logic.ts
create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/index.ts
create mode 100644 x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.test.ts
create mode 100644 x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.ts
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.test.tsx
new file mode 100644
index 0000000000000..09f1523f8e3be
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.test.tsx
@@ -0,0 +1,119 @@
+/*
+ * 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 { EuiFieldText } from '@elastic/eui';
+
+import { GenericEndpointInlineEditableTable } from '../../../../shared/tables/generic_endpoint_inline_editable_table';
+
+import { mountWithIntl } from '../../../../test_helpers';
+
+import { EntryPointsTable } from './entry_points_table';
+
+describe('EntryPointsTable', () => {
+ const engineName = 'my-engine';
+ const entryPoints = [
+ { id: '1', value: '/whatever' },
+ { id: '2', value: '/foo' },
+ ];
+ const domain = {
+ createdOn: '2018-01-01T00:00:00.000Z',
+ documentCount: 10,
+ id: '6113e1407a2f2e6f42489794',
+ url: 'https://www.elastic.co',
+ crawlRules: [],
+ entryPoints,
+ sitemaps: [],
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders', () => {
+ const wrapper = shallow(
+
+ );
+
+ expect(wrapper.find(GenericEndpointInlineEditableTable).exists()).toBe(true);
+ });
+
+ describe('the first and only column in the table', () => {
+ it('shows the value of an entry point', () => {
+ const entryPoint = { id: '1', value: '/whatever' };
+
+ const wrapper = shallow(
+
+ );
+
+ const columns = wrapper.find(GenericEndpointInlineEditableTable).prop('columns');
+ const column = shallow({columns[0].render(entryPoint)}
);
+ expect(column.html()).toContain('/whatever');
+ });
+
+ it('can show the value of an entry point as editable', () => {
+ const entryPoint = { id: '1', value: '/whatever' };
+ const onChange = jest.fn();
+
+ const wrapper = shallow(
+
+ );
+
+ const columns = wrapper.find(GenericEndpointInlineEditableTable).prop('columns');
+ const column = shallow(
+
+ {columns[0].editingRender(entryPoint, onChange, { isInvalid: false, isLoading: false })}
+
+ );
+
+ const textField = column.find(EuiFieldText);
+ expect(textField.props()).toEqual(
+ expect.objectContaining({
+ value: '/whatever',
+ disabled: false, // It would be disabled if isLoading is true
+ isInvalid: false,
+ prepend: 'https://www.elastic.co',
+ })
+ );
+
+ textField.simulate('change', { target: { value: '/foo' } });
+ expect(onChange).toHaveBeenCalledWith('/foo');
+ });
+ });
+
+ describe('routes', () => {
+ it('can calculate an update and delete route correctly', () => {
+ const wrapper = shallow(
+
+ );
+
+ const table = wrapper.find(GenericEndpointInlineEditableTable);
+
+ const entryPoint = { id: '1', value: '/whatever' };
+ expect(table.prop('deleteRoute')(entryPoint)).toEqual(
+ '/api/app_search/engines/my-engine/crawler/domains/6113e1407a2f2e6f42489794/entry_points/1'
+ );
+ expect(table.prop('updateRoute')(entryPoint)).toEqual(
+ '/api/app_search/engines/my-engine/crawler/domains/6113e1407a2f2e6f42489794/entry_points/1'
+ );
+ });
+ });
+
+ it('shows a no items message whem there are no entry points to show', () => {
+ const wrapper = shallow(
+
+ );
+
+ const editNewItems = jest.fn();
+ const table = wrapper.find(GenericEndpointInlineEditableTable);
+ const message = mountWithIntl({table.prop('noItemsMessage')!(editNewItems)}
);
+ expect(message.html()).toContain('There are no existing entry points.');
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.tsx
new file mode 100644
index 0000000000000..7657a23ce4789
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.tsx
@@ -0,0 +1,150 @@
+/*
+ * 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 { useActions } from 'kea';
+
+import { EuiFieldText, EuiLink, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
+
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import { GenericEndpointInlineEditableTable } from '../../../../shared/tables/generic_endpoint_inline_editable_table';
+import { InlineEditableTableColumn } from '../../../../shared/tables/inline_editable_table/types';
+import { ItemWithAnID } from '../../../../shared/tables/types';
+import { DOCS_PREFIX } from '../../../routes';
+import { CrawlerDomain, EntryPoint } from '../types';
+
+import { EntryPointsTableLogic } from './entry_points_table_logic';
+
+interface EntryPointsTableProps {
+ domain: CrawlerDomain;
+ engineName: string;
+ items: EntryPoint[];
+}
+
+export const EntryPointsTable: React.FC = ({
+ domain,
+ engineName,
+ items,
+}) => {
+ const { onAdd, onDelete, onUpdate } = useActions(EntryPointsTableLogic);
+ const field = 'value';
+
+ const columns: Array> = [
+ {
+ editingRender: (entryPoint, onChange, { isInvalid, isLoading }) => (
+ onChange(e.target.value)}
+ disabled={isLoading}
+ isInvalid={isInvalid}
+ prepend={domain.url}
+ />
+ ),
+ render: (entryPoint) => (
+
+ {domain.url}
+ {(entryPoint as EntryPoint)[field]}
+
+ ),
+ name: i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.urlTableHead',
+ { defaultMessage: 'URL' }
+ ),
+ field,
+ },
+ ];
+
+ const entryPointsRoute = `/api/app_search/engines/${engineName}/crawler/domains/${domain.id}/entry_points`;
+
+ const getEntryPointRoute = (entryPoint: EntryPoint) =>
+ `/api/app_search/engines/${engineName}/crawler/domains/${domain.id}/entry_points/${entryPoint.id}`;
+
+ return (
+
+ {i18n.translate('xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.description', {
+ defaultMessage:
+ 'Include the most important URLs for your website here. Entry point URLs will be the first pages to be indexed and processed for links to other pages.',
+ })}{' '}
+
+ {i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.learnMoreLinkText',
+ { defaultMessage: 'Learn more about entry points.' }
+ )}
+
+
+ }
+ instanceId="EntryPointsTable"
+ items={items}
+ lastItemWarning={i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.lastItemMessage',
+ { defaultMessage: 'The crawler requires at least one entry point.' }
+ )}
+ // Since canRemoveLastItem is false, the only time noItemsMessage would be displayed is if the last entry point was deleted via the API.
+ noItemsMessage={(editNewItem) => (
+ <>
+
+
+
+ {i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.emptyMessageTitle',
+ {
+ defaultMessage: 'There are no existing entry points.',
+ }
+ )}
+
+
+
+
+
+ {i18n.translate(
+ 'xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.emptyMessageLinkText',
+ { defaultMessage: 'Add an entry point' }
+ )}
+
+ ),
+ }}
+ />
+
+
+ >
+ )}
+ addRoute={entryPointsRoute}
+ canRemoveLastItem={false}
+ deleteRoute={getEntryPointRoute}
+ updateRoute={getEntryPointRoute}
+ dataProperty="entry_points"
+ onAdd={onAdd}
+ onDelete={onDelete}
+ onUpdate={onUpdate}
+ title={i18n.translate('xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.title', {
+ defaultMessage: 'Entry points',
+ })}
+ disableReordering
+ />
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table_logic.test.ts
new file mode 100644
index 0000000000000..7d6704b9abdb3
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table_logic.test.ts
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+jest.mock('../crawler_single_domain_logic', () => ({
+ CrawlerSingleDomainLogic: {
+ actions: {
+ updateEntryPoints: jest.fn(),
+ },
+ },
+}));
+
+import { LogicMounter, mockFlashMessageHelpers } from '../../../../__mocks__/kea_logic';
+
+import { CrawlerSingleDomainLogic } from '../crawler_single_domain_logic';
+
+import { EntryPointsTableLogic } from './entry_points_table_logic';
+
+describe('EntryPointsTableLogic', () => {
+ const { mount } = new LogicMounter(EntryPointsTableLogic);
+ const { clearFlashMessages, flashSuccessToast } = mockFlashMessageHelpers;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('listeners', () => {
+ describe('onAdd', () => {
+ it('should update the entry points for the current domain, and clear flash messages', () => {
+ const entryThatWasAdded = { id: '2', value: 'bar' };
+ const updatedEntries = [
+ { id: '1', value: 'foo' },
+ { id: '2', value: 'bar' },
+ ];
+ mount();
+ EntryPointsTableLogic.actions.onAdd(entryThatWasAdded, updatedEntries);
+ expect(CrawlerSingleDomainLogic.actions.updateEntryPoints).toHaveBeenCalledWith(
+ updatedEntries
+ );
+ expect(clearFlashMessages).toHaveBeenCalled();
+ });
+ });
+
+ describe('onDelete', () => {
+ it('should update the entry points for the current domain, clear flash messages, and show a success toast', () => {
+ const entryThatWasDeleted = { id: '2', value: 'bar' };
+ const updatedEntries = [{ id: '1', value: 'foo' }];
+ mount();
+ EntryPointsTableLogic.actions.onDelete(entryThatWasDeleted, updatedEntries);
+ expect(CrawlerSingleDomainLogic.actions.updateEntryPoints).toHaveBeenCalledWith(
+ updatedEntries
+ );
+ expect(clearFlashMessages).toHaveBeenCalled();
+ expect(flashSuccessToast).toHaveBeenCalled();
+ });
+ });
+
+ describe('onUpdate', () => {
+ it('should update the entry points for the current domain, clear flash messages, and show a success toast', () => {
+ const entryThatWasUpdated = { id: '2', value: 'baz' };
+ const updatedEntries = [
+ { id: '1', value: 'foo' },
+ { id: '2', value: 'baz' },
+ ];
+ mount();
+ EntryPointsTableLogic.actions.onUpdate(entryThatWasUpdated, updatedEntries);
+ expect(CrawlerSingleDomainLogic.actions.updateEntryPoints).toHaveBeenCalledWith(
+ updatedEntries
+ );
+ expect(clearFlashMessages).toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table_logic.ts
new file mode 100644
index 0000000000000..2332a24ea8b74
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table_logic.ts
@@ -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 { kea, MakeLogicType } from 'kea';
+
+import { clearFlashMessages, flashSuccessToast } from '../../../../shared/flash_messages';
+
+import { CrawlerSingleDomainLogic } from '../crawler_single_domain_logic';
+
+import { EntryPoint } from '../types';
+
+interface EntryPointsTableValues {
+ dataLoading: boolean;
+}
+
+interface EntryPointsTableActions {
+ onAdd(
+ entryPoint: EntryPoint,
+ entryPoints: EntryPoint[]
+ ): { entryPoint: EntryPoint; entryPoints: EntryPoint[] };
+ onDelete(
+ entryPoint: EntryPoint,
+ entryPoints: EntryPoint[]
+ ): { entryPoint: EntryPoint; entryPoints: EntryPoint[] };
+ onUpdate(
+ entryPoint: EntryPoint,
+ entryPoints: EntryPoint[]
+ ): { entryPoint: EntryPoint; entryPoints: EntryPoint[] };
+}
+
+export const EntryPointsTableLogic = kea<
+ MakeLogicType
+>({
+ path: ['enterprise_search', 'app_search', 'crawler', 'entry_points_table'],
+ actions: () => ({
+ onAdd: (entryPoint, entryPoints) => ({ entryPoint, entryPoints }),
+ onDelete: (entryPoint, entryPoints) => ({ entryPoint, entryPoints }),
+ onUpdate: (entryPoint, entryPoints) => ({ entryPoint, entryPoints }),
+ }),
+ listeners: () => ({
+ onAdd: ({ entryPoints }) => {
+ CrawlerSingleDomainLogic.actions.updateEntryPoints(entryPoints);
+ clearFlashMessages();
+ },
+ onDelete: ({ entryPoint, entryPoints }) => {
+ CrawlerSingleDomainLogic.actions.updateEntryPoints(entryPoints);
+ clearFlashMessages();
+ flashSuccessToast(`Entry point "${entryPoint.value}" was removed.`);
+ },
+ onUpdate: ({ entryPoints }) => {
+ CrawlerSingleDomainLogic.actions.updateEntryPoints(entryPoints);
+ clearFlashMessages();
+ },
+ }),
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain.tsx
index 6419c31cc16ca..da910ebc30726 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain.tsx
@@ -11,22 +11,24 @@ import { useParams } from 'react-router-dom';
import { useActions, useValues } from 'kea';
-import { EuiCode, EuiSpacer, EuiTitle } from '@elastic/eui';
+import { EuiCode, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { getEngineBreadcrumbs } from '../engine';
+import { EngineLogic, getEngineBreadcrumbs } from '../engine';
import { AppSearchPageTemplate } from '../layout';
import { CrawlerStatusBanner } from './components/crawler_status_banner';
import { CrawlerStatusIndicator } from './components/crawler_status_indicator/crawler_status_indicator';
import { DeleteDomainPanel } from './components/delete_domain_panel';
+import { EntryPointsTable } from './components/entry_points_table';
import { ManageCrawlsPopover } from './components/manage_crawls_popover/manage_crawls_popover';
import { CRAWLER_TITLE } from './constants';
import { CrawlerSingleDomainLogic } from './crawler_single_domain_logic';
export const CrawlerSingleDomain: React.FC = () => {
const { domainId } = useParams() as { domainId: string };
+ const { engineName } = EngineLogic.values;
const { dataLoading, domain } = useValues(CrawlerSingleDomainLogic);
@@ -51,6 +53,14 @@ export const CrawlerSingleDomain: React.FC = () => {
>
+ {domain && (
+ <>
+
+
+
+
+ >
+ )}
{i18n.translate(
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.test.ts
index f6c3b2c87ab8c..ead0c0ad91ced 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.test.ts
@@ -51,6 +51,36 @@ describe('CrawlerSingleDomainLogic', () => {
expect(CrawlerSingleDomainLogic.values.domain).toEqual(domain);
});
});
+
+ describe('updateEntryPoints', () => {
+ beforeEach(() => {
+ mount({
+ domain: {
+ id: '507f1f77bcf86cd799439011',
+ entryPoints: [],
+ },
+ });
+
+ CrawlerSingleDomainLogic.actions.updateEntryPoints([
+ {
+ id: '1234',
+ value: '/',
+ },
+ ]);
+ });
+
+ it('should update the entry points on the domain', () => {
+ expect(CrawlerSingleDomainLogic.values.domain).toEqual({
+ id: '507f1f77bcf86cd799439011',
+ entryPoints: [
+ {
+ id: '1234',
+ value: '/',
+ },
+ ],
+ });
+ });
+ });
});
describe('listeners', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.ts
index 7b5ba6984f106..780cab45564bb 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.ts
@@ -14,7 +14,7 @@ import { KibanaLogic } from '../../../shared/kibana';
import { ENGINE_CRAWLER_PATH } from '../../routes';
import { EngineLogic, generateEnginePath } from '../engine';
-import { CrawlerDomain } from './types';
+import { CrawlerDomain, EntryPoint } from './types';
import { crawlerDomainServerToClient, getDeleteDomainSuccessMessage } from './utils';
export interface CrawlerSingleDomainValues {
@@ -26,6 +26,7 @@ interface CrawlerSingleDomainActions {
deleteDomain(domain: CrawlerDomain): { domain: CrawlerDomain };
fetchDomainData(domainId: string): { domainId: string };
onReceiveDomainData(domain: CrawlerDomain): { domain: CrawlerDomain };
+ updateEntryPoints(entryPoints: EntryPoint[]): { entryPoints: EntryPoint[] };
}
export const CrawlerSingleDomainLogic = kea<
@@ -36,6 +37,7 @@ export const CrawlerSingleDomainLogic = kea<
deleteDomain: (domain) => ({ domain }),
fetchDomainData: (domainId) => ({ domainId }),
onReceiveDomainData: (domain) => ({ domain }),
+ updateEntryPoints: (entryPoints) => ({ entryPoints }),
},
reducers: {
dataLoading: [
@@ -48,6 +50,8 @@ export const CrawlerSingleDomainLogic = kea<
null,
{
onReceiveDomainData: (_, { domain }) => domain,
+ updateEntryPoints: (currentDomain, { entryPoints }) =>
+ ({ ...currentDomain, entryPoints } as CrawlerDomain),
},
],
},
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table.tsx
index 63b3d4b407667..4db5e81653a9a 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table.tsx
@@ -32,6 +32,7 @@ export interface GenericEndpointInlineEditableTableProps
onDelete(item: ItemWithAnID, items: ItemWithAnID[]): void;
onUpdate(item: ItemWithAnID, items: ItemWithAnID[]): void;
onReorder?(items: ItemWithAnID[]): void;
+ disableReordering?: boolean;
}
export const GenericEndpointInlineEditableTable = ({
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/index.ts
new file mode 100644
index 0000000000000..ce766171a85c6
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/index.ts
@@ -0,0 +1,8 @@
+/*
+ * 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.
+ */
+
+export { GenericEndpointInlineEditableTable } from './generic_endpoint_inline_editable_table';
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx
index 3e351f5826abc..093692dfde335 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/inline_editable_table/inline_editable_table.tsx
@@ -33,6 +33,7 @@ export interface InlineEditableTableProps- {
canRemoveLastItem?: boolean;
className?: string;
description?: React.ReactNode;
+ disableReordering?: boolean;
isLoading?: boolean;
lastItemWarning?: string;
noItemsMessage?: (editNewItem: () => void) => React.ReactNode;
@@ -170,6 +171,7 @@ export const InlineEditableTableContents =
- ({
noItemsMessage={noItemsMessage(editNewItem)}
onReorder={reorderItems}
disableDragging={isEditing}
+ {...rest}
/>
>
);
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/tables/types.ts
index f2a2531726b33..8407a14eae207 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/types.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/types.ts
@@ -6,6 +6,6 @@
*/
export type ItemWithAnID = {
- id: number | null;
+ id: number | string | null;
created_at?: string;
} & object;
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.test.ts
new file mode 100644
index 0000000000000..cdfb397b47c7e
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.test.ts
@@ -0,0 +1,134 @@
+/*
+ * 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 { mockDependencies, mockRequestHandler, MockRouter } from '../../__mocks__';
+
+import { registerCrawlerEntryPointRoutes } from './crawler_entry_points';
+
+describe('crawler entry point routes', () => {
+ describe('POST /api/app_search/engines/{engineName}/crawler/domains/{domainId}/entry_points', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockRouter = new MockRouter({
+ method: 'post',
+ path: '/api/app_search/engines/{engineName}/crawler/domains/{domainId}/entry_points',
+ });
+
+ registerCrawlerEntryPointRoutes({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+ });
+
+ it('creates a request to enterprise search', () => {
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/entry_points',
+ params: {
+ respond_with: 'index',
+ },
+ });
+ });
+
+ it('validates correctly with required params', () => {
+ const request = {
+ params: { engineName: 'some-engine', domainId: '1234' },
+ body: {
+ value: 'test',
+ },
+ };
+ mockRouter.shouldValidate(request);
+ });
+
+ it('fails otherwise', () => {
+ const request = { params: {}, body: {} };
+ mockRouter.shouldThrow(request);
+ });
+ });
+
+ describe('PUT /api/app_search/engines/{engineName}/crawler/domains/{domainId}/entry_points/{entryPointId}', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockRouter = new MockRouter({
+ method: 'put',
+ path:
+ '/api/app_search/engines/{engineName}/crawler/domains/{domainId}/entry_points/{entryPointId}',
+ });
+
+ registerCrawlerEntryPointRoutes({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+ });
+
+ it('creates a request to enterprise search', () => {
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/entry_points/:entryPointId',
+ params: {
+ respond_with: 'index',
+ },
+ });
+ });
+
+ it('validates correctly with required params', () => {
+ const request = {
+ params: { engineName: 'some-engine', domainId: '1234', entryPointId: '5678' },
+ body: {
+ value: 'test',
+ },
+ };
+ mockRouter.shouldValidate(request);
+ });
+
+ it('fails otherwise', () => {
+ const request = { params: {}, body: {} };
+ mockRouter.shouldThrow(request);
+ });
+ });
+
+ describe('DELETE /api/app_search/engines/{engineName}/crawler/domains/{domainId}/entry_points/{entryPointId}', () => {
+ let mockRouter: MockRouter;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockRouter = new MockRouter({
+ method: 'delete',
+ path:
+ '/api/app_search/engines/{engineName}/crawler/domains/{domainId}/entry_points/{entryPointId}',
+ });
+
+ registerCrawlerEntryPointRoutes({
+ ...mockDependencies,
+ router: mockRouter.router,
+ });
+ });
+
+ it('creates a request to enterprise search', () => {
+ expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
+ path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/entry_points/:entryPointId',
+ params: {
+ respond_with: 'index',
+ },
+ });
+ });
+
+ it('validates correctly with required params', () => {
+ const request = {
+ params: { engineName: 'some-engine', domainId: '1234', entryPointId: '5678' },
+ };
+ mockRouter.shouldValidate(request);
+ });
+
+ it('fails otherwise', () => {
+ const request = { params: {} };
+ mockRouter.shouldThrow(request);
+ });
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.ts
new file mode 100644
index 0000000000000..88cb58f70953c
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.ts
@@ -0,0 +1,79 @@
+/*
+ * 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 { schema } from '@kbn/config-schema';
+
+import { RouteDependencies } from '../../plugin';
+
+export function registerCrawlerEntryPointRoutes({
+ router,
+ enterpriseSearchRequestHandler,
+}: RouteDependencies) {
+ router.post(
+ {
+ path: '/api/app_search/engines/{engineName}/crawler/domains/{domainId}/entry_points',
+ validate: {
+ params: schema.object({
+ engineName: schema.string(),
+ domainId: schema.string(),
+ }),
+ body: schema.object({
+ value: schema.string(),
+ }),
+ },
+ },
+ enterpriseSearchRequestHandler.createRequest({
+ path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/entry_points',
+ params: {
+ respond_with: 'index',
+ },
+ })
+ );
+
+ router.put(
+ {
+ path:
+ '/api/app_search/engines/{engineName}/crawler/domains/{domainId}/entry_points/{entryPointId}',
+ validate: {
+ params: schema.object({
+ engineName: schema.string(),
+ domainId: schema.string(),
+ entryPointId: schema.string(),
+ }),
+ body: schema.object({
+ value: schema.string(),
+ }),
+ },
+ },
+ enterpriseSearchRequestHandler.createRequest({
+ path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/entry_points/:entryPointId',
+ params: {
+ respond_with: 'index',
+ },
+ })
+ );
+
+ router.delete(
+ {
+ path:
+ '/api/app_search/engines/{engineName}/crawler/domains/{domainId}/entry_points/{entryPointId}',
+ validate: {
+ params: schema.object({
+ engineName: schema.string(),
+ domainId: schema.string(),
+ entryPointId: schema.string(),
+ }),
+ },
+ },
+ enterpriseSearchRequestHandler.createRequest({
+ path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/entry_points/:entryPointId',
+ params: {
+ respond_with: 'index',
+ },
+ })
+ );
+}
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts
index 2442b61c632c1..af5cc78f01e78 100644
--- a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts
@@ -10,6 +10,7 @@ import { RouteDependencies } from '../../plugin';
import { registerAnalyticsRoutes } from './analytics';
import { registerApiLogsRoutes } from './api_logs';
import { registerCrawlerRoutes } from './crawler';
+import { registerCrawlerEntryPointRoutes } from './crawler_entry_points';
import { registerCredentialsRoutes } from './credentials';
import { registerCurationsRoutes } from './curations';
import { registerDocumentsRoutes, registerDocumentRoutes } from './documents';
@@ -44,4 +45,5 @@ export const registerAppSearchRoutes = (dependencies: RouteDependencies) => {
registerApiLogsRoutes(dependencies);
registerOnboardingRoutes(dependencies);
registerCrawlerRoutes(dependencies);
+ registerCrawlerEntryPointRoutes(dependencies);
};