[Enterprise Search] Allow user to customize name and description of connector indices (#141151)

This commit is contained in:
Byron Hulcher 2022-09-20 16:42:03 -04:00 committed by GitHub
parent b4467665cb
commit 348b7158f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 697 additions and 168 deletions

View file

@ -41,6 +41,7 @@ export interface IngestPipelineParams {
export interface Connector {
api_key_id: string | null;
configuration: ConnectorConfiguration;
description: string | null;
error: string | null;
id: string;
index_name: string;

View file

@ -27,6 +27,7 @@ export const indices: ElasticsearchIndexWithIngestion[] = [
connector: {
api_key_id: null,
configuration: { foo: { label: 'bar', value: 'barbar' } },
description: null,
error: null,
id: '2',
index_name: 'connector',
@ -76,6 +77,7 @@ export const indices: ElasticsearchIndexWithIngestion[] = [
connector: {
api_key_id: null,
configuration: { foo: { label: 'bar', value: 'barbar' } },
description: null,
error: null,
id: '4',
index_name: 'connector-crawler',

View file

@ -36,6 +36,7 @@ export const connectorIndex: ConnectorViewIndex = {
connector: {
api_key_id: null,
configuration: { foo: { label: 'bar', value: 'barbar' } },
description: null,
error: null,
id: '2',
index_name: 'connector',
@ -91,6 +92,7 @@ export const connectorCrawlerIndex: CrawlerViewIndex = {
connector: {
api_key_id: null,
configuration: { foo: { label: 'bar', value: 'barbar' } },
description: null,
error: null,
id: '4',
index_name: 'connector-crawler',

View file

@ -0,0 +1,42 @@
/*
* 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 { Connector } from '../../../../../common/types/connectors';
import { createApiLogic } from '../../../shared/api_logic/create_api_logic';
import { HttpLogic } from '../../../shared/http';
export type PutConnectorNameAndDescriptionArgs = Partial<
Pick<Connector, 'name' | 'description'>
> & {
connectorId: string;
indexName: string;
};
export type PutConnectorNameAndDescriptionResponse = Partial<
Pick<Connector, 'name' | 'description'>
> & {
indexName: string;
};
export const putConnectorNameAndDescription = async ({
connectorId,
description,
indexName,
name,
}: PutConnectorNameAndDescriptionArgs) => {
const route = `/internal/enterprise_search/connectors/${connectorId}/name_and_description`;
await HttpLogic.values.http.put(route, {
body: JSON.stringify({ description, name }),
});
return { description, indexName, name };
};
export const ConnectorNameAndDescriptionApiLogic = createApiLogic(
['content', 'connector_name_and_description_api_logic'],
putConnectorNameAndDescription
);

View file

@ -15,13 +15,7 @@ import { i18n } from '@kbn/i18n';
import { OverviewLogic } from './overview.logic';
interface TotalStatsProps {
additionalItems?: EuiStatProps[];
ingestionType: string;
lastUpdated?: string;
}
export const TotalStats: React.FC<TotalStatsProps> = ({ ingestionType, additionalItems = [] }) => {
export const ApiTotalStats: React.FC = () => {
const { indexData, isError, isLoading } = useValues(OverviewLogic);
const documentCount = indexData?.count ?? 0;
const hideStats = isLoading || isError;
@ -35,7 +29,12 @@ export const TotalStats: React.FC<TotalStatsProps> = ({ ingestionType, additiona
}
),
isLoading: hideStats,
title: ingestionType,
title: i18n.translate(
'xpack.enterpriseSearch.content.searchIndex.totalStats.apiIngestionMethodLabel',
{
defaultMessage: 'API',
}
),
},
{
description: i18n.translate(
@ -47,7 +46,6 @@ export const TotalStats: React.FC<TotalStatsProps> = ({ ingestionType, additiona
isLoading: hideStats,
title: documentCount,
},
...additionalItems,
];
return (

View file

@ -43,6 +43,7 @@ import { SearchIndexTabId } from '../search_index';
import { ApiKeyConfig } from './api_key_configuration';
import { ConnectorConfigurationConfig } from './connector_configuration_config';
import { ConnectorNameAndDescription } from './connector_name_and_description/connector_name_and_description';
import { NativeConnectorConfiguration } from './native_connector_configuration/native_connector_configuration';
export const ConnectorConfiguration: React.FC = () => {
@ -84,6 +85,17 @@ export const ConnectorConfiguration: React.FC = () => {
),
titleSize: 'xs',
},
{
children: <ConnectorNameAndDescription />,
status: indexData.connector.description ? 'complete' : 'incomplete',
title: i18n.translate(
'xpack.enterpriseSearch.content.indices.configurationConnector.steps.nameAndDescriptionTitle',
{
defaultMessage: 'Name and description',
}
),
titleSize: 'xs',
},
{
children: (
<>

View file

@ -0,0 +1,84 @@
/*
* 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,
EuiDescriptionList,
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { NAME_LABEL, DESCRIPTION_LABEL, EDIT_BUTTON_LABEL } from '../../../../../shared/constants';
import { isConnectorIndex } from '../../../../utils/indices';
import { IndexViewLogic } from '../../index_view_logic';
import { ConnectorNameAndDescriptionForm } from './connector_name_and_description_form';
import { ConnectorNameAndDescriptionLogic } from './connector_name_and_description_logic';
export const ConnectorNameAndDescription: React.FC = () => {
const { index: indexData } = useValues(IndexViewLogic);
const {
isEditing,
nameAndDescription: { name, description },
} = useValues(ConnectorNameAndDescriptionLogic);
const { setIsEditing } = useActions(ConnectorNameAndDescriptionLogic);
if (!isConnectorIndex(indexData)) {
return <></>;
}
return (
<EuiFlexGroup direction="column">
<EuiFlexItem>
<EuiText size="s">
{i18n.translate(
'xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionForm.description',
{
defaultMessage:
'By naming and describing this connector your colleagues and wider team will know what this connector is meant for.',
}
)}
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
{isEditing ? (
<ConnectorNameAndDescriptionForm />
) : (
<>
<EuiDescriptionList
listItems={[
{
description: name ?? '--',
title: NAME_LABEL,
},
{
description: description ?? '--',
title: DESCRIPTION_LABEL,
},
]}
/>
<EuiSpacer />
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiButton onClick={() => setIsEditing(!isEditing)}>{EDIT_BUTTON_LABEL}</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</>
)}
</EuiFlexItem>
</EuiFlexGroup>
);
};

View file

@ -0,0 +1,97 @@
/*
* 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,
EuiButtonEmpty,
EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
EuiForm,
EuiFormRow,
EuiTextArea,
} from '@elastic/eui';
import { Status } from '../../../../../../../common/types/api';
import {
NAME_LABEL,
DESCRIPTION_LABEL,
SAVE_BUTTON_LABEL,
CANCEL_BUTTON_LABEL,
} from '../../../../../shared/constants';
import { ConnectorNameAndDescriptionApiLogic } from '../../../../api/connector/update_connector_name_and_description_api_logic';
import { isConnectorIndex } from '../../../../utils/indices';
import { IndexViewLogic } from '../../index_view_logic';
import { ConnectorNameAndDescriptionLogic } from './connector_name_and_description_logic';
export const ConnectorNameAndDescriptionForm: React.FC = () => {
const { index: indexData } = useValues(IndexViewLogic);
const { status } = useValues(ConnectorNameAndDescriptionApiLogic);
const {
localNameAndDescription: { name, description },
} = useValues(ConnectorNameAndDescriptionLogic);
const { saveNameAndDescription, setIsEditing, updateLocalNameAndDescription } = useActions(
ConnectorNameAndDescriptionLogic
);
if (!isConnectorIndex(indexData)) {
return <></>;
}
return (
<EuiForm
onSubmit={(event) => {
event.preventDefault();
saveNameAndDescription();
}}
component="form"
>
<EuiFormRow label={NAME_LABEL}>
<EuiFieldText
required
value={name ?? ''}
onChange={(event) => {
updateLocalNameAndDescription({ name: event.target.value });
}}
/>
</EuiFormRow>
<EuiFormRow label={DESCRIPTION_LABEL}>
<EuiTextArea
placeholder={'Optional'}
value={description || ''}
onChange={(event) => {
updateLocalNameAndDescription({ description: event.target.value });
}}
/>
</EuiFormRow>
<EuiFormRow>
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiButton type="submit" isLoading={status === Status.LOADING}>
{SAVE_BUTTON_LABEL}
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
isDisabled={status === Status.LOADING}
onClick={() => {
setIsEditing(false);
}}
>
{CANCEL_BUTTON_LABEL}
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFormRow>
</EuiForm>
);
};

View file

@ -0,0 +1,136 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { Connector } from '../../../../../../../common/types/connectors';
import { Actions } from '../../../../../shared/api_logic/create_api_logic';
import {
flashAPIErrors,
flashSuccessToast,
clearFlashMessages,
} from '../../../../../shared/flash_messages';
import {
ConnectorNameAndDescriptionApiLogic,
PutConnectorNameAndDescriptionArgs,
PutConnectorNameAndDescriptionResponse,
} from '../../../../api/connector/update_connector_name_and_description_api_logic';
import {
FetchIndexApiLogic,
FetchIndexApiParams,
FetchIndexApiResponse,
} from '../../../../api/index/fetch_index_api_logic';
import { isConnectorIndex } from '../../../../utils/indices';
type NameAndDescription = Partial<Pick<Connector, 'name' | 'description'>>;
type ConnectorNameAndDescriptionActions = Pick<
Actions<PutConnectorNameAndDescriptionArgs, PutConnectorNameAndDescriptionResponse>,
'apiError' | 'apiSuccess' | 'makeRequest'
> & {
fetchIndexApiSuccess: Actions<FetchIndexApiParams, FetchIndexApiResponse>['apiSuccess'];
saveNameAndDescription: () => void;
setIsEditing(isEditing: boolean): { isEditing: boolean };
setLocalNameAndDescription(nameAndDescription: NameAndDescription): NameAndDescription;
setNameAndDescription(nameAndDescription: NameAndDescription): NameAndDescription;
updateLocalNameAndDescription(nameAndDescription: NameAndDescription): NameAndDescription;
};
interface ConnectorNameAndDescriptionValues {
index: FetchIndexApiResponse;
isEditing: boolean;
localNameAndDescription: NameAndDescription;
nameAndDescription: NameAndDescription;
}
export const ConnectorNameAndDescriptionLogic = kea<
MakeLogicType<ConnectorNameAndDescriptionValues, ConnectorNameAndDescriptionActions>
>({
actions: {
saveNameAndDescription: true,
setIsEditing: (isEditing: boolean) => ({
isEditing,
}),
setLocalNameAndDescription: (nameAndDescription) => nameAndDescription,
setNameAndDescription: (nameAndDescription) => nameAndDescription,
updateLocalNameAndDescription: (nameAndDescription) => nameAndDescription,
},
connect: {
actions: [
ConnectorNameAndDescriptionApiLogic,
['apiError', 'apiSuccess', 'makeRequest'],
FetchIndexApiLogic,
['apiSuccess as fetchIndexApiSuccess'],
],
values: [FetchIndexApiLogic, ['data as index']],
},
events: ({ actions, values }) => ({
afterMount: () =>
actions.setNameAndDescription(isConnectorIndex(values.index) ? values.index.connector : {}),
}),
listeners: ({ actions, values }) => ({
apiError: (error) => flashAPIErrors(error),
apiSuccess: ({ indexName }) => {
flashSuccessToast(
i18n.translate(
'xpack.enterpriseSearch.content.indices.configurationConnector.configuration.successToast.title',
{ defaultMessage: 'Configuration successfully updated' }
)
);
FetchIndexApiLogic.actions.makeRequest({ indexName });
},
fetchIndexApiSuccess: (index) => {
if (!values.isEditing && isConnectorIndex(index)) {
actions.setNameAndDescription(index.connector);
}
},
makeRequest: () => clearFlashMessages(),
saveNameAndDescription: () => {
if (isConnectorIndex(values.index)) {
actions.makeRequest({
connectorId: values.index.connector.id,
indexName: values.index.connector.index_name,
...values.localNameAndDescription,
});
}
},
setIsEditing: (isEditing) => {
if (isEditing) {
actions.setLocalNameAndDescription(values.nameAndDescription);
}
},
}),
path: ['enterprise_search', 'content', 'connector_name_and_description'],
reducers: () => ({
isEditing: [
false,
{
apiSuccess: () => false,
setIsEditing: (_, { isEditing }) => isEditing,
},
],
localNameAndDescription: [
{},
{
setLocalNameAndDescription: (_, nameAndDescription) => nameAndDescription,
updateLocalNameAndDescription: (localNameAndDescription, nameAndDescription) => ({
...localNameAndDescription,
...nameAndDescription,
}),
},
],
nameAndDescription: [
{},
{
apiSuccess: (_, { description, name }) => ({ description, name }),
setNameAndDescription: (_, nameAndDescription) => nameAndDescription,
},
],
}),
});

View file

@ -28,8 +28,6 @@ import {
import { IndexViewLogic } from '../index_view_logic';
import { SearchIndexTabId } from '../search_index';
import { NATIVE_CONNECTORS } from './constants';
const StatusPanel: React.FC<{ ingestionStatus: IngestionStatus }> = ({ ingestionStatus }) => (
<EuiPanel color={ingestionStatusToColor(ingestionStatus)} hasShadow={false} paddingSize="l">
<EuiStat
@ -47,36 +45,15 @@ export const ConnectorOverviewPanels: React.FC = () => {
return isConnectorIndex(index) ? (
<EuiFlexGroup>
<EuiFlexItem grow={1}>
<EuiPanel color="subdued" hasShadow={false} paddingSize="l">
<EuiPanel color="primary" hasShadow={false} paddingSize="l">
<EuiStat
description={i18n.translate(
'xpack.enterpriseSearch.connector.connectorNamePanel.title',
'xpack.enterpriseSearch.content.searchIndex.totalStats.documentCountCardLabel',
{
defaultMessage: 'Name',
defaultMessage: 'Document count',
}
)}
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={
NATIVE_CONNECTORS.find(
(connector) => connector.serviceType === index.connector.service_type
)?.name ??
index.connector.service_type ??
i18n.translate('xpack.enterpriseSearch.connector.connectorTypePanel.unknown.label', {
defaultMessage: 'Unknown',
})
}
title={index.count}
/>
</EuiPanel>
</EuiFlexItem>

View file

@ -1,83 +0,0 @@
/*
* 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 { useValues } from 'kea';
import {
EuiButton,
EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
EuiForm,
EuiFormRow,
EuiText,
EuiTextArea,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { isConnectorIndex } from '../../../../utils/indices';
import { IndexViewLogic } from '../../index_view_logic';
import { NativeConnector } from '../types';
interface ConnectorNameAndDescriptionProps {
nativeConnector: NativeConnector;
}
export const ConnectorNameAndDescription: React.FC<ConnectorNameAndDescriptionProps> = ({
nativeConnector,
}) => {
const { index: indexData } = useValues(IndexViewLogic);
const { name } = nativeConnector;
return (
<EuiForm component="form">
<EuiFlexGroup direction="column">
<EuiFlexItem>
<EuiText size="s">
{i18n.translate(
'xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionForm.description',
{
defaultMessage:
'By naming and describing this connector your colleagues and wider team will know what this connector is meant for.',
}
)}
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow label="Name">
<EuiFieldText
value={isConnectorIndex(indexData) ? indexData.connector.name : ''}
placeholder={`${name} connector`}
disabled
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow label="Description">
<EuiTextArea placeholder={'Optional'} disabled />
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiButton disabled type="submit">
{i18n.translate(
'xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionForm.submitButtonLabel',
{
defaultMessage: 'Save name and description',
}
)}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiForm>
);
};

View file

@ -28,6 +28,7 @@ import { NATIVE_CONNECTOR_ICONS } from '../../../../../../assets/source_icons/na
import { hasConfiguredConfiguration } from '../../../../utils/has_configured_configuration';
import { isConnectorIndex } from '../../../../utils/indices';
import { IndexViewLogic } from '../../index_view_logic';
import { ConnectorNameAndDescription } from '../connector_name_and_description/connector_name_and_description';
import { NATIVE_CONNECTORS } from '../constants';
import { NativeConnectorAdvancedConfiguration } from './native_connector_advanced_configuration';
@ -49,10 +50,11 @@ export const NativeConnectorConfiguration: React.FC = () => {
return <></>;
}
const hasDescription = !!indexData.connector.description;
const hasConfigured = hasConfiguredConfiguration(indexData.connector.configuration);
const hasConfiguredAdvanced =
indexData.connector.last_synced || indexData.connector.scheduling.enabled;
const hasResearched = hasConfigured || hasConfiguredAdvanced;
const hasResearched = hasDescription || hasConfigured || hasConfiguredAdvanced;
const icon = NATIVE_CONNECTOR_ICONS[nativeConnector.serviceType];
return (
@ -87,18 +89,17 @@ export const NativeConnectorConfiguration: React.FC = () => {
),
titleSize: 'xs',
},
/* Commenting this out for a future PR to implement fully */
// {
// children: <ConnectorNameAndDescription nativeConnector={nativeConnector} />,
// status: hasName ? 'complete' : 'incomplete',
// title: i18n.translate(
// 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.nameAndDescriptionTitle',
// {
// defaultMessage: 'Name and description',
// }
// ),
// titleSize: 'xs',
// },
{
children: <ConnectorNameAndDescription />,
status: hasDescription ? 'complete' : 'incomplete',
title: i18n.translate(
'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.steps.nameAndDescriptionTitle',
{
defaultMessage: 'Name and description',
}
),
titleSize: 'xs',
},
{
children: (
<NativeConnectorConfigurationConfig nativeConnector={nativeConnector} />

View file

@ -0,0 +1,99 @@
/*
* 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 { useValues } from 'kea';
import {
EuiStatProps,
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiStat,
EuiSpacer,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { isConnectorIndex } from '../../utils/indices';
import { ConnectorOverviewPanels } from './connector/connector_overview_panels';
import { NATIVE_CONNECTORS } from './connector/constants';
import { NameAndDescriptionStats } from './name_and_description_stats';
import { OverviewLogic } from './overview.logic';
export const ConnectorTotalStats: React.FC = () => {
const { indexData, isError, isLoading } = useValues(OverviewLogic);
const hideStats = isLoading || isError;
if (!isConnectorIndex(indexData)) {
return <></>;
}
const stats: EuiStatProps[] = [
{
description: i18n.translate(
'xpack.enterpriseSearch.content.searchIndex.totalStats.ingestionTypeCardLabel',
{
defaultMessage: 'Ingestion type',
}
),
isLoading: hideStats,
title: i18n.translate(
'xpack.enterpriseSearch.content.searchIndex.totalStats.connectorIngestionMethodLabel',
{
defaultMessage: 'Connector',
}
),
},
{
description: i18n.translate('xpack.enterpriseSearch.connector.connectorTypePanel.title', {
defaultMessage: 'Connector type',
}),
title:
NATIVE_CONNECTORS.find(
(connector) => connector.serviceType === indexData.connector.service_type
)?.name ??
indexData.connector.service_type ??
i18n.translate('xpack.enterpriseSearch.connector.connectorTypePanel.unknown.label', {
defaultMessage: 'Unknown',
}),
},
{
description: i18n.translate(
'xpack.enterpriseSearch.content.searchIndex.totalStats.languageLabel',
{
defaultMessage: 'Language analyzer',
}
),
isLoading: hideStats,
title:
indexData.connector.language ??
i18n.translate('xpack.enterpriseSearch.content.searchIndex.totalStats.noneLabel', {
defaultMessage: 'None',
}),
},
];
return (
<>
<NameAndDescriptionStats />
<EuiSpacer />
<EuiFlexGroup direction="row">
{stats.map((item, index) => (
<EuiFlexItem key={index}>
<EuiPanel color="primary" hasShadow={false} paddingSize="l">
<EuiStat {...item} />
</EuiPanel>
</EuiFlexItem>
))}
</EuiFlexGroup>
<EuiSpacer />
<ConnectorOverviewPanels />
</>
);
};

View file

@ -9,15 +9,34 @@ import React from 'react';
import { useValues } from 'kea';
import { EuiStatProps, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { CrawlerLogic } from './crawler/crawler_logic';
import { TotalStats } from './total_stats';
import { OverviewLogic } from './overview.logic';
export const CrawlerTotalStats: React.FC = () => {
const { domains, dataLoading } = useValues(CrawlerLogic);
const { indexData, isError, isLoading } = useValues(OverviewLogic);
const documentCount = indexData?.count ?? 0;
const hideStats = isLoading || isError;
const additionalItems = [
const stats: EuiStatProps[] = [
{
description: i18n.translate(
'xpack.enterpriseSearch.content.searchIndex.totalStats.ingestionTypeCardLabel',
{
defaultMessage: 'Ingestion type',
}
),
isLoading: hideStats,
title: i18n.translate(
'xpack.enterpriseSearch.content.searchIndex.totalStats.crawlerIngestionMethodLabel',
{
defaultMessage: 'Crawler',
}
),
},
{
description: i18n.translate(
'xpack.enterpriseSearch.content.searchIndex.totalStats.domainCountCardLabel',
@ -25,20 +44,30 @@ export const CrawlerTotalStats: React.FC = () => {
defaultMessage: 'Domain count',
}
),
isLoading: dataLoading,
isLoading: dataLoading || hideStats,
title: domains.length,
},
{
description: i18n.translate(
'xpack.enterpriseSearch.content.searchIndex.totalStats.documentCountCardLabel',
{
defaultMessage: 'Document count',
}
),
isLoading: hideStats,
title: documentCount,
},
];
return (
<TotalStats
ingestionType={i18n.translate(
'xpack.enterpriseSearch.content.searchIndex.totalStats.crawlerIngestionMethodLabel',
{
defaultMessage: 'Crawler',
}
)}
additionalItems={additionalItems}
/>
<EuiFlexGroup direction="row">
{stats.map((item, index) => (
<EuiFlexItem key={index}>
<EuiPanel color={index === 0 ? 'primary' : 'subdued'} hasShadow={false} paddingSize="l">
<EuiStat {...item} />
</EuiPanel>
</EuiFlexItem>
))}
</EuiFlexGroup>
);
};

View file

@ -0,0 +1,74 @@
/*
* 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 { useValues } from 'kea';
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat, EuiStatProps, EuiText } from '@elastic/eui';
import { DESCRIPTION_LABEL, NAME_LABEL } from '../../../shared/constants';
import { generateEncodedPath } from '../../../shared/encode_path_params';
import { EuiLinkTo } from '../../../shared/react_router_helpers';
import { SEARCH_INDEX_TAB_PATH } from '../../routes';
import { isConnectorIndex } from '../../utils/indices';
import { IndexNameLogic } from './index_name_logic';
import { OverviewLogic } from './overview.logic';
import { SearchIndexTabId } from './search_index';
const EditDescription: React.FC<{ label: string; indexName: string }> = ({ label, indexName }) => (
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>{label}</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiLinkTo
to={generateEncodedPath(SEARCH_INDEX_TAB_PATH, {
indexName,
tabId: SearchIndexTabId.CONFIGURATION,
})}
>
Edit
</EuiLinkTo>
</EuiFlexItem>
</EuiFlexGroup>
);
export const NameAndDescriptionStats: React.FC = () => {
const { indexName } = useValues(IndexNameLogic);
const { indexData, isError, isLoading } = useValues(OverviewLogic);
const hideStats = isLoading || isError;
if (!isConnectorIndex(indexData)) {
return <></>;
}
const stats: EuiStatProps[] = [
{
description: <EditDescription label={NAME_LABEL} indexName={indexName} />,
isLoading: hideStats,
title: indexData.connector.name,
},
{
description: <EditDescription label={DESCRIPTION_LABEL} indexName={indexName} />,
isLoading: hideStats,
title: <EuiText size="s">{indexData.connector.description || ''}</EuiText>,
titleElement: 'p',
},
];
return (
<EuiFlexGroup direction="row">
{stats.map((item, index) => (
<EuiFlexItem key={index}>
<EuiPanel color={'subdued'} hasShadow={false} paddingSize="l">
<EuiStat {...item} />
</EuiPanel>
</EuiFlexItem>
))}
</EuiFlexGroup>
);
};

View file

@ -15,14 +15,14 @@ import { i18n } from '@kbn/i18n';
import { isApiIndex, isConnectorIndex, isCrawlerIndex } from '../../utils/indices';
import { ConnectorOverviewPanels } from './connector/connector_overview_panels';
import { ApiTotalStats } from './api_total_stats';
import { ConnectorTotalStats } from './connector_total_stats';
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';
import { GenerateApiKeyPanel } from './generate_api_key_panel';
import { OverviewLogic } from './overview.logic';
import { SyncJobs } from './sync_jobs';
import { TotalStats } from './total_stats';
export const SearchIndexOverview: React.FC = () => {
const { indexData } = useValues(OverviewLogic);
@ -50,24 +50,10 @@ export const SearchIndexOverview: React.FC = () => {
)}
{isCrawlerIndex(indexData) ? (
<CrawlerTotalStats />
) : isConnectorIndex(indexData) ? (
<ConnectorTotalStats />
) : (
<TotalStats
ingestionType={
isConnectorIndex(indexData)
? i18n.translate(
'xpack.enterpriseSearch.content.searchIndex.totalStats.connectorIngestionMethodLabel',
{
defaultMessage: 'Connector',
}
)
: i18n.translate(
'xpack.enterpriseSearch.content.searchIndex.totalStats.apiIngestionMethodLabel',
{
defaultMessage: 'API',
}
)
}
/>
<ApiTotalStats />
)}
{isApiIndex(indexData) && (
<>
@ -84,8 +70,6 @@ export const SearchIndexOverview: React.FC = () => {
)}
{isConnectorIndex(indexData) && (
<>
<EuiSpacer />
<ConnectorOverviewPanels />
<EuiSpacer />
<SyncJobs />
</>

View file

@ -33,3 +33,11 @@ export const TECHNICAL_PREVIEW_LABEL = i18n.translate(
defaultMessage: 'Technical Preview', // title case specifically requested
}
);
export const NAME_LABEL = i18n.translate('xpack.enterpriseSearch.nameLabel', {
defaultMessage: 'Name',
});
export const DESCRIPTION_LABEL = i18n.translate('xpack.enterpriseSearch.descriptionLabel', {
defaultMessage: 'Description',
});

View file

@ -86,6 +86,7 @@ describe('addConnector lib function', () => {
document: {
api_key_id: null,
configuration: {},
description: null,
error: null,
index_name: 'index_name',
is_native: false,
@ -216,6 +217,7 @@ describe('addConnector lib function', () => {
document: {
api_key_id: null,
configuration: {},
description: null,
error: null,
index_name: 'index_name',
is_native: true,
@ -268,6 +270,7 @@ describe('addConnector lib function', () => {
document: {
api_key_id: null,
configuration: {},
description: null,
error: null,
index_name: 'search-index_name',
is_native: false,

View file

@ -87,6 +87,7 @@ export const addConnector = async (
const document: ConnectorDocument = {
api_key_id: null,
configuration: {},
description: null,
error: null,
index_name: input.index_name,
is_native: input.is_native,

View file

@ -0,0 +1,40 @@
/*
* 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 { IScopedClusterClient } from '@kbn/core/server';
import { i18n } from '@kbn/i18n';
import { CONNECTORS_INDEX } from '../..';
import { Connector, ConnectorDocument } from '../../../common/types/connectors';
export const updateConnectorNameAndDescription = async (
client: IScopedClusterClient,
connectorId: string,
connectorUpdates: Partial<Pick<Connector, 'name' | 'description'>>
) => {
const connectorResult = await client.asCurrentUser.get<ConnectorDocument>({
id: connectorId,
index: CONNECTORS_INDEX,
});
const connector = connectorResult._source;
if (connector) {
const result = await client.asCurrentUser.index<ConnectorDocument>({
document: { ...connector, ...connectorUpdates },
id: connectorId,
index: CONNECTORS_INDEX,
});
await client.asCurrentUser.indices.refresh({ index: CONNECTORS_INDEX });
return result;
} else {
throw new Error(
i18n.translate('xpack.enterpriseSearch.server.connectors.serviceType.error', {
defaultMessage: 'Could not find document',
})
);
}
};

View file

@ -16,6 +16,7 @@ import { addConnector } from '../../lib/connectors/add_connector';
import { fetchSyncJobsByConnectorId } from '../../lib/connectors/fetch_sync_jobs';
import { startConnectorSync } from '../../lib/connectors/start_sync';
import { updateConnectorConfiguration } from '../../lib/connectors/update_connector_configuration';
import { updateConnectorNameAndDescription } from '../../lib/connectors/update_connector_name_and_description';
import { updateConnectorScheduling } from '../../lib/connectors/update_connector_scheduling';
import { updateConnectorServiceType } from '../../lib/connectors/update_connector_service_type';
import { updateConnectorStatus } from '../../lib/connectors/update_connector_status';
@ -244,4 +245,28 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) {
return response.ok({ body: result });
})
);
router.put(
{
path: '/internal/enterprise_search/connectors/{connectorId}/name_and_description',
validate: {
params: schema.object({
connectorId: schema.string(),
}),
body: schema.object({
name: schema.maybe(schema.string()),
description: schema.maybe(schema.string()),
}),
},
},
elasticsearchErrorHandler(log, async (context, request, response) => {
const { client } = (await context.core).elasticsearch;
const { name, description } = request.body;
const result = await updateConnectorNameAndDescription(client, request.params.connectorId, {
description,
name,
});
return response.ok({ body: result });
})
);
}

View file

@ -11006,7 +11006,6 @@
"xpack.enterpriseSearch.appSearch.tokens.search.name": "Clé de recherche publique",
"xpack.enterpriseSearch.appSearch.tokens.update": "La clé d'API \"{name}\" a été mise à jour",
"xpack.enterpriseSearch.automaticCrawlSchedule.title": "Planification automatisée de lindexation",
"xpack.enterpriseSearch.connector.connectorNamePanel.title": "Nom",
"xpack.enterpriseSearch.connector.connectorTypePanel.title": "Type de connecteur",
"xpack.enterpriseSearch.connector.connectorTypePanel.unknown.label": "Inconnu",
"xpack.enterpriseSearch.connector.ingestionStatus.title": "Statut de l'ingestion",

View file

@ -10992,7 +10992,6 @@
"xpack.enterpriseSearch.appSearch.tokens.search.name": "公開検索キー",
"xpack.enterpriseSearch.appSearch.tokens.update": "APIキー'{name}'が更新されました",
"xpack.enterpriseSearch.automaticCrawlSchedule.title": "自動クローリングスケジュール",
"xpack.enterpriseSearch.connector.connectorNamePanel.title": "名前",
"xpack.enterpriseSearch.connector.connectorTypePanel.title": "コネクタータイプ",
"xpack.enterpriseSearch.connector.connectorTypePanel.unknown.label": "不明",
"xpack.enterpriseSearch.connector.ingestionStatus.title": "インジェスチョンステータス",

View file

@ -11008,7 +11008,6 @@
"xpack.enterpriseSearch.appSearch.tokens.search.name": "公有搜索密钥",
"xpack.enterpriseSearch.appSearch.tokens.update": "API 密钥“{name}”已更新",
"xpack.enterpriseSearch.automaticCrawlSchedule.title": "自动化爬网计划",
"xpack.enterpriseSearch.connector.connectorNamePanel.title": "名称",
"xpack.enterpriseSearch.connector.connectorTypePanel.title": "连接器类型",
"xpack.enterpriseSearch.connector.connectorTypePanel.unknown.label": "未知",
"xpack.enterpriseSearch.connector.ingestionStatus.title": "采集状态",