[Enterprise Search] Name and description flyout for connectors (#143827)

This commit is contained in:
Byron Hulcher 2022-11-14 20:07:36 -05:00 committed by GitHub
parent 879b101669
commit 8974e826bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 219 additions and 84 deletions

View file

@ -191,7 +191,7 @@ export const indices: ElasticsearchIndexWithIngestion[] = [
count: 1,
crawler: {
id: '5',
index_name: 'crawler',
index_name: 'connector-crawler',
},
hidden: false,
name: 'crawler',

View file

@ -205,7 +205,7 @@ export const crawlerIndex: CrawlerViewIndex = {
count: 1,
crawler: {
id: '5',
index_name: 'crawler',
index_name: 'connector-crawler',
},
hidden: false,
ingestionMethod: IngestionMethod.CRAWLER,

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, useActions } from 'kea';
import {
EuiFlyout,
EuiFlyoutHeader,
EuiFlyoutBody,
EuiFormRow,
EuiText,
EuiSpacer,
EuiFlyoutFooter,
EuiButtonEmpty,
EuiButton,
EuiTitle,
EuiFlexGroup,
EuiFlexItem,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { Status } from '../../../../../../../common/types/api';
import { CANCEL_BUTTON_LABEL } from '../../../../../shared/constants';
import { ConnectorNameAndDescriptionApiLogic } from '../../../../api/connector/update_connector_name_and_description_api_logic';
import { ConnectorNameAndDescriptionFormContent } from './connector_name_and_description_form_content';
import { ConnectorNameAndDescriptionLogic } from './connector_name_and_description_logic';
export const ConnectorNameAndDescriptionFlyout: React.FC = () => {
const { status } = useValues(ConnectorNameAndDescriptionApiLogic);
const { isEditing } = useValues(ConnectorNameAndDescriptionLogic);
const { saveNameAndDescription, setIsEditing } = useActions(ConnectorNameAndDescriptionLogic);
if (!isEditing) return null;
return (
<EuiFlyout onClose={() => setIsEditing(false)} size="s">
<EuiFlyoutHeader>
<EuiTitle size="m">
<h3>
{i18n.translate(
'xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionFlyout.title',
{
defaultMessage: 'Describe this crawler',
}
)}
</h3>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<EuiFormRow>
<EuiText size="s">
{i18n.translate(
'xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionFlyout.description',
{
defaultMessage:
'By naming and describing this connector your colleagues and wider team will know what this connector is meant for.',
}
)}
</EuiText>
</EuiFormRow>
<EuiSpacer />
<ConnectorNameAndDescriptionFormContent />
</EuiFlyoutBody>
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty
onClick={() => setIsEditing(false)}
isLoading={status === Status.LOADING}
>
{CANCEL_BUTTON_LABEL}
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton isLoading={status === Status.LOADING} fill onClick={saveNameAndDescription}>
{i18n.translate(
'xpack.enterpriseSearch.content.indices.configurationConnector.nameAndDescriptionFlyout.saveButtonLabel',
{
defaultMessage: 'Save name and description',
}
)}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
</EuiFlyout>
);
};

View file

@ -12,40 +12,23 @@ 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 { 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 { ConnectorNameAndDescriptionFormContent } from './connector_name_and_description_form_content';
import { ConnectorNameAndDescriptionLogic } from './connector_name_and_description_logic';
export const ConnectorNameAndDescriptionForm: React.FC = () => {
const { index } = useValues(IndexViewLogic);
const { status } = useValues(ConnectorNameAndDescriptionApiLogic);
const {
localNameAndDescription: { name, description },
} = useValues(ConnectorNameAndDescriptionLogic);
const { saveNameAndDescription, setIsEditing, updateLocalNameAndDescription } = useActions(
ConnectorNameAndDescriptionLogic
);
if (!isConnectorIndex(index)) {
return <></>;
}
const { saveNameAndDescription, setIsEditing } = useActions(ConnectorNameAndDescriptionLogic);
return (
<EuiForm
@ -55,24 +38,7 @@ export const ConnectorNameAndDescriptionForm: React.FC = () => {
}}
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>
<ConnectorNameAndDescriptionFormContent />
<EuiFormRow>
<EuiFlexGroup>
<EuiFlexItem grow={false}>

View file

@ -0,0 +1,46 @@
/*
* 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 { EuiFieldText, EuiFormRow, EuiTextArea } from '@elastic/eui';
import { NAME_LABEL, DESCRIPTION_LABEL, OPTIONAL_LABEL } from '../../../../../shared/constants';
import { ConnectorNameAndDescriptionLogic } from './connector_name_and_description_logic';
export const ConnectorNameAndDescriptionFormContent: React.FC = () => {
const {
localNameAndDescription: { name, description },
} = useValues(ConnectorNameAndDescriptionLogic);
const { updateLocalNameAndDescription } = useActions(ConnectorNameAndDescriptionLogic);
return (
<>
<EuiFormRow label={NAME_LABEL}>
<EuiFieldText
required
value={name ?? ''}
onChange={(event) => {
updateLocalNameAndDescription({ name: event.target.value });
}}
/>
</EuiFormRow>
<EuiFormRow label={DESCRIPTION_LABEL}>
<EuiTextArea
placeholder={OPTIONAL_LABEL}
value={description || ''}
onChange={(event) => {
updateLocalNameAndDescription({ description: event.target.value });
}}
/>
</EuiFormRow>
</>
);
};

View file

@ -26,7 +26,7 @@ import {
CachedFetchIndexApiLogicActions,
} from '../../../../api/index/cached_fetch_index_api_logic';
import { FetchIndexApiResponse } from '../../../../api/index/fetch_index_api_logic';
import { isConnectorIndex } from '../../../../utils/indices';
import { isConnectorIndex, isCrawlerIndex } from '../../../../utils/indices';
type NameAndDescription = Partial<Pick<Connector, 'name' | 'description'>>;
@ -72,7 +72,9 @@ export const ConnectorNameAndDescriptionLogic = kea<
},
events: ({ actions, values }) => ({
afterMount: () =>
actions.setNameAndDescription(isConnectorIndex(values.index) ? values.index.connector : {}),
actions.setNameAndDescription(
isConnectorIndex(values.index) || isCrawlerIndex(values.index) ? values.index.connector : {}
),
}),
listeners: ({ actions, values }) => ({
apiError: (error) => flashAPIErrors(error),
@ -92,7 +94,7 @@ export const ConnectorNameAndDescriptionLogic = kea<
},
makeRequest: () => clearFlashMessages(),
saveNameAndDescription: () => {
if (isConnectorIndex(values.index)) {
if (isConnectorIndex(values.index) || isCrawlerIndex(values.index)) {
actions.makeRequest({
connectorId: values.index.connector.id,
indexName: values.index.connector.index_name,

View file

@ -9,10 +9,18 @@ import React from 'react';
import { useValues } from 'kea';
import { EuiStatProps, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat } from '@elastic/eui';
import {
EuiStatProps,
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiStat,
EuiSpacer,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { CrawlerLogic } from './crawler/crawler_logic';
import { NameAndDescriptionStats } from './name_and_description_stats';
import { OverviewLogic } from './overview.logic';
export const CrawlerTotalStats: React.FC = () => {
@ -60,14 +68,18 @@ export const CrawlerTotalStats: React.FC = () => {
];
return (
<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>
<>
<NameAndDescriptionStats />
<EuiSpacer />
<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

@ -5,55 +5,58 @@
* 2.0.
*/
import React from 'react';
import React, { MouseEventHandler } from 'react';
import { useValues } from 'kea';
import { useActions, useValues } from 'kea';
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat, EuiStatProps, EuiText } from '@elastic/eui';
import {
EuiButtonEmpty,
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 { isConnectorIndex, isCrawlerIndex } from '../../utils/indices';
import { IndexNameLogic } from './index_name_logic';
import { ConnectorNameAndDescriptionFlyout } from './connector/connector_name_and_description/connector_name_and_description_flyout';
import { ConnectorNameAndDescriptionLogic } from './connector/connector_name_and_description/connector_name_and_description_logic';
import { OverviewLogic } from './overview.logic';
import { SearchIndexTabId } from './search_index';
const EditDescription: React.FC<{ label: string; indexName: string }> = ({ label, indexName }) => (
const EditDescription: React.FC<{
label: string;
onClick: MouseEventHandler<HTMLButtonElement>;
}> = ({ label, onClick }) => (
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>{label}</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiLinkTo
to={generateEncodedPath(SEARCH_INDEX_TAB_PATH, {
indexName,
tabId: SearchIndexTabId.CONFIGURATION,
})}
>
Edit
</EuiLinkTo>
<EuiButtonEmpty onClick={onClick}>Edit</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
);
export const NameAndDescriptionStats: React.FC = () => {
const { indexName } = useValues(IndexNameLogic);
const { indexData, isError, isLoading } = useValues(OverviewLogic);
const hideStats = isLoading || isError;
const { setIsEditing: setIsFlyoutVisible } = useActions(ConnectorNameAndDescriptionLogic);
if (!isConnectorIndex(indexData)) {
if (!(isConnectorIndex(indexData) || isCrawlerIndex(indexData))) {
return <></>;
}
const stats: EuiStatProps[] = [
{
description: <EditDescription label={NAME_LABEL} indexName={indexName} />,
description: <EditDescription label={NAME_LABEL} onClick={() => setIsFlyoutVisible(true)} />,
isLoading: hideStats,
title: indexData.connector.name,
},
{
description: <EditDescription label={DESCRIPTION_LABEL} indexName={indexName} />,
description: (
<EditDescription label={DESCRIPTION_LABEL} onClick={() => setIsFlyoutVisible(true)} />
),
isLoading: hideStats,
title: <EuiText size="s">{indexData.connector.description || ''}</EuiText>,
titleElement: 'p',
@ -61,14 +64,17 @@ export const NameAndDescriptionStats: React.FC = () => {
];
return (
<EuiFlexGroup direction="row">
{stats.map((item, index) => (
<EuiFlexItem key={index}>
<EuiPanel color={'subdued'} hasShadow={false} paddingSize="l">
<EuiStat {...item} />
</EuiPanel>
</EuiFlexItem>
))}
</EuiFlexGroup>
<>
<EuiFlexGroup direction="row">
{stats.map((item, index) => (
<EuiFlexItem key={index}>
<EuiPanel color={'subdued'} hasShadow={false} paddingSize="l">
<EuiStat {...item} />
</EuiPanel>
</EuiFlexItem>
))}
</EuiFlexGroup>
<ConnectorNameAndDescriptionFlyout />
</>
);
};

View file

@ -37,3 +37,7 @@ export const NAME_LABEL = i18n.translate('xpack.enterpriseSearch.nameLabel', {
export const DESCRIPTION_LABEL = i18n.translate('xpack.enterpriseSearch.descriptionLabel', {
defaultMessage: 'Description',
});
export const OPTIONAL_LABEL = i18n.translate('xpack.enterpriseSearch.optionalLabel', {
defaultMessage: 'Optional',
});