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

[Enterprise Search] Show error for crawlers without a connector document #150928

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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 { mockHttpValues } from '../../../__mocks__/kea_logic';

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

import { recreateCrawlerConnector } from './recreate_crawler_connector_api_logic';

describe('CreateCrawlerIndexApiLogic', () => {
const { http } = mockHttpValues;
beforeEach(() => {
jest.clearAllMocks();
});
describe('createCrawlerIndex', () => {
it('calls correct api', async () => {
const indexName = 'elastic-co-crawler';
http.post.mockReturnValue(Promise.resolve({ connector_id: 'connectorId' }));

const result = recreateCrawlerConnector({ indexName });
await nextTick();

expect(http.post).toHaveBeenCalledWith(
'/internal/enterprise_search/indices/elastic-co-crawler/crawler/connector'
);
await expect(result).resolves.toEqual({ connector_id: 'connectorId' });
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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 { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic';
import { HttpLogic } from '../../../shared/http';

export interface RecreateCrawlerConnectorArgs {
indexName: string;
}

export interface RecreateCrawlerConnectorResponse {
created: string; // the name of the newly created index
}

export const recreateCrawlerConnector = async ({ indexName }: RecreateCrawlerConnectorArgs) => {
const route = `/internal/enterprise_search/indices/${indexName}/crawler/connector`;

return await HttpLogic.values.http.post<RecreateCrawlerConnectorResponse>(route);
};

export const RecreateCrawlerConnectorApiLogic = createApiLogic(
['recreate_crawler_connector_api_logic'],
recreateCrawlerConnector
);

export type RecreateCrawlerConnectorActions = Actions<
RecreateCrawlerConnectorArgs,
RecreateCrawlerConnectorResponse
>;
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

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

import { createApiLogic } from '../../../shared/api_logic/create_api_logic';
import { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic';
import { HttpLogic } from '../../../shared/http';

export interface DeleteIndexApiLogicArgs {
Expand Down Expand Up @@ -36,3 +36,5 @@ export const DeleteIndexApiLogic = createApiLogic(['delete_index_api_logic'], de
},
}),
});

export type DeleteIndexApiActions = Actions<DeleteIndexApiLogicArgs, DeleteIndexApiLogicValues>;
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { SyncsContextMenu } from './syncs_context_menu';
export const getHeaderActions = (indexData?: ElasticsearchIndexWithIngestion) => {
const ingestionMethod = getIngestionMethod(indexData);
return [
...(isCrawlerIndex(indexData) ? [<CrawlerStatusIndicator />] : []),
...(isCrawlerIndex(indexData) && indexData.connector ? [<CrawlerStatusIndicator />] : []),
...(isConnectorIndex(indexData) ? [<SyncsContextMenu />] : []),
<SearchEnginesPopover
indexName={indexData?.name}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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, useValues } from 'kea';

import { EuiButton, EuiPageTemplate } from '@elastic/eui';

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

import { Status } from '../../../../../../common/types/api';

import { RecreateCrawlerConnectorApiLogic } from '../../../api/crawler/recreate_crawler_connector_api_logic';
import { DeleteIndexModal } from '../../search_indices/delete_index_modal';
import { IndicesLogic } from '../../search_indices/indices_logic';
import { IndexViewLogic } from '../index_view_logic';

import { NoConnectorRecordLogic } from './no_connector_record_logic';

export const NoConnectorRecord: React.FC = () => {
const { indexName } = useValues(IndexViewLogic);
const { isDeleteLoading } = useValues(IndicesLogic);
const { openDeleteModal } = useActions(IndicesLogic);
const { makeRequest } = useActions(RecreateCrawlerConnectorApiLogic);
const { status } = useValues(RecreateCrawlerConnectorApiLogic);
NoConnectorRecordLogic.mount();
const buttonsDisabled = status === Status.LOADING || isDeleteLoading;

return (
<>
<DeleteIndexModal />

<EuiPageTemplate.EmptyPrompt
iconType="alert"
color="danger"
title={
<h2>
{i18n.translate(
'xpack.enterpriseSearch.content.searchIndex.noCrawlerConnectorFound.title',
{
defaultMessage: 'This crawler has no connector',
}
)}
</h2>
}
body={
<p>
{i18n.translate(
'xpack.enterpriseSearch.content.searchIndex.noCrawlerConnectorFound.description',
{
defaultMessage:
'We could not find a connector document for this crawler index. The record should be recreated, or the index should be deleted.',
}
)}
</p>
}
actions={[
<EuiButton
color="danger"
disabled={buttonsDisabled}
isLoading={status === Status.LOADING}
onClick={() => makeRequest({ indexName })}
>
{i18n.translate(
'xpack.enterpriseSearch.content.searchIndex.noCrawlerConnectorFound.recreateConnectorRecord',
{
defaultMessage: 'Recreate connector record',
}
)}
</EuiButton>,
<EuiButton
color="danger"
disabled={buttonsDisabled}
isLoading={isDeleteLoading}
fill
onClick={() => openDeleteModal(indexName)}
>
{i18n.translate(
'xpack.enterpriseSearch.content.searchIndex.noCrawlerConnectorFound.recreateConnectorRecord',
{
defaultMessage: 'Delete index',
}
)}
</EuiButton>,
]}
/>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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 { KibanaLogic } from '../../../../shared/kibana';

import { RecreateCrawlerConnectorApiLogic } from '../../../api/crawler/recreate_crawler_connector_api_logic';
import { DeleteIndexApiLogic } from '../../../api/index/delete_index_api_logic';
import { SEARCH_INDICES_PATH } from '../../../routes';

import { NoConnectorRecordLogic } from './no_connector_record_logic';

describe('NoConnectorRecordLogic', () => {
const { mount: deleteMount } = new LogicMounter(DeleteIndexApiLogic);
const { mount: recreateMount } = new LogicMounter(RecreateCrawlerConnectorApiLogic);
const { mount } = new LogicMounter(NoConnectorRecordLogic);
beforeEach(() => {
deleteMount();
recreateMount();
mount();
});
it('should redirect to search indices on delete', () => {
KibanaLogic.values.navigateToUrl = jest.fn();
DeleteIndexApiLogic.actions.apiSuccess({} as any);
expect(KibanaLogic.values.navigateToUrl).toHaveBeenCalledWith(SEARCH_INDICES_PATH);
});
it('should fetch index on recreate', () => {
NoConnectorRecordLogic.actions.fetchIndex = jest.fn();
RecreateCrawlerConnectorApiLogic.actions.apiSuccess({} as any);
expect(NoConnectorRecordLogic.actions.fetchIndex).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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 { KibanaLogic } from '../../../../shared/kibana';

import {
RecreateCrawlerConnectorActions,
RecreateCrawlerConnectorApiLogic,
} from '../../../api/crawler/recreate_crawler_connector_api_logic';
import {
DeleteIndexApiActions,
DeleteIndexApiLogic,
} from '../../../api/index/delete_index_api_logic';
import { SEARCH_INDICES_PATH } from '../../../routes';
import { IndexViewActions, IndexViewLogic } from '../index_view_logic';

type NoConnectorRecordActions = RecreateCrawlerConnectorActions['apiSuccess'] & {
deleteSuccess: DeleteIndexApiActions['apiSuccess'];
fetchIndex: IndexViewActions['fetchIndex'];
};

export const NoConnectorRecordLogic = kea<MakeLogicType<{}, NoConnectorRecordActions>>({
connect: {
actions: [
RecreateCrawlerConnectorApiLogic,
['apiSuccess'],
IndexViewLogic,
['fetchIndex'],
DeleteIndexApiLogic,
['apiSuccess as deleteSuccess'],
],
},
listeners: ({ actions }) => ({
apiSuccess: () => {
actions.fetchIndex();
},
deleteSuccess: () => {
KibanaLogic.values.navigateToUrl(SEARCH_INDICES_PATH);
},
}),
path: ['enterprise_search', 'content', 'no_connector_record'],
});
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { AutomaticCrawlScheduler } from './crawler/automatic_crawl_scheduler/aut
import { CrawlCustomSettingsFlyout } from './crawler/crawl_custom_settings_flyout/crawl_custom_settings_flyout';
import { CrawlerConfiguration } from './crawler/crawler_configuration/crawler_configuration';
import { SearchIndexDomainManagement } from './crawler/domain_management/domain_management';
import { NoConnectorRecord } from './crawler/no_connector_record';
import { SearchIndexDocuments } from './documents';
import { SearchIndexIndexMappings } from './index_mappings';
import { IndexNameLogic } from './index_name_logic';
Expand Down Expand Up @@ -224,12 +225,16 @@ export const SearchIndex: React.FC = () => {
rightSideItems: getHeaderActions(index),
}}
>
<>
{indexName === index?.name && (
<EuiTabbedContent tabs={tabs} selectedTab={selectedTab} onTabClick={onTabClick} />
)}
{isCrawlerIndex(index) && <CrawlCustomSettingsFlyout />}
</>
{isCrawlerIndex(index) && !index.connector ? (
<NoConnectorRecord />
) : (
<>
{indexName === index?.name && (
<EuiTabbedContent tabs={tabs} selectedTab={selectedTab} onTabClick={onTabClick} />
)}
{isCrawlerIndex(index) && <CrawlCustomSettingsFlyout />}
</>
)}
</EnterpriseSearchContentPageTemplate>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ describe('IndicesLogic', () => {
describe('openDeleteModal', () => {
it('should set deleteIndexName and set isDeleteModalVisible to true', () => {
IndicesLogic.actions.fetchIndexDetails = jest.fn();
IndicesLogic.actions.openDeleteModal(connectorIndex);
IndicesLogic.actions.openDeleteModal(connectorIndex.name);
expect(IndicesLogic.values).toEqual({
...DEFAULT_VALUES,
deleteModalIndexName: 'connector',
Expand All @@ -98,7 +98,7 @@ describe('IndicesLogic', () => {
});
describe('closeDeleteModal', () => {
it('should set deleteIndexName to empty and set isDeleteModalVisible to false', () => {
IndicesLogic.actions.openDeleteModal(connectorIndex);
IndicesLogic.actions.openDeleteModal(connectorIndex.name);
IndicesLogic.actions.fetchIndexDetails = jest.fn();
IndicesLogic.actions.closeDeleteModal();
expect(IndicesLogic.values).toEqual({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export interface IndicesActions {
}): { meta: Meta; returnHiddenIndices: boolean; searchQuery?: string };
makeRequest: typeof FetchIndicesAPILogic.actions.makeRequest;
onPaginate(newPageIndex: number): { newPageIndex: number };
openDeleteModal(index: ElasticsearchViewIndex): { index: ElasticsearchViewIndex };
openDeleteModal(indexName: string): { indexName: string };
setIsFirstRequest(): void;
}
export interface IndicesValues {
Expand Down Expand Up @@ -102,7 +102,7 @@ export const IndicesLogic = kea<MakeLogicType<IndicesValues, IndicesActions>>({
searchQuery,
}),
onPaginate: (newPageIndex) => ({ newPageIndex }),
openDeleteModal: (index) => ({ index }),
openDeleteModal: (indexName) => ({ indexName }),
setIsFirstRequest: true,
},
connect: {
Expand Down Expand Up @@ -137,8 +137,8 @@ export const IndicesLogic = kea<MakeLogicType<IndicesValues, IndicesActions>>({
await breakpoint(150);
actions.makeRequest(input);
},
openDeleteModal: ({ index }) => {
actions.fetchIndexDetails({ indexName: index.name });
openDeleteModal: ({ indexName }) => {
actions.fetchIndexDetails({ indexName });
},
}),
path: ['enterprise_search', 'content', 'indices_logic'],
Expand All @@ -147,7 +147,7 @@ export const IndicesLogic = kea<MakeLogicType<IndicesValues, IndicesActions>>({
'',
{
closeDeleteModal: () => '',
openDeleteModal: (_, { index: { name } }) => name,
openDeleteModal: (_, { indexName }) => indexName,
},
],
isDeleteModalVisible: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ interface IndicesTableProps {
isLoading?: boolean;
meta: Meta;
onChange: (criteria: CriteriaWithPagination<ElasticsearchViewIndex>) => void;
onDelete: (index: ElasticsearchViewIndex) => void;
onDelete: (indexName: string) => void;
}

export const IndicesTable: React.FC<IndicesTableProps> = ({
Expand Down Expand Up @@ -175,7 +175,7 @@ export const IndicesTable: React.FC<IndicesTableProps> = ({
},
}
),
onClick: (index) => onDelete(index),
onClick: (index) => onDelete(index.name),
type: 'icon',
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export function getIngestionStatus(index?: ElasticsearchIndexWithIngestion): Ing
if (!index || isApiIndex(index)) {
return IngestionStatus.CONNECTED;
}
if (isConnectorIndex(index) || isCrawlerIndex(index)) {
if (isConnectorIndex(index) || (isCrawlerIndex(index) && index.connector)) {
if (
index.connector.last_seen &&
moment(index.connector.last_seen).isBefore(moment().subtract(30, 'minutes'))
Expand Down
Loading