mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Enterprise Search] Fix connector overview (#137069)
This commit is contained in:
parent
a37aae22aa
commit
3d62cc3201
12 changed files with 249 additions and 69 deletions
|
@ -39,6 +39,7 @@ export interface Connector {
|
|||
last_sync_error: string | null;
|
||||
last_sync_status: string | null;
|
||||
last_synced: string | null;
|
||||
name: string;
|
||||
scheduling: {
|
||||
enabled: boolean;
|
||||
interval: string; // crontab syntax
|
||||
|
|
|
@ -30,7 +30,7 @@ export const indices: ElasticsearchIndexWithIngestion[] = [
|
|||
last_sync_error: null,
|
||||
last_sync_status: SyncStatus.COMPLETED,
|
||||
last_synced: null,
|
||||
|
||||
name: 'connector',
|
||||
scheduling: {
|
||||
enabled: false,
|
||||
interval: '',
|
||||
|
|
|
@ -39,6 +39,7 @@ export const connectorIndex: ConnectorViewIndex = {
|
|||
last_sync_error: null,
|
||||
last_sync_status: SyncStatus.COMPLETED,
|
||||
last_synced: null,
|
||||
name: 'connector',
|
||||
scheduling: {
|
||||
enabled: false,
|
||||
interval: '',
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* 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 { generatePath } from 'react-router-dom';
|
||||
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { EuiLinkTo } from '../../../../shared/react_router_helpers';
|
||||
|
||||
import { SEARCH_INDEX_TAB_PATH } from '../../../routes';
|
||||
import { IngestionStatus } from '../../../types';
|
||||
import { isConnectorIndex } from '../../../utils/indices';
|
||||
|
||||
import {
|
||||
ingestionStatusToColor,
|
||||
ingestionStatusToText,
|
||||
} from '../../../utils/ingestion_status_helpers';
|
||||
import { IndexViewLogic } from '../index_view_logic';
|
||||
import { SearchIndexTabId } from '../search_index';
|
||||
|
||||
export const ConnectorOverviewPanels: React.FC = () => {
|
||||
const { ingestionStatus, index } = useValues(IndexViewLogic);
|
||||
|
||||
const statusPanel = (
|
||||
<EuiPanel color={ingestionStatusToColor(ingestionStatus)} hasShadow={false} paddingSize="l">
|
||||
<EuiStat
|
||||
description={i18n.translate('xpack.enterpriseSearch.connector.ingestionStatus.title', {
|
||||
defaultMessage: 'Ingestion status',
|
||||
})}
|
||||
title={ingestionStatusToText(ingestionStatus)}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
|
||||
return isConnectorIndex(index) ? (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiPanel color="subdued" hasShadow={false} paddingSize="l">
|
||||
<EuiStat
|
||||
description={i18n.translate(
|
||||
'xpack.enterpriseSearch.connector.connectorNamePanel.title',
|
||||
{
|
||||
defaultMessage: 'Name',
|
||||
}
|
||||
)}
|
||||
title={index.connector.name}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiPanel color="subdued" hasShadow={false} paddingSize="l">
|
||||
<EuiStat
|
||||
description={i18n.translate(
|
||||
'xpack.enterpriseSearch.connector.connectorTypePanel.title',
|
||||
{
|
||||
defaultMessage: 'Connector type',
|
||||
}
|
||||
)}
|
||||
title={
|
||||
index.connector.service_type ??
|
||||
i18n.translate('xpack.enterpriseSearch.connector.connectorTypePanel.unknown.label', {
|
||||
defaultMessage: 'Unknown',
|
||||
})
|
||||
}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={1}>
|
||||
{ingestionStatus === IngestionStatus.INCOMPLETE ? (
|
||||
<EuiLinkTo
|
||||
to={generatePath(SEARCH_INDEX_TAB_PATH, {
|
||||
indexName: index.name,
|
||||
tabId: SearchIndexTabId.CONFIGURATION,
|
||||
})}
|
||||
>
|
||||
{statusPanel}
|
||||
</EuiLinkTo>
|
||||
) : (
|
||||
statusPanel
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
};
|
|
@ -15,6 +15,7 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import { isApiIndex, isConnectorIndex, isCrawlerIndex } from '../../utils/indices';
|
||||
|
||||
import { ConnectorOverviewPanels } from './connector/connector_overview_panels';
|
||||
import { CrawlDetailsFlyout } from './crawler/crawl_details_flyout/crawl_details_flyout';
|
||||
import { CrawlRequestsPanel } from './crawler/crawl_requests_panel/crawl_requests_panel';
|
||||
import { CrawlerTotalStats } from './crawler_total_stats';
|
||||
|
@ -62,6 +63,12 @@ export const SearchIndexOverview: React.FC = () => {
|
|||
<CrawlDetailsFlyout />
|
||||
</>
|
||||
)}
|
||||
{isConnectorIndex(indexData) && (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<ConnectorOverviewPanels />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -26,6 +26,10 @@ import { convertMetaToPagination } from '../../../shared/table_pagination';
|
|||
import { SEARCH_INDEX_PATH } from '../../routes';
|
||||
import { ElasticsearchViewIndex, IngestionMethod, IngestionStatus } from '../../types';
|
||||
import { ingestionMethodToText } from '../../utils/indices';
|
||||
import {
|
||||
ingestionStatusToColor,
|
||||
ingestionStatusToText,
|
||||
} from '../../utils/ingestion_status_helpers';
|
||||
|
||||
const healthColorsMap = {
|
||||
green: 'success',
|
||||
|
@ -122,45 +126,12 @@ const columns: Array<EuiBasicTableColumn<ElasticsearchViewIndex>> = [
|
|||
defaultMessage: 'Ingestion status',
|
||||
}
|
||||
),
|
||||
render: (ingestionStatus: IngestionStatus) => {
|
||||
const getBadge = (status: string, text: string) => {
|
||||
return <EuiBadge color={status}>{text}</EuiBadge>;
|
||||
};
|
||||
if (ingestionStatus === IngestionStatus.CONNECTED) {
|
||||
return getBadge(
|
||||
'success',
|
||||
i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndices.ingestionStatus.connected.label',
|
||||
{ defaultMessage: 'Connected' }
|
||||
)
|
||||
);
|
||||
}
|
||||
if (ingestionStatus === IngestionStatus.ERROR) {
|
||||
return getBadge(
|
||||
'danger',
|
||||
i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndices.ingestionStatus.connectorError.label',
|
||||
{ defaultMessage: 'Connector failure' }
|
||||
)
|
||||
);
|
||||
}
|
||||
if (ingestionStatus === IngestionStatus.SYNC_ERROR) {
|
||||
return getBadge(
|
||||
'danger',
|
||||
i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndices.ingestionStatus.syncError.label',
|
||||
{ defaultMessage: 'Sync failure' }
|
||||
)
|
||||
);
|
||||
}
|
||||
return getBadge(
|
||||
'warning',
|
||||
i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndices.ingestionStatus.incomplete.label',
|
||||
{ defaultMessage: 'Incomplete' }
|
||||
)
|
||||
);
|
||||
},
|
||||
render: (ingestionStatus: IngestionStatus) => (
|
||||
<EuiBadge color={ingestionStatusToColor(ingestionStatus)}>
|
||||
{ingestionStatusToText(ingestionStatus)}
|
||||
</EuiBadge>
|
||||
),
|
||||
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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 { IngestionStatus } from '../types';
|
||||
|
||||
import { ingestionStatusToColor, ingestionStatusToText } from './ingestion_status_helpers';
|
||||
|
||||
describe('ingestionStatus helper functions', () => {
|
||||
describe('ingestionStatusToText', () => {
|
||||
it('should return connected for connected', () => {
|
||||
expect(ingestionStatusToText(IngestionStatus.CONNECTED)).toEqual('Connected');
|
||||
});
|
||||
it('should return error for error', () => {
|
||||
expect(ingestionStatusToText(IngestionStatus.ERROR)).toEqual('Connector failure');
|
||||
});
|
||||
it('should return error for sync error', () => {
|
||||
expect(ingestionStatusToText(IngestionStatus.SYNC_ERROR)).toEqual('Sync failure');
|
||||
});
|
||||
it('should return incomplete for other statuses', () => {
|
||||
expect(ingestionStatusToText(IngestionStatus.INCOMPLETE)).toEqual('Incomplete');
|
||||
});
|
||||
});
|
||||
describe('ingestionStatusToColor', () => {
|
||||
it('should return success for connected', () => {
|
||||
expect(ingestionStatusToColor(IngestionStatus.CONNECTED)).toEqual('success');
|
||||
});
|
||||
it('should return error for error', () => {
|
||||
expect(ingestionStatusToColor(IngestionStatus.ERROR)).toEqual('danger');
|
||||
});
|
||||
it('should return error for sync error', () => {
|
||||
expect(ingestionStatusToColor(IngestionStatus.SYNC_ERROR)).toEqual('danger');
|
||||
});
|
||||
it('should return warning for other statuses', () => {
|
||||
expect(ingestionStatusToColor(IngestionStatus.INCOMPLETE)).toEqual('warning');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
import { IngestionStatus } from '../types';
|
||||
|
||||
export function ingestionStatusToText(ingestionStatus: IngestionStatus): string {
|
||||
if (ingestionStatus === IngestionStatus.CONNECTED) {
|
||||
return i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndices.ingestionStatus.connected.label',
|
||||
{ defaultMessage: 'Connected' }
|
||||
);
|
||||
}
|
||||
if (ingestionStatus === IngestionStatus.ERROR) {
|
||||
return i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndices.ingestionStatus.connectorError.label',
|
||||
{ defaultMessage: 'Connector failure' }
|
||||
);
|
||||
}
|
||||
if (ingestionStatus === IngestionStatus.SYNC_ERROR) {
|
||||
return i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndices.ingestionStatus.syncError.label',
|
||||
{ defaultMessage: 'Sync failure' }
|
||||
);
|
||||
}
|
||||
return i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndices.ingestionStatus.incomplete.label',
|
||||
{ defaultMessage: 'Incomplete' }
|
||||
);
|
||||
}
|
||||
|
||||
export function ingestionStatusToColor(
|
||||
ingestionStatus: IngestionStatus
|
||||
): 'warning' | 'danger' | 'success' {
|
||||
if (ingestionStatus === IngestionStatus.CONNECTED) {
|
||||
return 'success';
|
||||
}
|
||||
if (ingestionStatus === IngestionStatus.ERROR || ingestionStatus === IngestionStatus.SYNC_ERROR) {
|
||||
return 'danger';
|
||||
}
|
||||
return 'warning';
|
||||
}
|
|
@ -44,6 +44,7 @@ describe('Setup Indices', () => {
|
|||
last_sync_error: { type: 'keyword' },
|
||||
last_sync_status: { type: 'keyword' },
|
||||
last_synced: { type: 'date' },
|
||||
name: { type: 'keyword' },
|
||||
scheduling: {
|
||||
properties: {
|
||||
enabled: { type: 'boolean' },
|
||||
|
@ -62,6 +63,7 @@ describe('Setup Indices', () => {
|
|||
},
|
||||
properties: {
|
||||
completed_at: { type: 'date' },
|
||||
connector: connectorsMappings.properties,
|
||||
connector_id: {
|
||||
type: 'keyword',
|
||||
},
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { IndicesIndexSettings, MappingTypeMapping } from '@elastic/elasticsearch/lib/api/types';
|
||||
import {
|
||||
IndicesIndexSettings,
|
||||
MappingProperty,
|
||||
MappingTypeMapping,
|
||||
} from '@elastic/elasticsearch/lib/api/types';
|
||||
import { ElasticsearchClient } from '@kbn/core/server';
|
||||
|
||||
import { CONNECTORS_INDEX } from '..';
|
||||
|
@ -23,6 +27,32 @@ interface IndexDefinition {
|
|||
settings: IndicesIndexSettings;
|
||||
}
|
||||
|
||||
const connectorMappingsProperties: Record<string, MappingProperty> = {
|
||||
api_key_id: {
|
||||
type: 'keyword',
|
||||
},
|
||||
configuration: {
|
||||
type: 'object',
|
||||
},
|
||||
error: { type: 'keyword' },
|
||||
index_name: { type: 'keyword' },
|
||||
language: { type: 'keyword' },
|
||||
last_seen: { type: 'date' },
|
||||
last_sync_error: { type: 'keyword' },
|
||||
last_sync_status: { type: 'keyword' },
|
||||
last_synced: { type: 'date' },
|
||||
name: { type: 'keyword' },
|
||||
scheduling: {
|
||||
properties: {
|
||||
enabled: { type: 'boolean' },
|
||||
interval: { type: 'text' },
|
||||
},
|
||||
},
|
||||
service_type: { type: 'keyword' },
|
||||
status: { type: 'keyword' },
|
||||
sync_now: { type: 'boolean' },
|
||||
};
|
||||
|
||||
const indices: IndexDefinition[] = [
|
||||
{
|
||||
aliases: ['.elastic-connectors'],
|
||||
|
@ -30,30 +60,7 @@ const indices: IndexDefinition[] = [
|
|||
_meta: {
|
||||
version: '1',
|
||||
},
|
||||
properties: {
|
||||
api_key_id: {
|
||||
type: 'keyword',
|
||||
},
|
||||
configuration: {
|
||||
type: 'object',
|
||||
},
|
||||
error: { type: 'keyword' },
|
||||
index_name: { type: 'keyword' },
|
||||
language: { type: 'keyword' },
|
||||
last_seen: { type: 'date' },
|
||||
last_sync_error: { type: 'keyword' },
|
||||
last_sync_status: { type: 'keyword' },
|
||||
last_synced: { type: 'date' },
|
||||
scheduling: {
|
||||
properties: {
|
||||
enabled: { type: 'boolean' },
|
||||
interval: { type: 'text' },
|
||||
},
|
||||
},
|
||||
service_type: { type: 'keyword' },
|
||||
status: { type: 'keyword' },
|
||||
sync_now: { type: 'boolean' },
|
||||
},
|
||||
properties: connectorMappingsProperties,
|
||||
},
|
||||
name: '.elastic-connectors-v1',
|
||||
settings: {
|
||||
|
@ -68,6 +75,7 @@ const indices: IndexDefinition[] = [
|
|||
},
|
||||
properties: {
|
||||
completed_at: { type: 'date' },
|
||||
connector: connectorMappingsProperties,
|
||||
connector_id: {
|
||||
type: 'keyword',
|
||||
},
|
||||
|
|
|
@ -61,6 +61,7 @@ describe('addConnector lib function', () => {
|
|||
last_sync_error: null,
|
||||
last_sync_status: null,
|
||||
last_synced: null,
|
||||
name: 'index_name',
|
||||
scheduling: { enabled: false, interval: '0 0 0 * * ?' },
|
||||
service_type: null,
|
||||
status: ConnectorStatus.CREATED,
|
||||
|
@ -139,6 +140,7 @@ describe('addConnector lib function', () => {
|
|||
last_sync_error: null,
|
||||
last_sync_status: null,
|
||||
last_synced: null,
|
||||
name: 'index_name',
|
||||
scheduling: { enabled: false, interval: '0 0 0 * * ?' },
|
||||
service_type: null,
|
||||
status: ConnectorStatus.CREATED,
|
||||
|
@ -160,21 +162,22 @@ describe('addConnector lib function', () => {
|
|||
(fetchConnectorByIndexName as jest.Mock).mockImplementation(() => false);
|
||||
await expect(
|
||||
addConnector(mockClient as unknown as IScopedClusterClient, {
|
||||
index_name: 'index_name',
|
||||
index_name: 'search-index_name',
|
||||
language: 'en',
|
||||
})
|
||||
).resolves.toEqual({ id: 'fakeId', index_name: 'index_name' });
|
||||
).resolves.toEqual({ id: 'fakeId', index_name: 'search-index_name' });
|
||||
expect(setupConnectorsIndices as jest.Mock).toHaveBeenCalledWith(mockClient.asCurrentUser);
|
||||
expect(mockClient.asCurrentUser.index).toHaveBeenCalledWith({
|
||||
document: {
|
||||
api_key_id: null,
|
||||
configuration: {},
|
||||
index_name: 'index_name',
|
||||
index_name: 'search-index_name',
|
||||
language: 'en',
|
||||
last_seen: null,
|
||||
last_sync_error: null,
|
||||
last_sync_status: null,
|
||||
last_synced: null,
|
||||
name: 'index_name',
|
||||
scheduling: { enabled: false, interval: '0 0 0 * * ?' },
|
||||
service_type: null,
|
||||
status: ConnectorStatus.CREATED,
|
||||
|
@ -182,7 +185,9 @@ describe('addConnector lib function', () => {
|
|||
},
|
||||
index: CONNECTORS_INDEX,
|
||||
});
|
||||
expect(mockClient.asCurrentUser.indices.create).toHaveBeenCalledWith({ index: 'index_name' });
|
||||
expect(mockClient.asCurrentUser.indices.create).toHaveBeenCalledWith({
|
||||
index: 'search-index_name',
|
||||
});
|
||||
});
|
||||
it('should not create index if status code is not 404', async () => {
|
||||
mockClient.asCurrentUser.index.mockImplementationOnce(() => {
|
||||
|
|
|
@ -62,6 +62,7 @@ export const addConnector = async (
|
|||
last_sync_error: null,
|
||||
last_sync_status: null,
|
||||
last_synced: null,
|
||||
name: input.index_name.startsWith('search-') ? input.index_name.substring(7) : input.index_name,
|
||||
scheduling: { enabled: false, interval: '0 0 0 * * ?' },
|
||||
service_type: null,
|
||||
status: ConnectorStatus.CREATED,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue