mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Search serverless connector config (#169139)
## Summary This adds the ability to edit connector configuration in Serverless, and moves connector configuration to a shared package. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
ea6b251ee8
commit
1f1c4553db
94 changed files with 2540 additions and 2570 deletions
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { createContext, useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiCallOut,
|
||||
EuiDescriptionList,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { sortAndFilterConnectorConfiguration } from '../../utils/connector_configuration_utils';
|
||||
import { Connector, ConnectorConfigProperties, ConnectorStatus, FeatureName } from '../..';
|
||||
|
||||
import { ConnectorConfigurationForm } from './connector_configuration_form';
|
||||
|
||||
function entryToDisplaylistItem(entry: ConfigEntryView): { description: string; title: string } {
|
||||
return {
|
||||
description: entry.sensitive && !!entry.value ? '********' : String(entry.value) || '--',
|
||||
title: entry.label,
|
||||
};
|
||||
}
|
||||
|
||||
interface ConnectorConfigurationProps {
|
||||
connector: Connector;
|
||||
hasPlatinumLicense: boolean;
|
||||
isLoading: boolean;
|
||||
saveConfig: (configuration: Record<string, string | number | boolean | null>) => void;
|
||||
stackManagementLink?: string;
|
||||
subscriptionLink?: string;
|
||||
}
|
||||
|
||||
interface ConfigEntry extends ConnectorConfigProperties {
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface ConfigEntryView extends ConfigEntry {
|
||||
isValid: boolean;
|
||||
validationErrors: string[];
|
||||
}
|
||||
|
||||
export interface CategoryEntry {
|
||||
configEntries: ConfigEntryView[];
|
||||
key: string;
|
||||
label: string;
|
||||
order: number;
|
||||
}
|
||||
|
||||
export interface ConfigView {
|
||||
advancedConfigurations: ConfigEntryView[];
|
||||
categories: CategoryEntry[];
|
||||
unCategorizedItems: ConfigEntryView[];
|
||||
}
|
||||
|
||||
export const LicenseContext = createContext<{
|
||||
hasPlatinumLicense: boolean;
|
||||
stackManagementLink?: string;
|
||||
subscriptionLink?: string;
|
||||
}>({
|
||||
hasPlatinumLicense: false,
|
||||
subscriptionLink: undefined,
|
||||
stackManagementLink: undefined,
|
||||
});
|
||||
|
||||
export const ConnectorConfigurationComponent: React.FC<ConnectorConfigurationProps> = ({
|
||||
children,
|
||||
connector,
|
||||
hasPlatinumLicense,
|
||||
isLoading,
|
||||
saveConfig,
|
||||
subscriptionLink,
|
||||
stackManagementLink,
|
||||
}) => {
|
||||
const {
|
||||
configuration,
|
||||
error,
|
||||
status: connectorStatus,
|
||||
is_native: isNative,
|
||||
features,
|
||||
} = connector;
|
||||
const hasDocumentLevelSecurity = Boolean(
|
||||
features?.[FeatureName.DOCUMENT_LEVEL_SECURITY]?.enabled
|
||||
);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsEditing(false);
|
||||
}, [configuration]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
Object.keys(configuration || {}).length > 0 &&
|
||||
(connectorStatus === ConnectorStatus.CREATED ||
|
||||
connectorStatus === ConnectorStatus.NEEDS_CONFIGURATION)
|
||||
) {
|
||||
// Only start in edit mode if we haven't configured yet
|
||||
// Necessary to prevent a race condition between saving config and getting updated connector
|
||||
setIsEditing(true);
|
||||
}
|
||||
}, [configuration, connectorStatus]);
|
||||
|
||||
const configView = sortAndFilterConnectorConfiguration(configuration, isNative);
|
||||
|
||||
const uncategorizedDisplayList = configView.unCategorizedItems.map(entryToDisplaylistItem);
|
||||
|
||||
return (
|
||||
<LicenseContext.Provider value={{ hasPlatinumLicense, stackManagementLink, subscriptionLink }}>
|
||||
<EuiFlexGroup direction="column">
|
||||
{children && <EuiFlexItem>{children}</EuiFlexItem>}
|
||||
<EuiFlexItem>
|
||||
{isEditing ? (
|
||||
<ConnectorConfigurationForm
|
||||
cancelEditing={() => setIsEditing(false)}
|
||||
configuration={configuration}
|
||||
hasDocumentLevelSecurity={hasDocumentLevelSecurity}
|
||||
isLoading={isLoading}
|
||||
isNative={isNative}
|
||||
saveConfig={(config) => {
|
||||
saveConfig(config);
|
||||
setIsEditing(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
uncategorizedDisplayList.length > 0 && (
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiDescriptionList
|
||||
listItems={uncategorizedDisplayList}
|
||||
className="eui-textBreakWord"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{configView.categories.length > 0 &&
|
||||
configView.categories.map((category) => (
|
||||
<EuiFlexGroup direction="column" key={category.key}>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="s">
|
||||
<h3>{category.label}</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiDescriptionList
|
||||
listItems={category.configEntries.map(entryToDisplaylistItem)}
|
||||
className="eui-textBreakWord"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
))}
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="entSearchContent-connector-configuration-editConfiguration"
|
||||
data-telemetry-id="entSearchContent-connector-overview-configuration-editConfiguration"
|
||||
onClick={() => setIsEditing(!isEditing)}
|
||||
>
|
||||
{i18n.translate(
|
||||
'searchConnectors.configurationConnector.config.editButton.title',
|
||||
{
|
||||
defaultMessage: 'Edit configuration',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
{!!error && (
|
||||
<EuiFlexItem>
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
title={i18n.translate('searchConnectors.configurationConnector.config.error.title', {
|
||||
defaultMessage: 'Connector error',
|
||||
})}
|
||||
>
|
||||
<EuiText size="s">{error}</EuiText>
|
||||
</EuiCallOut>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</LicenseContext.Provider>
|
||||
);
|
||||
};
|
|
@ -1,13 +1,12 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
import React, { useContext, useState } from 'react';
|
||||
|
||||
import {
|
||||
EuiAccordion,
|
||||
|
@ -25,35 +24,39 @@ import {
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { DisplayType } from '@kbn/search-connectors';
|
||||
import { DisplayType } from '../..';
|
||||
|
||||
import { Status } from '../../../../../../common/types/api';
|
||||
import { LicensingLogic } from '../../../../shared/licensing';
|
||||
|
||||
import { ConnectorConfigurationApiLogic } from '../../../api/connector/update_connector_configuration_api_logic';
|
||||
|
||||
import { PlatinumLicensePopover } from '../../shared/platinum_license_popover/platinum_license_popover';
|
||||
|
||||
import { ConnectorConfigurationLogic, ConfigEntryView } from './connector_configuration_logic';
|
||||
import { DocumentLevelSecurityPanel } from './document_level_security/document_level_security_panel';
|
||||
import { ensureBooleanType, ensureStringType } from './utils/connector_configuration_utils';
|
||||
import { ConfigEntryView, LicenseContext } from './connector_configuration';
|
||||
import { DocumentLevelSecurityPanel } from './document_level_security_panel';
|
||||
import {
|
||||
ensureBooleanType,
|
||||
ensureCorrectTyping,
|
||||
ensureStringType,
|
||||
} from '../../utils/connector_configuration_utils';
|
||||
import { PlatinumLicensePopover } from './platinum_license_popover';
|
||||
|
||||
interface ConnectorConfigurationFieldProps {
|
||||
configEntry: ConfigEntryView;
|
||||
isLoading: boolean;
|
||||
setConfigValue: (value: number | string | boolean | null) => void;
|
||||
}
|
||||
|
||||
export const ConnectorConfigurationField: React.FC<ConnectorConfigurationFieldProps> = ({
|
||||
configEntry,
|
||||
isLoading,
|
||||
setConfigValue,
|
||||
}) => {
|
||||
const { status } = useValues(ConnectorConfigurationApiLogic);
|
||||
const { setLocalConfigEntry } = useActions(ConnectorConfigurationLogic);
|
||||
const { hasPlatinumLicense } = useValues(LicensingLogic);
|
||||
const { hasPlatinumLicense, stackManagementLink, subscriptionLink } = useContext(LicenseContext);
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
|
||||
const validateAndSetConfigValue = (value: number | string | boolean) => {
|
||||
setConfigValue(ensureCorrectTyping(configEntry.type, value));
|
||||
};
|
||||
|
||||
const {
|
||||
key,
|
||||
display,
|
||||
is_valid: isValid,
|
||||
isValid,
|
||||
label,
|
||||
options,
|
||||
required,
|
||||
|
@ -67,22 +70,22 @@ export const ConnectorConfigurationField: React.FC<ConnectorConfigurationFieldPr
|
|||
case DisplayType.DROPDOWN:
|
||||
return options.length > 3 ? (
|
||||
<EuiSelect
|
||||
disabled={status === Status.LOADING}
|
||||
disabled={isLoading}
|
||||
options={options.map((option) => ({ text: option.label, value: option.value }))}
|
||||
required={required}
|
||||
value={ensureStringType(value)}
|
||||
onChange={(event) => {
|
||||
setLocalConfigEntry({ ...configEntry, value: event.target.value });
|
||||
validateAndSetConfigValue(event.target.value);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<EuiRadioGroup
|
||||
disabled={status === Status.LOADING}
|
||||
disabled={isLoading}
|
||||
idSelected={ensureStringType(value)}
|
||||
name="radio group"
|
||||
options={options.map((option) => ({ id: option.value, label: option.label }))}
|
||||
onChange={(id) => {
|
||||
setLocalConfigEntry({ ...configEntry, value: id });
|
||||
validateAndSetConfigValue(id);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -90,12 +93,12 @@ export const ConnectorConfigurationField: React.FC<ConnectorConfigurationFieldPr
|
|||
case DisplayType.NUMERIC:
|
||||
return (
|
||||
<EuiFieldText
|
||||
disabled={status === Status.LOADING}
|
||||
disabled={isLoading}
|
||||
required={required}
|
||||
value={ensureStringType(value)}
|
||||
isInvalid={!isValid}
|
||||
onChange={(event) => {
|
||||
setLocalConfigEntry({ ...configEntry, value: event.target.value });
|
||||
validateAndSetConfigValue(event.target.value);
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
|
@ -104,12 +107,12 @@ export const ConnectorConfigurationField: React.FC<ConnectorConfigurationFieldPr
|
|||
case DisplayType.TEXTAREA:
|
||||
const textarea = (
|
||||
<EuiTextArea
|
||||
disabled={status === Status.LOADING}
|
||||
disabled={isLoading}
|
||||
placeholder={placeholder}
|
||||
required={required}
|
||||
value={ensureStringType(value) || undefined} // ensures placeholder shows up when value is empty string
|
||||
onChange={(event) => {
|
||||
setLocalConfigEntry({ ...configEntry, value: event.target.value });
|
||||
validateAndSetConfigValue(event.target.value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -147,10 +150,10 @@ export const ConnectorConfigurationField: React.FC<ConnectorConfigurationFieldPr
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch
|
||||
checked={ensureBooleanType(value)}
|
||||
disabled={status === Status.LOADING || !hasPlatinumLicense}
|
||||
disabled={isLoading || !hasPlatinumLicense}
|
||||
label={<p>{label}</p>}
|
||||
onChange={(event) => {
|
||||
setLocalConfigEntry({ ...configEntry, value: event.target.checked });
|
||||
validateAndSetConfigValue(event.target.checked);
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
@ -160,7 +163,7 @@ export const ConnectorConfigurationField: React.FC<ConnectorConfigurationFieldPr
|
|||
button={
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.newIndex.selectConnector.openPopoverLabel',
|
||||
'searchConnectors.configuration.openPopoverLabel',
|
||||
{
|
||||
defaultMessage: 'Open licensing popover',
|
||||
}
|
||||
|
@ -171,6 +174,8 @@ export const ConnectorConfigurationField: React.FC<ConnectorConfigurationFieldPr
|
|||
}
|
||||
closePopover={() => setIsPopoverOpen(false)}
|
||||
isPopoverOpen={isPopoverOpen}
|
||||
stackManagementHref={stackManagementLink}
|
||||
subscriptionLink={subscriptionLink}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
@ -182,7 +187,7 @@ export const ConnectorConfigurationField: React.FC<ConnectorConfigurationFieldPr
|
|||
return (
|
||||
<EuiSwitch
|
||||
checked={ensureBooleanType(value)}
|
||||
disabled={status === Status.LOADING}
|
||||
disabled={isLoading}
|
||||
label={
|
||||
tooltip ? (
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
|
@ -198,7 +203,7 @@ export const ConnectorConfigurationField: React.FC<ConnectorConfigurationFieldPr
|
|||
)
|
||||
}
|
||||
onChange={(event) => {
|
||||
setLocalConfigEntry({ ...configEntry, value: event.target.checked });
|
||||
validateAndSetConfigValue(event.target.checked);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -206,22 +211,22 @@ export const ConnectorConfigurationField: React.FC<ConnectorConfigurationFieldPr
|
|||
default:
|
||||
return sensitive ? (
|
||||
<EuiFieldPassword
|
||||
disabled={status === Status.LOADING}
|
||||
disabled={isLoading}
|
||||
required={required}
|
||||
type="dual"
|
||||
value={ensureStringType(value)}
|
||||
onChange={(event) => {
|
||||
setLocalConfigEntry({ ...configEntry, value: event.target.value });
|
||||
validateAndSetConfigValue(event.target.value);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<EuiFieldText
|
||||
disabled={status === Status.LOADING}
|
||||
disabled={isLoading}
|
||||
placeholder={placeholder}
|
||||
required={required}
|
||||
value={ensureStringType(value)}
|
||||
onChange={(event) => {
|
||||
setLocalConfigEntry({ ...configEntry, value: event.target.value });
|
||||
validateAndSetConfigValue(event.target.value);
|
||||
}}
|
||||
/>
|
||||
);
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiSpacer,
|
||||
EuiPanel,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { isCategoryEntry } from '../../utils';
|
||||
import { sortAndFilterConnectorConfiguration } from '../../utils/connector_configuration_utils';
|
||||
import { ConnectorConfiguration } from '../../types';
|
||||
import { ConfigView } from './connector_configuration';
|
||||
import { ConnectorConfigurationFormItems } from './connector_configuration_form_items';
|
||||
|
||||
interface ConnectorConfigurationForm {
|
||||
cancelEditing: () => void;
|
||||
configuration: ConnectorConfiguration;
|
||||
hasDocumentLevelSecurity: boolean;
|
||||
isLoading: boolean;
|
||||
isNative: boolean;
|
||||
saveConfig: (config: Record<string, string | number | boolean | null>) => void;
|
||||
stackManagementHref?: string;
|
||||
subscriptionLink?: string;
|
||||
}
|
||||
|
||||
function configViewToConfigValues(
|
||||
configView: ConfigView
|
||||
): Record<string, string | number | boolean | null> {
|
||||
const result: Record<string, string | number | boolean | null> = {};
|
||||
for (const { key, value } of configView.advancedConfigurations) {
|
||||
result[key] = value;
|
||||
}
|
||||
for (const { key, value } of configView.unCategorizedItems) {
|
||||
result[key] = value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export const ConnectorConfigurationForm: React.FC<ConnectorConfigurationForm> = ({
|
||||
cancelEditing,
|
||||
configuration,
|
||||
hasDocumentLevelSecurity,
|
||||
isLoading,
|
||||
isNative,
|
||||
saveConfig,
|
||||
}) => {
|
||||
const [localConfig, setLocalConfig] = useState<ConnectorConfiguration>(configuration);
|
||||
const [configView, setConfigView] = useState<ConfigView>(
|
||||
sortAndFilterConnectorConfiguration(configuration, isNative)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setConfigView(sortAndFilterConnectorConfiguration(localConfig, isNative));
|
||||
}, [localConfig, isNative]);
|
||||
|
||||
useEffect(() => {
|
||||
setLocalConfig(configuration);
|
||||
}, [configuration]);
|
||||
|
||||
return (
|
||||
<EuiForm
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
saveConfig(configViewToConfigValues(configView));
|
||||
}}
|
||||
component="form"
|
||||
>
|
||||
<ConnectorConfigurationFormItems
|
||||
isLoading={isLoading}
|
||||
items={configView.unCategorizedItems}
|
||||
hasDocumentLevelSecurityEnabled={hasDocumentLevelSecurity}
|
||||
setConfigEntry={(key, value) => {
|
||||
const entry = localConfig[key];
|
||||
if (entry && !isCategoryEntry(entry)) {
|
||||
const newConfiguration: ConnectorConfiguration = {
|
||||
...localConfig,
|
||||
[key]: { ...entry, value },
|
||||
};
|
||||
setLocalConfig(newConfiguration);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{configView.categories.map((category, index) => (
|
||||
<React.Fragment key={index}>
|
||||
<EuiSpacer />
|
||||
<EuiTitle size="s">
|
||||
<h3>{category.label}</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer />
|
||||
<ConnectorConfigurationFormItems
|
||||
isLoading={isLoading}
|
||||
items={category.configEntries}
|
||||
hasDocumentLevelSecurityEnabled={hasDocumentLevelSecurity}
|
||||
setConfigEntry={(key, value) => {
|
||||
const categories = configView.categories;
|
||||
categories[index] = { ...categories[index], [key]: value };
|
||||
setConfigView({
|
||||
...configView,
|
||||
categories,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
{configView.advancedConfigurations.length > 0 && (
|
||||
<React.Fragment>
|
||||
<EuiSpacer />
|
||||
<EuiTitle size="xs">
|
||||
<h4>
|
||||
{i18n.translate(
|
||||
'searchConnectors.configurationConnector.config.advancedConfigurations.title',
|
||||
{ defaultMessage: 'Advanced Configurations' }
|
||||
)}
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiPanel color="subdued">
|
||||
<ConnectorConfigurationFormItems
|
||||
isLoading={isLoading}
|
||||
items={configView.advancedConfigurations}
|
||||
hasDocumentLevelSecurityEnabled={hasDocumentLevelSecurity}
|
||||
setConfigEntry={(key, value) => {
|
||||
setConfigView({
|
||||
...configView,
|
||||
advancedConfigurations: { ...configView.advancedConfigurations, [key]: value },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<EuiSpacer />
|
||||
<EuiFormRow>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="entSearchContent-connector-configuration-saveConfiguration"
|
||||
data-telemetry-id="entSearchContent-connector-configuration-saveConfiguration"
|
||||
type="submit"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
{i18n.translate('searchConnectors.configurationConnector.config.submitButton.title', {
|
||||
defaultMessage: 'Save configuration',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-telemetry-id="entSearchContent-connector-configuration-cancelEdit"
|
||||
isDisabled={isLoading}
|
||||
onClick={() => {
|
||||
cancelEditing();
|
||||
}}
|
||||
>
|
||||
{i18n.translate(
|
||||
'searchConnectors.configurationConnector.config.cancelEditingButton.title',
|
||||
{
|
||||
defaultMessage: 'Cancel',
|
||||
}
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
);
|
||||
};
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
@ -11,19 +12,23 @@ import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiIcon, EuiPanel, EuiToolTip }
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { DisplayType } from '@kbn/search-connectors';
|
||||
import { DisplayType } from '../..';
|
||||
|
||||
import { ConfigEntryView } from './connector_configuration';
|
||||
import { ConnectorConfigurationField } from './connector_configuration_field';
|
||||
import { ConfigEntryView } from './connector_configuration_logic';
|
||||
|
||||
interface ConnectorConfigurationFormItemsProps {
|
||||
hasDocumentLevelSecurityEnabled: boolean;
|
||||
isLoading: boolean;
|
||||
items: ConfigEntryView[];
|
||||
setConfigEntry: (key: string, value: string | number | boolean | null) => void;
|
||||
}
|
||||
|
||||
export const ConnectorConfigurationFormItems: React.FC<ConnectorConfigurationFormItemsProps> = ({
|
||||
isLoading,
|
||||
items,
|
||||
hasDocumentLevelSecurityEnabled,
|
||||
setConfigEntry,
|
||||
}) => {
|
||||
return (
|
||||
<EuiFlexGroup direction="column">
|
||||
|
@ -33,11 +38,11 @@ export const ConnectorConfigurationFormItems: React.FC<ConnectorConfigurationFor
|
|||
depends_on: dependencies,
|
||||
key,
|
||||
display,
|
||||
is_valid: isValid,
|
||||
isValid,
|
||||
label,
|
||||
sensitive,
|
||||
tooltip,
|
||||
validation_errors: validationErrors,
|
||||
validationErrors,
|
||||
} = configEntry;
|
||||
|
||||
if (key === 'use_document_level_security' && !hasDocumentLevelSecurityEnabled) {
|
||||
|
@ -45,13 +50,10 @@ export const ConnectorConfigurationFormItems: React.FC<ConnectorConfigurationFor
|
|||
}
|
||||
|
||||
const helpText = defaultValue
|
||||
? i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.config.defaultValue',
|
||||
{
|
||||
defaultMessage: 'If left empty, the default value {defaultValue} will be used.',
|
||||
values: { defaultValue },
|
||||
}
|
||||
)
|
||||
? i18n.translate('searchConnectors.configurationConnector.config.defaultValue', {
|
||||
defaultMessage: 'If left empty, the default value {defaultValue} will be used.',
|
||||
values: { defaultValue },
|
||||
})
|
||||
: '';
|
||||
// toggle and sensitive textarea labels go next to the element, not in the row
|
||||
const rowLabel =
|
||||
|
@ -82,7 +84,13 @@ export const ConnectorConfigurationFormItems: React.FC<ConnectorConfigurationFor
|
|||
isInvalid={!isValid}
|
||||
data-test-subj={`entSearchContent-connector-configuration-formrow-${key}`}
|
||||
>
|
||||
<ConnectorConfigurationField configEntry={configEntry} />
|
||||
<ConnectorConfigurationField
|
||||
configEntry={configEntry}
|
||||
isLoading={isLoading}
|
||||
setConfigValue={(value) => {
|
||||
setConfigEntry(configEntry.key, value);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiToolTip>
|
||||
</EuiPanel>
|
||||
|
@ -99,7 +107,13 @@ export const ConnectorConfigurationFormItems: React.FC<ConnectorConfigurationFor
|
|||
isInvalid={!isValid}
|
||||
data-test-subj={`entSearchContent-connector-configuration-formrow-${key}`}
|
||||
>
|
||||
<ConnectorConfigurationField configEntry={configEntry} />
|
||||
<ConnectorConfigurationField
|
||||
configEntry={configEntry}
|
||||
isLoading={isLoading}
|
||||
setConfigValue={(value) => {
|
||||
setConfigEntry(configEntry.key, value);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
@ -24,7 +25,7 @@ export const DocumentLevelSecurityPanel: React.FC<DocumentLevelSecurityPanelProp
|
|||
<EuiTitle>
|
||||
<h4>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.connector.documentLevelSecurity.enablePanel.heading',
|
||||
'searchConnectors.connector.documentLevelSecurity.enablePanel.heading',
|
||||
{ defaultMessage: 'Document Level Security' }
|
||||
)}
|
||||
</h4>
|
||||
|
@ -33,7 +34,7 @@ export const DocumentLevelSecurityPanel: React.FC<DocumentLevelSecurityPanelProp
|
|||
<EuiText size="s">
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.connector.documentLevelSecurity.enablePanel.description',
|
||||
'searchConnectors.connector.documentLevelSecurity.enablePanel.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Enables you to control which documents users can access, based on their permissions. This ensures search results only return relevant, authorized information for users, based on their roles.',
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export * from './connector_configuration';
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
import {
|
||||
EuiPopover,
|
||||
EuiPopoverTitle,
|
||||
EuiText,
|
||||
EuiPopoverFooter,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButton,
|
||||
EuiPopoverProps,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
interface PlatinumLicensePopoverProps {
|
||||
button: EuiPopoverProps['button'];
|
||||
closePopover: () => void;
|
||||
isPopoverOpen: boolean;
|
||||
stackManagementHref?: string;
|
||||
subscriptionLink?: string;
|
||||
}
|
||||
|
||||
export const PlatinumLicensePopover: React.FC<PlatinumLicensePopoverProps> = ({
|
||||
button,
|
||||
isPopoverOpen,
|
||||
closePopover,
|
||||
stackManagementHref,
|
||||
subscriptionLink,
|
||||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
return (
|
||||
<EuiPopover button={button} isOpen={isPopoverOpen} closePopover={closePopover}>
|
||||
<EuiPopoverTitle>
|
||||
{i18n.translate('searchConnectors.connectors.upgradeTitle', {
|
||||
defaultMessage: 'Upgrade to Elastic Platinum',
|
||||
})}
|
||||
</EuiPopoverTitle>
|
||||
<EuiText
|
||||
grow={false}
|
||||
size="s"
|
||||
css={css`
|
||||
max-width: calc(${euiTheme.size.xl} * 10);
|
||||
`}
|
||||
>
|
||||
<p>
|
||||
{i18n.translate('searchConnectors.connectors.upgradeDescription', {
|
||||
defaultMessage:
|
||||
'To use this connector, you must update your license to Platinum or start a 30-day free trial.',
|
||||
})}
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiPopoverFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
{subscriptionLink && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton iconType="popout" target="_blank" href={subscriptionLink}>
|
||||
{i18n.translate('searchConnectors.connectors.subscriptionLabel', {
|
||||
defaultMessage: 'Subscription plans',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{stackManagementHref && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton iconType="wrench" iconSide="right" href={stackManagementHref}>
|
||||
{i18n.translate('searchConnectors.manageLicenseButtonLabel', {
|
||||
defaultMessage: 'Manage license',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiPopoverFooter>
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
10
packages/kbn-search-connectors/components/index.ts
Normal file
10
packages/kbn-search-connectors/components/index.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export * from './configuration';
|
||||
export * from './sync_jobs';
|
|
@ -10,7 +10,7 @@ exports[`SyncCalloutsPanel renders 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Completed at {date}"
|
||||
id="xpack.enterpriseSearch.content.syncJobs.flyout.completedDescription"
|
||||
id="searchConnectors.syncJobs.flyout.completedDescription"
|
||||
values={
|
||||
Object {
|
||||
"date": <FormattedDateTime
|
||||
|
@ -29,7 +29,7 @@ exports[`SyncCalloutsPanel renders 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Started at {date}"
|
||||
id="xpack.enterpriseSearch.content.syncJobs.flyout.startedAtDescription"
|
||||
id="searchConnectors.syncJobs.flyout.startedAtDescription"
|
||||
values={
|
||||
Object {
|
||||
"date": <FormattedDateTime
|
||||
|
@ -53,7 +53,7 @@ exports[`SyncCalloutsPanel renders canceled job 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Completed at {date}"
|
||||
id="xpack.enterpriseSearch.content.syncJobs.flyout.completedDescription"
|
||||
id="searchConnectors.syncJobs.flyout.completedDescription"
|
||||
values={
|
||||
Object {
|
||||
"date": <FormattedDateTime
|
||||
|
@ -79,7 +79,7 @@ exports[`SyncCalloutsPanel renders canceled job 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Started at {date}"
|
||||
id="xpack.enterpriseSearch.content.syncJobs.flyout.startedAtDescription"
|
||||
id="searchConnectors.syncJobs.flyout.startedAtDescription"
|
||||
values={
|
||||
Object {
|
||||
"date": <FormattedDateTime
|
||||
|
@ -103,7 +103,7 @@ exports[`SyncCalloutsPanel renders different trigger method 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Completed at {date}"
|
||||
id="xpack.enterpriseSearch.content.syncJobs.flyout.completedDescription"
|
||||
id="searchConnectors.syncJobs.flyout.completedDescription"
|
||||
values={
|
||||
Object {
|
||||
"date": <FormattedDateTime
|
||||
|
@ -131,7 +131,7 @@ exports[`SyncCalloutsPanel renders different trigger method 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Started at {date}"
|
||||
id="xpack.enterpriseSearch.content.syncJobs.flyout.startedAtDescription"
|
||||
id="searchConnectors.syncJobs.flyout.startedAtDescription"
|
||||
values={
|
||||
Object {
|
||||
"date": <FormattedDateTime
|
||||
|
@ -155,7 +155,7 @@ exports[`SyncCalloutsPanel renders error job 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Completed at {date}"
|
||||
id="xpack.enterpriseSearch.content.syncJobs.flyout.completedDescription"
|
||||
id="searchConnectors.syncJobs.flyout.completedDescription"
|
||||
values={
|
||||
Object {
|
||||
"date": <FormattedDateTime
|
||||
|
@ -183,7 +183,7 @@ exports[`SyncCalloutsPanel renders error job 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Started at {date}"
|
||||
id="xpack.enterpriseSearch.content.syncJobs.flyout.startedAtDescription"
|
||||
id="searchConnectors.syncJobs.flyout.startedAtDescription"
|
||||
values={
|
||||
Object {
|
||||
"date": <FormattedDateTime
|
||||
|
@ -207,7 +207,7 @@ exports[`SyncCalloutsPanel renders in progress job 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Completed at {date}"
|
||||
id="xpack.enterpriseSearch.content.syncJobs.flyout.completedDescription"
|
||||
id="searchConnectors.syncJobs.flyout.completedDescription"
|
||||
values={
|
||||
Object {
|
||||
"date": <FormattedDateTime
|
||||
|
@ -235,7 +235,7 @@ exports[`SyncCalloutsPanel renders in progress job 1`] = `
|
|||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Started at {date}"
|
||||
id="xpack.enterpriseSearch.content.syncJobs.flyout.startedAtDescription"
|
||||
id="searchConnectors.syncJobs.flyout.startedAtDescription"
|
||||
values={
|
||||
Object {
|
||||
"date": <FormattedDateTime
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
@ -24,49 +25,43 @@ export const SyncJobDocumentsPanel: React.FC<SyncJobDocumentsPanelProps> = (sync
|
|||
const columns: Array<EuiBasicTableColumn<SyncJobDocumentsPanelProps>> = [
|
||||
{
|
||||
field: 'added',
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.index.syncJobs.documents.added', {
|
||||
name: i18n.translate('searchConnectors.index.syncJobs.documents.added', {
|
||||
defaultMessage: 'Added',
|
||||
}),
|
||||
},
|
||||
{
|
||||
field: 'removed',
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.index.syncJobs.documents.removed', {
|
||||
name: i18n.translate('searchConnectors.index.syncJobs.documents.removed', {
|
||||
defaultMessage: 'Removed',
|
||||
}),
|
||||
},
|
||||
{
|
||||
field: 'total',
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.index.syncJobs.documents.total', {
|
||||
name: i18n.translate('searchConnectors.index.syncJobs.documents.total', {
|
||||
defaultMessage: 'Total',
|
||||
}),
|
||||
},
|
||||
{
|
||||
field: 'volume',
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.index.syncJobs.documents.volume', {
|
||||
name: i18n.translate('searchConnectors.index.syncJobs.documents.volume', {
|
||||
defaultMessage: 'Volume',
|
||||
}),
|
||||
render: (volume: number) =>
|
||||
volume < 1
|
||||
? i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.syncJobs.documents.volume.lessThanOneMBLabel',
|
||||
{
|
||||
defaultMessage: 'Less than 1mb',
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.syncJobs.documents.volume.aboutLabel',
|
||||
{
|
||||
defaultMessage: 'About {volume}',
|
||||
values: {
|
||||
volume: new ByteSizeValue(volume * 1024 * 1024).toString(),
|
||||
},
|
||||
}
|
||||
),
|
||||
? i18n.translate('searchConnectors.index.syncJobs.documents.volume.lessThanOneMBLabel', {
|
||||
defaultMessage: 'Less than 1mb',
|
||||
})
|
||||
: i18n.translate('searchConnectors.index.syncJobs.documents.volume.aboutLabel', {
|
||||
defaultMessage: 'About {volume}',
|
||||
values: {
|
||||
volume: new ByteSizeValue(volume * 1024 * 1024).toString(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<FlyoutPanel
|
||||
title={i18n.translate('xpack.enterpriseSearch.content.index.syncJobs.documents.title', {
|
||||
title={i18n.translate('searchConnectors.index.syncJobs.documents.title', {
|
||||
defaultMessage: 'Documents',
|
||||
})}
|
||||
>
|
|
@ -1,15 +1,16 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { TriggerMethod } from '@kbn/search-connectors';
|
||||
import { TriggerMethod } from '../..';
|
||||
|
||||
import { SyncJobEventsPanel } from './events_panel';
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
@ -13,9 +14,8 @@ import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui';
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { TriggerMethod } from '@kbn/search-connectors';
|
||||
|
||||
import { FormattedDateTime } from '../../../../shared/formatted_date_time';
|
||||
import { FormattedDateTime } from '../../utils/formatted_date_time';
|
||||
import { TriggerMethod } from '../..';
|
||||
|
||||
import { FlyoutPanel } from './flyout_panel';
|
||||
|
||||
|
@ -48,45 +48,40 @@ export const SyncJobEventsPanel: React.FC<SyncJobsEventPanelProps> = ({
|
|||
date: syncRequestedAt,
|
||||
title:
|
||||
triggerMethod === TriggerMethod.ON_DEMAND
|
||||
? i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.syncJobs.events.syncRequestedManually',
|
||||
{ defaultMessage: 'Sync requested manually' }
|
||||
)
|
||||
: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.syncJobs.events.syncRequestedScheduled',
|
||||
{ defaultMessage: 'Sync requested by schedule' }
|
||||
),
|
||||
? i18n.translate('searchConnectors.index.syncJobs.events.syncRequestedManually', {
|
||||
defaultMessage: 'Sync requested manually',
|
||||
})
|
||||
: i18n.translate('searchConnectors.index.syncJobs.events.syncRequestedScheduled', {
|
||||
defaultMessage: 'Sync requested by schedule',
|
||||
}),
|
||||
},
|
||||
{
|
||||
date: syncStarted,
|
||||
title: i18n.translate('xpack.enterpriseSearch.content.index.syncJobs.events.syncStarted', {
|
||||
title: i18n.translate('searchConnectors.index.syncJobs.events.syncStarted', {
|
||||
defaultMessage: 'Sync started',
|
||||
}),
|
||||
},
|
||||
{
|
||||
date: lastUpdated,
|
||||
title: i18n.translate('xpack.enterpriseSearch.content.index.syncJobs.events.lastUpdated', {
|
||||
title: i18n.translate('searchConnectors.index.syncJobs.events.lastUpdated', {
|
||||
defaultMessage: 'Last updated',
|
||||
}),
|
||||
},
|
||||
{
|
||||
date: completed,
|
||||
title: i18n.translate('xpack.enterpriseSearch.content.index.syncJobs.events.completed', {
|
||||
title: i18n.translate('searchConnectors.index.syncJobs.events.completed', {
|
||||
defaultMessage: 'Completed',
|
||||
}),
|
||||
},
|
||||
{
|
||||
date: cancelationRequestedAt,
|
||||
title: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.syncJobs.events.cancelationRequested',
|
||||
{
|
||||
defaultMessage: 'Cancelation requested',
|
||||
}
|
||||
),
|
||||
title: i18n.translate('searchConnectors.index.syncJobs.events.cancelationRequested', {
|
||||
defaultMessage: 'Cancelation requested',
|
||||
}),
|
||||
},
|
||||
{
|
||||
date: canceledAt,
|
||||
title: i18n.translate('xpack.enterpriseSearch.content.index.syncJobs.events.canceled', {
|
||||
title: i18n.translate('searchConnectors.index.syncJobs.events.canceled', {
|
||||
defaultMessage: 'Canceled',
|
||||
}),
|
||||
},
|
||||
|
@ -97,14 +92,14 @@ export const SyncJobEventsPanel: React.FC<SyncJobsEventPanelProps> = ({
|
|||
const columns: Array<EuiBasicTableColumn<SyncJobEvent>> = [
|
||||
{
|
||||
field: 'title',
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.index.syncJobs.events.state', {
|
||||
name: i18n.translate('searchConnectors.index.syncJobs.events.state', {
|
||||
defaultMessage: 'State',
|
||||
}),
|
||||
width: '50%',
|
||||
},
|
||||
{
|
||||
field: 'date',
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.index.syncJobs.events.time', {
|
||||
name: i18n.translate('searchConnectors.index.syncJobs.events.time', {
|
||||
defaultMessage: 'Time',
|
||||
}),
|
||||
render: (date: string) => <FormattedDateTime date={new Date(date)} />,
|
||||
|
@ -113,7 +108,7 @@ export const SyncJobEventsPanel: React.FC<SyncJobsEventPanelProps> = ({
|
|||
];
|
||||
return (
|
||||
<FlyoutPanel
|
||||
title={i18n.translate('xpack.enterpriseSearch.content.index.syncJobs.events.title', {
|
||||
title={i18n.translate('searchConnectors.index.syncJobs.events.title', {
|
||||
defaultMessage: 'Events',
|
||||
})}
|
||||
>
|
|
@ -1,15 +1,16 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { FilteringPolicy, FilteringRule, FilteringRuleRule } from '@kbn/search-connectors';
|
||||
import { FilteringRule } from '../..';
|
||||
|
||||
import { FilteringPanel } from './filtering_panel';
|
||||
|
||||
|
@ -17,32 +18,32 @@ describe('FilteringPanel', () => {
|
|||
const filteringRules = [
|
||||
{
|
||||
order: 1,
|
||||
policy: FilteringPolicy.EXCLUDE,
|
||||
rule: FilteringRuleRule.CONTAINS,
|
||||
policy: 'exclude',
|
||||
rule: 'contains',
|
||||
value: 'THIS VALUE',
|
||||
},
|
||||
{
|
||||
order: 2,
|
||||
policy: FilteringPolicy.EXCLUDE,
|
||||
rule: FilteringRuleRule.ENDS_WITH,
|
||||
policy: 'exclude',
|
||||
rule: 'ends_with',
|
||||
value: 'THIS VALUE',
|
||||
},
|
||||
{
|
||||
order: 0,
|
||||
policy: FilteringPolicy.INCLUDE,
|
||||
rule: FilteringRuleRule.EQUALS,
|
||||
policy: 'include',
|
||||
rule: 'equals',
|
||||
value: 'THIS VALUE',
|
||||
},
|
||||
{
|
||||
order: 5,
|
||||
policy: FilteringPolicy.INCLUDE,
|
||||
rule: FilteringRuleRule.GT,
|
||||
policy: 'include',
|
||||
rule: '>',
|
||||
value: 'THIS VALUE',
|
||||
},
|
||||
{
|
||||
order: 4,
|
||||
policy: FilteringPolicy.EXCLUDE,
|
||||
rule: FilteringRuleRule.LT,
|
||||
policy: 'exclude',
|
||||
rule: '<',
|
||||
value: 'THIS VALUE',
|
||||
},
|
||||
] as FilteringRule[];
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
@ -11,9 +12,9 @@ import { EuiCodeBlock, EuiPanel, EuiSpacer } from '@elastic/eui';
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { FilteringRule, FilteringRules } from '@kbn/search-connectors';
|
||||
import { FilteringRule, FilteringRules } from '../..';
|
||||
|
||||
import { FilteringRulesTable } from '../../shared/filtering_rules_table/filtering_rules_table';
|
||||
import { FilteringRulesTable } from './filtering_rules_table';
|
||||
|
||||
import { FlyoutPanel } from './flyout_panel';
|
||||
|
||||
|
@ -29,7 +30,7 @@ export const FilteringPanel: React.FC<FilteringPanelProps> = ({
|
|||
return (
|
||||
<>
|
||||
<FlyoutPanel
|
||||
title={i18n.translate('xpack.enterpriseSearch.content.index.syncJobs.syncRulesTitle', {
|
||||
title={i18n.translate('searchConnectors.index.syncJobs.syncRulesTitle', {
|
||||
defaultMessage: 'Sync rules',
|
||||
})}
|
||||
>
|
||||
|
@ -39,12 +40,9 @@ export const FilteringPanel: React.FC<FilteringPanelProps> = ({
|
|||
<>
|
||||
<EuiSpacer />
|
||||
<FlyoutPanel
|
||||
title={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.syncJobs.syncRulesAdvancedTitle',
|
||||
{
|
||||
defaultMessage: 'Advanced sync rules',
|
||||
}
|
||||
)}
|
||||
title={i18n.translate('searchConnectors.index.syncJobs.syncRulesAdvancedTitle', {
|
||||
defaultMessage: 'Advanced sync rules',
|
||||
})}
|
||||
>
|
||||
<EuiPanel hasShadow={false}>
|
||||
<EuiCodeBlock transparentBackground language="json">
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { EuiBasicTable, EuiBasicTableColumn, EuiCode } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { filteringPolicyToText, filteringRuleToText } from '../../utils/filtering_rule_helpers';
|
||||
import { FilteringRule, FilteringPolicy, FilteringRuleRule } from '../..';
|
||||
|
||||
interface FilteringRulesTableProps {
|
||||
filteringRules: FilteringRule[];
|
||||
showOrder: boolean;
|
||||
}
|
||||
|
||||
export const FilteringRulesTable: React.FC<FilteringRulesTableProps> = ({
|
||||
showOrder,
|
||||
filteringRules,
|
||||
}) => {
|
||||
const columns: Array<EuiBasicTableColumn<FilteringRule>> = [
|
||||
...(showOrder
|
||||
? [
|
||||
{
|
||||
field: 'order',
|
||||
name: i18n.translate('searchConnectors.index.filtering.priority', {
|
||||
defaultMessage: 'Rule priority',
|
||||
}),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
field: 'policy',
|
||||
name: i18n.translate('searchConnectors.index.filtering.policy', {
|
||||
defaultMessage: 'Policy',
|
||||
}),
|
||||
render: (policy: FilteringPolicy) => filteringPolicyToText(policy),
|
||||
},
|
||||
{
|
||||
field: 'field',
|
||||
name: i18n.translate('searchConnectors.index.filtering.field', {
|
||||
defaultMessage: 'field',
|
||||
}),
|
||||
render: (value: string) => <EuiCode>{value}</EuiCode>,
|
||||
},
|
||||
{
|
||||
field: 'rule',
|
||||
name: i18n.translate('searchConnectors.index.filtering.rule', {
|
||||
defaultMessage: 'Rule',
|
||||
}),
|
||||
render: (rule: FilteringRuleRule) => filteringRuleToText(rule),
|
||||
},
|
||||
{
|
||||
field: 'value',
|
||||
name: i18n.translate('searchConnectors.index.filtering.value', {
|
||||
defaultMessage: 'Value',
|
||||
}),
|
||||
render: (value: string) => <EuiCode>{value}</EuiCode>,
|
||||
},
|
||||
];
|
||||
return (
|
||||
<EuiBasicTable
|
||||
columns={columns}
|
||||
items={filteringRules.sort(({ order }, { order: secondOrder }) => order - secondOrder)}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export * from './sync_jobs_table';
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
@ -11,7 +12,7 @@ import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui';
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { IngestPipelineParams } from '@kbn/search-connectors';
|
||||
import { IngestPipelineParams } from '../..';
|
||||
|
||||
import { FlyoutPanel } from './flyout_panel';
|
||||
|
||||
|
@ -22,56 +23,47 @@ interface PipelinePanelProps {
|
|||
export const PipelinePanel: React.FC<PipelinePanelProps> = ({ pipeline }) => {
|
||||
const items: Array<{ setting: string; value: string | boolean }> = [
|
||||
{
|
||||
setting: i18n.translate('xpack.enterpriseSearch.content.index.syncJobs.pipeline.name', {
|
||||
setting: i18n.translate('searchConnectors.index.syncJobs.pipeline.name', {
|
||||
defaultMessage: 'Pipeline name',
|
||||
}),
|
||||
value: pipeline.name,
|
||||
},
|
||||
{
|
||||
setting: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.syncJobs.pipeline.extractBinaryContent',
|
||||
{
|
||||
defaultMessage: 'Extract binary content',
|
||||
}
|
||||
),
|
||||
setting: i18n.translate('searchConnectors.index.syncJobs.pipeline.extractBinaryContent', {
|
||||
defaultMessage: 'Extract binary content',
|
||||
}),
|
||||
value: pipeline.extract_binary_content,
|
||||
},
|
||||
{
|
||||
setting: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.syncJobs.pipeline.reduceWhitespace',
|
||||
{
|
||||
defaultMessage: 'Reduce whitespace',
|
||||
}
|
||||
),
|
||||
setting: i18n.translate('searchConnectors.index.syncJobs.pipeline.reduceWhitespace', {
|
||||
defaultMessage: 'Reduce whitespace',
|
||||
}),
|
||||
value: pipeline.reduce_whitespace,
|
||||
},
|
||||
{
|
||||
setting: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.syncJobs.pipeline.runMlInference',
|
||||
{
|
||||
defaultMessage: 'Machine learning inference',
|
||||
}
|
||||
),
|
||||
setting: i18n.translate('searchConnectors.index.syncJobs.pipeline.runMlInference', {
|
||||
defaultMessage: 'Machine learning inference',
|
||||
}),
|
||||
value: pipeline.run_ml_inference,
|
||||
},
|
||||
];
|
||||
const columns: Array<EuiBasicTableColumn<{ setting: string; value: string | boolean }>> = [
|
||||
{
|
||||
field: 'setting',
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.index.syncJobs.pipeline.setting', {
|
||||
name: i18n.translate('searchConnectors.index.syncJobs.pipeline.setting', {
|
||||
defaultMessage: 'Pipeline setting',
|
||||
}),
|
||||
},
|
||||
{
|
||||
field: 'value',
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.index.syncJobs.documents.value', {
|
||||
name: i18n.translate('searchConnectors.index.syncJobs.documents.value', {
|
||||
defaultMessage: 'Value',
|
||||
}),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<FlyoutPanel
|
||||
title={i18n.translate('xpack.enterpriseSearch.content.index.syncJobs.pipeline.title', {
|
||||
title={i18n.translate('searchConnectors.index.syncJobs.pipeline.title', {
|
||||
defaultMessage: 'Pipeline',
|
||||
})}
|
||||
>
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { SyncJobType, SyncStatus, TriggerMethod } from '../..';
|
||||
|
||||
import { SyncJobCallouts } from './sync_callouts';
|
||||
|
||||
describe('SyncCalloutsPanel', () => {
|
||||
const syncJob = {
|
||||
cancelation_requested_at: null,
|
||||
canceled_at: null,
|
||||
completed_at: '2022-09-05T15:59:39.816+00:00',
|
||||
connector: {
|
||||
configuration: {},
|
||||
filtering: null,
|
||||
id: 'we2284IBjobuR2-lAuXh',
|
||||
index_name: 'indexName',
|
||||
language: '',
|
||||
pipeline: null,
|
||||
service_type: '',
|
||||
},
|
||||
created_at: '2022-09-05T14:59:39.816+00:00',
|
||||
deleted_document_count: 20,
|
||||
error: null,
|
||||
id: 'id',
|
||||
indexed_document_count: 50,
|
||||
indexed_document_volume: 40,
|
||||
job_type: SyncJobType.FULL,
|
||||
last_seen: '2022-09-05T15:59:39.816+00:00',
|
||||
metadata: {},
|
||||
started_at: '2022-09-05T14:59:39.816+00:00',
|
||||
status: SyncStatus.COMPLETED,
|
||||
total_document_count: null,
|
||||
trigger_method: TriggerMethod.ON_DEMAND,
|
||||
worker_hostname: 'hostname_fake',
|
||||
};
|
||||
it('renders', () => {
|
||||
const wrapper = shallow(<SyncJobCallouts syncJob={syncJob} />);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it('renders error job', () => {
|
||||
const wrapper = shallow(<SyncJobCallouts syncJob={{ ...syncJob, status: SyncStatus.ERROR }} />);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it('renders canceled job', () => {
|
||||
const wrapper = shallow(
|
||||
<SyncJobCallouts syncJob={{ ...syncJob, status: SyncStatus.CANCELED }} />
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it('renders in progress job', () => {
|
||||
const wrapper = shallow(
|
||||
<SyncJobCallouts syncJob={{ ...syncJob, status: SyncStatus.IN_PROGRESS }} />
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it('renders different trigger method', () => {
|
||||
const wrapper = shallow(
|
||||
<SyncJobCallouts
|
||||
syncJob={{
|
||||
...syncJob,
|
||||
status: SyncStatus.IN_PROGRESS,
|
||||
trigger_method: TriggerMethod.SCHEDULED,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
@ -13,16 +14,12 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { SyncStatus, TriggerMethod } from '@kbn/search-connectors';
|
||||
|
||||
import { FormattedDateTime } from '../../../../shared/formatted_date_time';
|
||||
|
||||
import { durationToText } from '../../../utils/duration_to_text';
|
||||
|
||||
import { SyncJobView } from './sync_jobs_view_logic';
|
||||
import { durationToText, getSyncJobDuration } from '../../utils/duration_to_text';
|
||||
import { FormattedDateTime } from '../../utils/formatted_date_time';
|
||||
import { ConnectorSyncJob, SyncStatus, TriggerMethod } from '../..';
|
||||
|
||||
interface SyncJobCalloutsProps {
|
||||
syncJob: SyncJobView;
|
||||
syncJob: ConnectorSyncJob;
|
||||
}
|
||||
|
||||
export const SyncJobCallouts: React.FC<SyncJobCalloutsProps> = ({ syncJob }) => {
|
||||
|
@ -33,12 +30,12 @@ export const SyncJobCallouts: React.FC<SyncJobCalloutsProps> = ({ syncJob }) =>
|
|||
<EuiCallOut
|
||||
color="success"
|
||||
iconType="check"
|
||||
title={i18n.translate('xpack.enterpriseSearch.content.syncJobs.flyout.completedTitle', {
|
||||
title={i18n.translate('searchConnectors.syncJobs.flyout.completedTitle', {
|
||||
defaultMessage: 'Sync complete',
|
||||
})}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.content.syncJobs.flyout.completedDescription"
|
||||
id="searchConnectors.syncJobs.flyout.completedDescription"
|
||||
defaultMessage="Completed at {date}"
|
||||
values={{
|
||||
date: <FormattedDateTime date={new Date(syncJob.completed_at)} />,
|
||||
|
@ -52,11 +49,11 @@ export const SyncJobCallouts: React.FC<SyncJobCalloutsProps> = ({ syncJob }) =>
|
|||
<EuiCallOut
|
||||
color="danger"
|
||||
iconType="cross"
|
||||
title={i18n.translate('xpack.enterpriseSearch.content.syncJobs.flyout.failureTitle', {
|
||||
title={i18n.translate('searchConnectors.syncJobs.flyout.failureTitle', {
|
||||
defaultMessage: 'Sync failure',
|
||||
})}
|
||||
>
|
||||
{i18n.translate('xpack.enterpriseSearch.content.syncJobs.flyout.failureDescription', {
|
||||
{i18n.translate('searchConnectors.syncJobs.flyout.failureDescription', {
|
||||
defaultMessage: 'Sync failure: {error}.',
|
||||
values: {
|
||||
error: syncJob.error,
|
||||
|
@ -70,13 +67,13 @@ export const SyncJobCallouts: React.FC<SyncJobCalloutsProps> = ({ syncJob }) =>
|
|||
<EuiCallOut
|
||||
color="danger"
|
||||
iconType="cross"
|
||||
title={i18n.translate('xpack.enterpriseSearch.content.syncJobs.flyout.canceledTitle', {
|
||||
title={i18n.translate('searchConnectors.syncJobs.flyout.canceledTitle', {
|
||||
defaultMessage: 'Sync canceled',
|
||||
})}
|
||||
>
|
||||
{!!syncJob.canceled_at && (
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.content.syncJobs.flyout.canceledDescription"
|
||||
id="searchConnectors.syncJobs.flyout.canceledDescription"
|
||||
defaultMessage="Sync canceled at {date}"
|
||||
values={{
|
||||
date: <FormattedDateTime date={new Date(syncJob.canceled_at)} />,
|
||||
|
@ -91,22 +88,16 @@ export const SyncJobCallouts: React.FC<SyncJobCalloutsProps> = ({ syncJob }) =>
|
|||
<EuiCallOut
|
||||
color="warning"
|
||||
iconType="clock"
|
||||
title={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.syncJobs.flyout.inProgressTitle',
|
||||
{
|
||||
defaultMessage: 'In progress',
|
||||
}
|
||||
)}
|
||||
title={i18n.translate('searchConnectors.syncJobs.flyout.inProgressTitle', {
|
||||
defaultMessage: 'In progress',
|
||||
})}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.syncJobs.flyout.inProgressDescription',
|
||||
{
|
||||
defaultMessage: 'Sync has been running for {duration}.',
|
||||
values: {
|
||||
duration: durationToText(syncJob.duration),
|
||||
},
|
||||
}
|
||||
)}
|
||||
{i18n.translate('searchConnectors.syncJobs.flyout.inProgressDescription', {
|
||||
defaultMessage: 'Sync has been running for {duration}.',
|
||||
values: {
|
||||
duration: durationToText(getSyncJobDuration(syncJob)),
|
||||
},
|
||||
})}
|
||||
</EuiCallOut>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
@ -117,22 +108,16 @@ export const SyncJobCallouts: React.FC<SyncJobCalloutsProps> = ({ syncJob }) =>
|
|||
iconType="iInCircle"
|
||||
title={
|
||||
syncJob.trigger_method === TriggerMethod.ON_DEMAND
|
||||
? i18n.translate(
|
||||
'xpack.enterpriseSearch.content.syncJobs.flyout.syncStartedManually',
|
||||
{
|
||||
defaultMessage: 'Sync started manually',
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.syncJobs.flyout.syncStartedScheduled',
|
||||
{
|
||||
defaultMessage: 'Sync started by schedule',
|
||||
}
|
||||
)
|
||||
? i18n.translate('searchConnectors.syncJobs.flyout.syncStartedManually', {
|
||||
defaultMessage: 'Sync started manually',
|
||||
})
|
||||
: i18n.translate('searchConnectors.syncJobs.flyout.syncStartedScheduled', {
|
||||
defaultMessage: 'Sync started by schedule',
|
||||
})
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.content.syncJobs.flyout.startedAtDescription"
|
||||
id="searchConnectors.syncJobs.flyout.startedAtDescription"
|
||||
defaultMessage="Started at {date}"
|
||||
values={{
|
||||
date: <FormattedDateTime date={new Date(syncJob.started_at)} />,
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
@ -19,17 +20,17 @@ import {
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { ConnectorSyncJob } from '../../types';
|
||||
import { SyncJobDocumentsPanel } from './documents_panel';
|
||||
import { SyncJobEventsPanel } from './events_panel';
|
||||
import { FilteringPanel } from './filtering_panel';
|
||||
import { FlyoutPanel } from './flyout_panel';
|
||||
import { PipelinePanel } from './pipeline_panel';
|
||||
import { SyncJobCallouts } from './sync_callouts';
|
||||
import { SyncJobView } from './sync_jobs_view_logic';
|
||||
|
||||
interface SyncJobFlyoutProps {
|
||||
onClose: () => void;
|
||||
syncJob?: SyncJobView;
|
||||
syncJob?: ConnectorSyncJob;
|
||||
}
|
||||
|
||||
export const SyncJobFlyout: React.FC<SyncJobFlyoutProps> = ({ onClose, syncJob }) => {
|
||||
|
@ -44,7 +45,7 @@ export const SyncJobFlyout: React.FC<SyncJobFlyoutProps> = ({ onClose, syncJob }
|
|||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<h2>
|
||||
{i18n.translate('xpack.enterpriseSearch.content.syncJobs.flyout.title', {
|
||||
{i18n.translate('searchConnectors.syncJobs.flyout.title', {
|
||||
defaultMessage: 'Event log',
|
||||
})}
|
||||
</h2>
|
||||
|
@ -55,7 +56,7 @@ export const SyncJobFlyout: React.FC<SyncJobFlyoutProps> = ({ onClose, syncJob }
|
|||
<SyncJobCallouts syncJob={syncJob} />
|
||||
<EuiFlexItem>
|
||||
<FlyoutPanel
|
||||
title={i18n.translate('xpack.enterpriseSearch.content.syncJobs.flyout.sync', {
|
||||
title={i18n.translate('searchConnectors.syncJobs.flyout.sync', {
|
||||
defaultMessage: 'Sync',
|
||||
})}
|
||||
>
|
||||
|
@ -63,7 +64,7 @@ export const SyncJobFlyout: React.FC<SyncJobFlyoutProps> = ({ onClose, syncJob }
|
|||
columns={[
|
||||
{
|
||||
field: 'id',
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.syncJobs.flyout.sync.id', {
|
||||
name: i18n.translate('searchConnectors.syncJobs.flyout.sync.id', {
|
||||
defaultMessage: 'ID',
|
||||
}),
|
||||
},
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import {
|
||||
CriteriaWithPagination,
|
||||
EuiBadge,
|
||||
EuiBasicTable,
|
||||
EuiBasicTableColumn,
|
||||
Pagination,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ConnectorSyncJob, SyncJobType, SyncStatus } from '../..';
|
||||
|
||||
import { syncJobTypeToText, syncStatusToColor, syncStatusToText } from '../..';
|
||||
import { durationToText, getSyncJobDuration } from '../../utils/duration_to_text';
|
||||
import { FormattedDateTime } from '../../utils/formatted_date_time';
|
||||
import { SyncJobFlyout } from './sync_job_flyout';
|
||||
|
||||
interface SyncJobHistoryTableProps {
|
||||
isLoading?: boolean;
|
||||
onPaginate: (criteria: CriteriaWithPagination<ConnectorSyncJob>) => void;
|
||||
pagination: Pagination;
|
||||
syncJobs: ConnectorSyncJob[];
|
||||
type: 'content' | 'access_control';
|
||||
}
|
||||
|
||||
export const SyncJobsTable: React.FC<SyncJobHistoryTableProps> = ({
|
||||
isLoading,
|
||||
onPaginate,
|
||||
pagination,
|
||||
syncJobs,
|
||||
type,
|
||||
}) => {
|
||||
const [selectedSyncJob, setSelectedSyncJob] = useState<ConnectorSyncJob | undefined>(undefined);
|
||||
const columns: Array<EuiBasicTableColumn<ConnectorSyncJob>> = [
|
||||
{
|
||||
field: 'completed_at',
|
||||
name: i18n.translate('searchConnectors.syncJobs.lastSync.columnTitle', {
|
||||
defaultMessage: 'Last sync',
|
||||
}),
|
||||
render: (lastSync: string) =>
|
||||
lastSync ? <FormattedDateTime date={new Date(lastSync)} /> : '--',
|
||||
sortable: true,
|
||||
truncateText: false,
|
||||
},
|
||||
{
|
||||
name: i18n.translate('searchConnectors.syncJobs.syncDuration.columnTitle', {
|
||||
defaultMessage: 'Sync duration',
|
||||
}),
|
||||
render: (syncJob: ConnectorSyncJob) => durationToText(getSyncJobDuration(syncJob)),
|
||||
truncateText: false,
|
||||
},
|
||||
...(type === 'content'
|
||||
? [
|
||||
{
|
||||
field: 'indexed_document_count',
|
||||
name: i18n.translate('searchConnectors.searchIndices.addedDocs.columnTitle', {
|
||||
defaultMessage: 'Docs added',
|
||||
}),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
field: 'deleted_document_count',
|
||||
name: i18n.translate('searchConnectors.searchIndices.deletedDocs.columnTitle', {
|
||||
defaultMessage: 'Docs deleted',
|
||||
}),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
field: 'job_type',
|
||||
name: i18n.translate('searchConnectors.searchIndices.syncJobType.columnTitle', {
|
||||
defaultMessage: 'Content sync type',
|
||||
}),
|
||||
render: (syncType: SyncJobType) => {
|
||||
const syncJobTypeText = syncJobTypeToText(syncType);
|
||||
if (syncJobTypeText.length === 0) return null;
|
||||
return <EuiBadge color="hollow">{syncJobTypeText}</EuiBadge>;
|
||||
},
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(type === 'access_control'
|
||||
? [
|
||||
{
|
||||
field: 'indexed_document_count',
|
||||
name: i18n.translate('searchConnectors.searchIndices.identitySync.columnTitle', {
|
||||
defaultMessage: 'Identities synced',
|
||||
}),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
field: 'status',
|
||||
name: i18n.translate('searchConnectors.searchIndices.syncStatus.columnTitle', {
|
||||
defaultMessage: 'Status',
|
||||
}),
|
||||
render: (syncStatus: SyncStatus) => (
|
||||
<EuiBadge color={syncStatusToColor(syncStatus)}>{syncStatusToText(syncStatus)}</EuiBadge>
|
||||
),
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
description: i18n.translate('searchConnectors.index.syncJobs.actions.viewJob.title', {
|
||||
defaultMessage: 'View this sync job',
|
||||
}),
|
||||
icon: 'eye',
|
||||
isPrimary: false,
|
||||
name: i18n.translate('searchConnectors.index.syncJobs.actions.viewJob.caption', {
|
||||
defaultMessage: 'View this sync job',
|
||||
}),
|
||||
onClick: (job) => setSelectedSyncJob(job),
|
||||
type: 'icon',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
{Boolean(selectedSyncJob) && (
|
||||
<SyncJobFlyout onClose={() => setSelectedSyncJob(undefined)} syncJob={selectedSyncJob} />
|
||||
)}
|
||||
<EuiBasicTable
|
||||
data-test-subj={`entSearchContent-index-${type}-syncJobs-table`}
|
||||
items={syncJobs}
|
||||
columns={columns}
|
||||
hasActions
|
||||
onChange={onPaginate}
|
||||
pagination={pagination}
|
||||
tableLayout="fixed"
|
||||
loading={isLoading}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -13,6 +13,7 @@ export const CURRENT_CONNECTORS_JOB_INDEX = '.elastic-connectors-sync-jobs-v1';
|
|||
export const CONNECTORS_VERSION = 1;
|
||||
export const CONNECTORS_ACCESS_CONTROL_INDEX_PREFIX = '.search-acl-filter-';
|
||||
|
||||
export * from './components';
|
||||
export * from './connectors';
|
||||
export * from './lib';
|
||||
export * from './types';
|
||||
|
|
|
@ -11,8 +11,6 @@ import {
|
|||
ConnectorConfiguration,
|
||||
ConnectorDocument,
|
||||
ConnectorStatus,
|
||||
FilteringPolicy,
|
||||
FilteringRuleRule,
|
||||
FilteringValidationState,
|
||||
IngestPipelineParams,
|
||||
} from '../types/connectors';
|
||||
|
@ -59,8 +57,8 @@ export function createConnectorDocument({
|
|||
field: '_',
|
||||
id: 'DEFAULT',
|
||||
order: 0,
|
||||
policy: FilteringPolicy.INCLUDE,
|
||||
rule: FilteringRuleRule.REGEX,
|
||||
policy: 'include',
|
||||
rule: 'regex',
|
||||
updated_at: currentTimestamp,
|
||||
value: '.*',
|
||||
},
|
||||
|
@ -83,8 +81,8 @@ export function createConnectorDocument({
|
|||
field: '_',
|
||||
id: 'DEFAULT',
|
||||
order: 0,
|
||||
policy: FilteringPolicy.INCLUDE,
|
||||
rule: FilteringRuleRule.REGEX,
|
||||
policy: 'include',
|
||||
rule: 'regex',
|
||||
updated_at: currentTimestamp,
|
||||
value: '.*',
|
||||
},
|
||||
|
|
|
@ -17,6 +17,7 @@ export * from './update_filtering_draft';
|
|||
export * from './update_native';
|
||||
export * from './start_sync';
|
||||
export * from './update_connector_configuration';
|
||||
export * from './update_connector_index_name';
|
||||
export * from './update_connector_name_and_description';
|
||||
export * from './update_connector_scheduling';
|
||||
export * from './update_connector_service_type';
|
||||
|
|
|
@ -30,7 +30,7 @@ export const updateConnectorConfiguration = async (
|
|||
connector.status === ConnectorStatus.CREATED
|
||||
? ConnectorStatus.CONFIGURED
|
||||
: connector.status;
|
||||
const updatedConfig = Object.keys(connector.configuration)
|
||||
const updatedConfig: ConnectorConfiguration = Object.keys(connector.configuration)
|
||||
.map((key) => {
|
||||
const configEntry = connector.configuration[key];
|
||||
return isConfigEntry(configEntry)
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { WriteResponseBase } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { ElasticsearchClient } from '@kbn/core/server';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CONNECTORS_INDEX, fetchConnectorByIndexName } from '..';
|
||||
|
||||
export const updateConnectorIndexName = async (
|
||||
client: ElasticsearchClient,
|
||||
connectorId: string,
|
||||
indexName: string
|
||||
): Promise<WriteResponseBase> => {
|
||||
const connectorResult = await fetchConnectorByIndexName(client, indexName);
|
||||
if (connectorResult) {
|
||||
throw new Error(
|
||||
i18n.translate('searchConnectors.server.connectors.indexName.error', {
|
||||
defaultMessage:
|
||||
'This index has already been registered to connector {connectorId}. Please delete that connector or select a different index name.',
|
||||
values: { connectorId },
|
||||
})
|
||||
);
|
||||
}
|
||||
return await client.update({
|
||||
index: CONNECTORS_INDEX,
|
||||
doc: { index_name: indexName },
|
||||
id: connectorId,
|
||||
});
|
||||
};
|
|
@ -19,5 +19,7 @@
|
|||
"@kbn/i18n",
|
||||
"@kbn/core",
|
||||
"@kbn/core-elasticsearch-server",
|
||||
"@kbn/config-schema",
|
||||
"@kbn/i18n-react",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -111,20 +111,26 @@ export interface IngestPipelineParams {
|
|||
run_ml_inference: boolean;
|
||||
}
|
||||
|
||||
export enum FilteringPolicy {
|
||||
EXCLUDE = 'exclude',
|
||||
INCLUDE = 'include',
|
||||
}
|
||||
export type FilteringPolicy = 'exclude' | 'include';
|
||||
|
||||
export enum FilteringRuleRule {
|
||||
CONTAINS = 'contains',
|
||||
ENDS_WITH = 'ends_with',
|
||||
EQUALS = 'equals',
|
||||
GT = '>',
|
||||
LT = '<',
|
||||
REGEX = 'regex',
|
||||
STARTS_WITH = 'starts_with',
|
||||
}
|
||||
export type FilteringRuleRule =
|
||||
| 'contains'
|
||||
| 'ends_with'
|
||||
| 'equals'
|
||||
| '>'
|
||||
| '<'
|
||||
| 'regex'
|
||||
| 'starts_with';
|
||||
|
||||
export const FilteringRuleRuleValues: FilteringRuleRule[] = [
|
||||
'contains',
|
||||
'ends_with',
|
||||
'equals',
|
||||
'>',
|
||||
'<',
|
||||
'regex',
|
||||
'starts_with',
|
||||
];
|
||||
|
||||
export interface FilteringRule {
|
||||
created_at: string;
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -12,13 +13,13 @@ import {
|
|||
Dependency,
|
||||
FieldType,
|
||||
isConfigEntry,
|
||||
} from '@kbn/search-connectors';
|
||||
} from '..';
|
||||
|
||||
import { isCategoryEntry } from '../../../../../../../common/connectors/is_category_entry';
|
||||
import { ConfigEntryView, ConfigView } from '../components/configuration/connector_configuration';
|
||||
|
||||
import { isNotNullish } from '../../../../../../../common/utils/is_not_nullish';
|
||||
import { isCategoryEntry } from './is_category_entry';
|
||||
|
||||
import type { ConfigEntryView, ConfigView } from '../connector_configuration_logic';
|
||||
import { isNotNullish } from './is_not_nullish';
|
||||
|
||||
export type ConnectorConfigEntry = ConnectorConfigProperties & { key: string };
|
||||
|
||||
|
@ -120,20 +121,17 @@ export const filterSortValidateEntries = (
|
|||
|
||||
if (configEntry.type === FieldType.INTEGER && !validIntInput(configEntry.value)) {
|
||||
validationErrors.push(
|
||||
i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.config.invalidInteger',
|
||||
{
|
||||
defaultMessage: '{label} must be an integer.',
|
||||
values: { label },
|
||||
}
|
||||
)
|
||||
i18n.translate('searchConnectors.config.invalidInteger', {
|
||||
defaultMessage: '{label} must be an integer.',
|
||||
values: { label },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...configEntry,
|
||||
is_valid: validationErrors.length <= 0,
|
||||
validation_errors: validationErrors,
|
||||
isValid: validationErrors.length <= 0,
|
||||
validationErrors,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
@ -153,6 +151,13 @@ export const sortAndFilterConnectorConfiguration = (
|
|||
config: ConnectorConfiguration,
|
||||
isNative: boolean
|
||||
): ConfigView => {
|
||||
if (!config) {
|
||||
return {
|
||||
advancedConfigurations: [],
|
||||
categories: [],
|
||||
unCategorizedItems: [],
|
||||
};
|
||||
}
|
||||
// This casting is ugly but makes all of the iteration below work for TypeScript
|
||||
// extract_full_html is only defined for crawlers, who don't use this config screen
|
||||
// we explicitly filter it out as well
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import moment from 'moment';
|
27
packages/kbn-search-connectors/utils/duration_to_text.ts
Normal file
27
packages/kbn-search-connectors/utils/duration_to_text.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import { ConnectorSyncJob } from '../types';
|
||||
|
||||
export function getSyncJobDuration(syncJob: ConnectorSyncJob): moment.Duration | undefined {
|
||||
return syncJob.started_at
|
||||
? moment.duration(moment(syncJob.completed_at || new Date()).diff(moment(syncJob.started_at)))
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function durationToText(input?: moment.Duration): string {
|
||||
if (input) {
|
||||
const hours = input.hours();
|
||||
const minutes = input.minutes();
|
||||
const seconds = input.seconds();
|
||||
return `${hours}h ${minutes}m ${seconds}s`;
|
||||
} else {
|
||||
return '--';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { FilteringPolicy, FilteringRuleRule } from '..';
|
||||
|
||||
const filteringRuleStringMap: Record<FilteringRuleRule, string> = {
|
||||
contains: i18n.translate('searchConnectors.content.filteringRules.rules.contains', {
|
||||
defaultMessage: 'Contains',
|
||||
}),
|
||||
ends_with: i18n.translate('searchConnectors.content.filteringRules.rules.endsWith', {
|
||||
defaultMessage: 'Ends with',
|
||||
}),
|
||||
equals: i18n.translate('searchConnectors.content.filteringRules.rules.equals', {
|
||||
defaultMessage: 'Equals',
|
||||
}),
|
||||
['>']: i18n.translate('searchConnectors.content.filteringRules.rules.greaterThan', {
|
||||
defaultMessage: 'Greater than',
|
||||
}),
|
||||
['<']: i18n.translate('searchConnectors.content.filteringRules.rules.lessThan', {
|
||||
defaultMessage: 'Less than',
|
||||
}),
|
||||
regex: i18n.translate('searchConnectors.content.filteringRules.rules.regEx', {
|
||||
defaultMessage: 'Regular expression',
|
||||
}),
|
||||
starts_with: i18n.translate('searchConnectors.content.filteringRules.rules.startsWith', {
|
||||
defaultMessage: 'Starts with',
|
||||
}),
|
||||
};
|
||||
|
||||
export function filteringRuleToText(filteringRule: FilteringRuleRule): string {
|
||||
return filteringRuleStringMap[filteringRule];
|
||||
}
|
||||
|
||||
const filteringPolicyStringMap: Record<FilteringPolicy, string> = {
|
||||
exclude: i18n.translate('searchConnectors.content.filteringRules.policy.exclude', {
|
||||
defaultMessage: 'Exclude',
|
||||
}),
|
||||
include: i18n.translate('searchConnectors.content.filteringRules.policy.include', {
|
||||
defaultMessage: 'Include',
|
||||
}),
|
||||
};
|
||||
|
||||
export function filteringPolicyToText(filteringPolicy: FilteringPolicy): string {
|
||||
return filteringPolicyStringMap[filteringPolicy];
|
||||
}
|
28
packages/kbn-search-connectors/utils/formatted_date_time.tsx
Normal file
28
packages/kbn-search-connectors/utils/formatted_date_time.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { FormattedDate, FormattedTime } from '@kbn/i18n-react';
|
||||
|
||||
interface Props {
|
||||
date: Date;
|
||||
hideTime?: boolean;
|
||||
}
|
||||
|
||||
export const FormattedDateTime: React.FC<Props> = ({ date, hideTime = false }) => (
|
||||
<>
|
||||
<FormattedDate value={date} year="numeric" month="short" day="numeric" />
|
||||
{!hideTime && (
|
||||
<>
|
||||
{' '}
|
||||
<FormattedTime value={date} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
|
@ -6,5 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export * from './filtering_rule_helpers';
|
||||
export * from './is_category_entry';
|
||||
export * from './page_to_pagination';
|
||||
export * from './sync_status_to_text';
|
||||
|
|
17
packages/kbn-search-connectors/utils/page_to_pagination.ts
Normal file
17
packages/kbn-search-connectors/utils/page_to_pagination.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export function pageToPagination(page: { from: number; size: number; total: number }) {
|
||||
// Prevent divide-by-zero-error
|
||||
const pageIndex = page.size ? Math.trunc(page.from / page.size) : 0;
|
||||
return {
|
||||
pageIndex,
|
||||
pageSize: page.size,
|
||||
totalItemCount: page.total,
|
||||
};
|
||||
}
|
|
@ -9,8 +9,6 @@ import {
|
|||
ConnectorStatus,
|
||||
DisplayType,
|
||||
FieldType,
|
||||
FilteringPolicy,
|
||||
FilteringRuleRule,
|
||||
FilteringValidationState,
|
||||
SyncStatus,
|
||||
} from '@kbn/search-connectors';
|
||||
|
@ -78,8 +76,8 @@ export const indices: ElasticsearchIndexWithIngestion[] = [
|
|||
field: '_',
|
||||
id: 'DEFAULT',
|
||||
order: 0,
|
||||
policy: FilteringPolicy.INCLUDE,
|
||||
rule: FilteringRuleRule.REGEX,
|
||||
policy: 'include',
|
||||
rule: 'regex',
|
||||
updated_at: expect.any(String),
|
||||
value: '.*',
|
||||
},
|
||||
|
@ -102,8 +100,8 @@ export const indices: ElasticsearchIndexWithIngestion[] = [
|
|||
field: '_',
|
||||
id: 'DEFAULT',
|
||||
order: 0,
|
||||
policy: FilteringPolicy.INCLUDE,
|
||||
rule: FilteringRuleRule.REGEX,
|
||||
policy: 'include',
|
||||
rule: 'regex',
|
||||
updated_at: expect.any(String),
|
||||
value: '.*',
|
||||
},
|
||||
|
@ -204,8 +202,8 @@ export const indices: ElasticsearchIndexWithIngestion[] = [
|
|||
field: '_',
|
||||
id: 'DEFAULT',
|
||||
order: 0,
|
||||
policy: FilteringPolicy.INCLUDE,
|
||||
rule: FilteringRuleRule.REGEX,
|
||||
policy: 'include',
|
||||
rule: 'regex',
|
||||
updated_at: expect.any(String),
|
||||
value: '.*',
|
||||
},
|
||||
|
@ -228,8 +226,8 @@ export const indices: ElasticsearchIndexWithIngestion[] = [
|
|||
field: '_',
|
||||
id: 'DEFAULT',
|
||||
order: 0,
|
||||
policy: FilteringPolicy.INCLUDE,
|
||||
rule: FilteringRuleRule.REGEX,
|
||||
policy: 'include',
|
||||
rule: 'regex',
|
||||
updated_at: expect.any(String),
|
||||
value: '.*',
|
||||
},
|
||||
|
|
|
@ -9,8 +9,6 @@ import {
|
|||
ConnectorStatus,
|
||||
DisplayType,
|
||||
FieldType,
|
||||
FilteringPolicy,
|
||||
FilteringRuleRule,
|
||||
FilteringValidationState,
|
||||
SyncStatus,
|
||||
} from '@kbn/search-connectors';
|
||||
|
@ -87,8 +85,8 @@ export const connectorIndex: ConnectorViewIndex = {
|
|||
field: '_',
|
||||
id: 'DEFAULT',
|
||||
order: 0,
|
||||
policy: FilteringPolicy.INCLUDE,
|
||||
rule: FilteringRuleRule.REGEX,
|
||||
policy: 'include',
|
||||
rule: 'regex',
|
||||
updated_at: expect.any(String),
|
||||
value: '.*',
|
||||
},
|
||||
|
@ -111,8 +109,8 @@ export const connectorIndex: ConnectorViewIndex = {
|
|||
field: '_',
|
||||
id: 'DEFAULT',
|
||||
order: 0,
|
||||
policy: FilteringPolicy.INCLUDE,
|
||||
rule: FilteringRuleRule.REGEX,
|
||||
policy: 'include',
|
||||
rule: 'regex',
|
||||
updated_at: expect.any(String),
|
||||
value: '.*',
|
||||
},
|
||||
|
@ -217,8 +215,8 @@ export const crawlerIndex: CrawlerViewIndex = {
|
|||
field: '_',
|
||||
id: 'DEFAULT',
|
||||
order: 0,
|
||||
policy: FilteringPolicy.INCLUDE,
|
||||
rule: FilteringRuleRule.REGEX,
|
||||
policy: 'include',
|
||||
rule: 'regex',
|
||||
updated_at: expect.any(String),
|
||||
value: '.*',
|
||||
},
|
||||
|
@ -241,8 +239,8 @@ export const crawlerIndex: CrawlerViewIndex = {
|
|||
field: '_',
|
||||
id: 'DEFAULT',
|
||||
order: 0,
|
||||
policy: FilteringPolicy.INCLUDE,
|
||||
rule: FilteringRuleRule.REGEX,
|
||||
policy: 'include',
|
||||
rule: 'regex',
|
||||
updated_at: expect.any(String),
|
||||
value: '.*',
|
||||
},
|
||||
|
|
|
@ -27,7 +27,6 @@ import { KibanaLogic } from '../../../../../shared/kibana';
|
|||
import { CancelSyncsApiLogic } from '../../../../api/connector/cancel_syncs_api_logic';
|
||||
import { IngestionStatus } from '../../../../types';
|
||||
import { CancelSyncsLogic } from '../../connector/cancel_syncs_logic';
|
||||
import { ConnectorConfigurationLogic } from '../../connector/connector_configuration_logic';
|
||||
import { IndexViewLogic } from '../../index_view_logic';
|
||||
|
||||
export const SyncsContextMenu: React.FC = () => {
|
||||
|
@ -44,7 +43,7 @@ export const SyncsContextMenu: React.FC = () => {
|
|||
const { cancelSyncs } = useActions(CancelSyncsLogic);
|
||||
const { status } = useValues(CancelSyncsApiLogic);
|
||||
const { startSync, startIncrementalSync, startAccessControlSync } = useActions(IndexViewLogic);
|
||||
const { configState } = useValues(ConnectorConfigurationLogic);
|
||||
const { connector } = useValues(IndexViewLogic);
|
||||
|
||||
const [isPopoverOpen, setPopover] = useState(false);
|
||||
const togglePopover = () => setPopover(!isPopoverOpen);
|
||||
|
@ -126,9 +125,10 @@ export const SyncsContextMenu: React.FC = () => {
|
|||
'entSearchContent-${ingestionMethod}-header-sync-more-accessControlSync',
|
||||
'data-test-subj':
|
||||
'entSearchContent-${ingestionMethod}-header-sync-more-accessControlSync',
|
||||
disabled:
|
||||
disabled: Boolean(
|
||||
ingestionStatus === IngestionStatus.INCOMPLETE ||
|
||||
!configState.use_document_level_security?.value,
|
||||
connector?.configuration.use_document_level_security?.value
|
||||
),
|
||||
icon: 'play',
|
||||
name: i18n.translate('xpack.enterpriseSearch.index.header.more.accessControlSync', {
|
||||
defaultMessage: 'Access Control',
|
||||
|
|
|
@ -26,15 +26,20 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { ConnectorStatus } from '@kbn/search-connectors';
|
||||
import { ConnectorConfigurationComponent, ConnectorStatus } from '@kbn/search-connectors';
|
||||
|
||||
import { Status } from '../../../../../../common/types/api';
|
||||
|
||||
import { BetaConnectorCallout } from '../../../../shared/beta/beta_connector_callout';
|
||||
import { useCloudDetails } from '../../../../shared/cloud_details/cloud_details';
|
||||
import { docLinks } from '../../../../shared/doc_links';
|
||||
import { generateEncodedPath } from '../../../../shared/encode_path_params';
|
||||
import { HttpLogic } from '../../../../shared/http/http_logic';
|
||||
import { LicensingLogic } from '../../../../shared/licensing';
|
||||
import { EuiButtonTo, EuiLinkTo } from '../../../../shared/react_router_helpers';
|
||||
|
||||
import { GenerateConnectorApiKeyApiLogic } from '../../../api/connector/generate_connector_api_key_api_logic';
|
||||
import { ConnectorConfigurationApiLogic } from '../../../api/connector/update_connector_configuration_api_logic';
|
||||
import { SEARCH_INDEX_TAB_PATH } from '../../../routes';
|
||||
import { isConnectorIndex } from '../../../utils/indices';
|
||||
|
||||
|
@ -45,7 +50,6 @@ import { IndexViewLogic } from '../index_view_logic';
|
|||
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 { BETA_CONNECTORS, CONNECTORS, getConnectorTemplate } from './constants';
|
||||
import { NativeConnectorConfiguration } from './native_connector_configuration/native_connector_configuration';
|
||||
|
@ -56,6 +60,10 @@ export const ConnectorConfiguration: React.FC = () => {
|
|||
const { indexName } = useValues(IndexNameLogic);
|
||||
const { recheckIndex } = useActions(IndexViewLogic);
|
||||
const cloudContext = useCloudDetails();
|
||||
const { hasPlatinumLicense } = useValues(LicensingLogic);
|
||||
const { status } = useValues(ConnectorConfigurationApiLogic);
|
||||
const { makeRequest } = useActions(ConnectorConfigurationApiLogic);
|
||||
const { http } = useValues(HttpLogic);
|
||||
|
||||
if (!isConnectorIndex(index)) {
|
||||
return <></>;
|
||||
|
@ -190,7 +198,22 @@ export const ConnectorConfiguration: React.FC = () => {
|
|||
},
|
||||
{
|
||||
children: (
|
||||
<ConnectorConfigurationConfig>
|
||||
<ConnectorConfigurationComponent
|
||||
connector={index.connector}
|
||||
hasPlatinumLicense={hasPlatinumLicense}
|
||||
isLoading={status === Status.LOADING}
|
||||
saveConfig={(configuration) =>
|
||||
makeRequest({
|
||||
configuration,
|
||||
connectorId: index.connector.id,
|
||||
indexName: index.name,
|
||||
})
|
||||
}
|
||||
subscriptionLink={docLinks.licenseManagement}
|
||||
stackManagementLink={http.basePath.prepend(
|
||||
'/app/management/stack/license_management'
|
||||
)}
|
||||
>
|
||||
{!index.connector.status ||
|
||||
index.connector.status === ConnectorStatus.CREATED ? (
|
||||
<EuiCallOut
|
||||
|
@ -238,7 +261,7 @@ export const ConnectorConfiguration: React.FC = () => {
|
|||
)}
|
||||
/>
|
||||
)}
|
||||
</ConnectorConfigurationConfig>
|
||||
</ConnectorConfigurationComponent>
|
||||
),
|
||||
status:
|
||||
index.connector.status === ConnectorStatus.CONNECTED
|
||||
|
|
|
@ -1,113 +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 { useActions, useValues } from 'kea';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiCallOut,
|
||||
EuiDescriptionList,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { IndexViewLogic } from '../index_view_logic';
|
||||
|
||||
import { ConnectorConfigurationForm } from './connector_configuration_form';
|
||||
import { ConfigEntryView, ConnectorConfigurationLogic } from './connector_configuration_logic';
|
||||
|
||||
function entryToDisplaylistItem(entry: ConfigEntryView): { description: string; title: string } {
|
||||
return {
|
||||
description: entry.sensitive && !!entry.value ? '********' : String(entry.value) || '--',
|
||||
title: entry.label,
|
||||
};
|
||||
}
|
||||
|
||||
export const ConnectorConfigurationConfig: React.FC = ({ children }) => {
|
||||
const { connectorError } = useValues(IndexViewLogic);
|
||||
const { configView, isEditing } = useValues(ConnectorConfigurationLogic);
|
||||
const { setIsEditing } = useActions(ConnectorConfigurationLogic);
|
||||
|
||||
const uncategorizedDisplayList = configView.unCategorizedItems.map(entryToDisplaylistItem);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column">
|
||||
{children && <EuiFlexItem>{children}</EuiFlexItem>}
|
||||
<EuiFlexItem>
|
||||
{isEditing ? (
|
||||
<ConnectorConfigurationForm />
|
||||
) : (
|
||||
uncategorizedDisplayList.length > 0 && (
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiDescriptionList
|
||||
listItems={uncategorizedDisplayList}
|
||||
className="eui-textBreakWord"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{configView.categories.length > 0 &&
|
||||
configView.categories.map((category) => (
|
||||
<EuiFlexGroup direction="column" key={category.key}>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="s">
|
||||
<h3>{category.label}</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiDescriptionList
|
||||
listItems={category.configEntries.map(entryToDisplaylistItem)}
|
||||
className="eui-textBreakWord"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
))}
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="entSearchContent-connector-configuration-editConfiguration"
|
||||
data-telemetry-id="entSearchContent-connector-overview-configuration-editConfiguration"
|
||||
onClick={() => setIsEditing(!isEditing)}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.config.editButton.title',
|
||||
{
|
||||
defaultMessage: 'Edit configuration',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
{!!connectorError && (
|
||||
<EuiFlexItem>
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
title={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.config.error.title',
|
||||
{
|
||||
defaultMessage: 'Connector error',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiText size="s">{connectorError}</EuiText>
|
||||
</EuiCallOut>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -1,124 +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 { useActions, useValues } from 'kea';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiSpacer,
|
||||
EuiPanel,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { Status } from '../../../../../../common/types/api';
|
||||
|
||||
import { KibanaLogic } from '../../../../shared/kibana';
|
||||
|
||||
import { ConnectorConfigurationApiLogic } from '../../../api/connector/update_connector_configuration_api_logic';
|
||||
|
||||
import { ConnectorConfigurationFormItems } from './connector_configuration_form_items';
|
||||
import { ConnectorConfigurationLogic } from './connector_configuration_logic';
|
||||
|
||||
export const ConnectorConfigurationForm = () => {
|
||||
const { productFeatures } = useValues(KibanaLogic);
|
||||
const { status } = useValues(ConnectorConfigurationApiLogic);
|
||||
|
||||
const { localConfigView } = useValues(ConnectorConfigurationLogic);
|
||||
const { saveConfig, setIsEditing } = useActions(ConnectorConfigurationLogic);
|
||||
|
||||
return (
|
||||
<EuiForm
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
saveConfig();
|
||||
}}
|
||||
component="form"
|
||||
>
|
||||
<ConnectorConfigurationFormItems
|
||||
items={localConfigView.unCategorizedItems}
|
||||
hasDocumentLevelSecurityEnabled={productFeatures.hasDocumentLevelSecurityEnabled}
|
||||
/>
|
||||
{localConfigView.categories.map((category, index) => (
|
||||
<React.Fragment key={index}>
|
||||
<EuiSpacer />
|
||||
<EuiTitle size="s">
|
||||
<h3>{category.label}</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer />
|
||||
<ConnectorConfigurationFormItems
|
||||
items={category.configEntries}
|
||||
hasDocumentLevelSecurityEnabled={productFeatures.hasDocumentLevelSecurityEnabled}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
{localConfigView.advancedConfigurations.length > 0 && (
|
||||
<React.Fragment>
|
||||
<EuiSpacer />
|
||||
<EuiTitle size="xs">
|
||||
<h4>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.config.advancedConfigurations.title',
|
||||
{ defaultMessage: 'Advanced Configurations' }
|
||||
)}
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiPanel color="subdued">
|
||||
<ConnectorConfigurationFormItems
|
||||
items={localConfigView.advancedConfigurations}
|
||||
hasDocumentLevelSecurityEnabled={productFeatures.hasDocumentLevelSecurityEnabled}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<EuiSpacer />
|
||||
<EuiFormRow>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="entSearchContent-connector-configuration-saveConfiguration"
|
||||
data-telemetry-id="entSearchContent-connector-configuration-saveConfiguration"
|
||||
type="submit"
|
||||
isLoading={status === Status.LOADING}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.config.submitButton.title',
|
||||
{
|
||||
defaultMessage: 'Save configuration',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-telemetry-id="entSearchContent-connector-configuration-cancelEdit"
|
||||
isDisabled={status === Status.LOADING}
|
||||
onClick={() => {
|
||||
setIsEditing(false);
|
||||
}}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.config.cancelEditingButton.title',
|
||||
{
|
||||
defaultMessage: 'Cancel',
|
||||
}
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
);
|
||||
};
|
File diff suppressed because it is too large
Load diff
|
@ -1,231 +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 { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import {
|
||||
ConnectorConfigProperties,
|
||||
ConnectorConfiguration,
|
||||
ConnectorStatus,
|
||||
} from '@kbn/search-connectors';
|
||||
|
||||
import { isCategoryEntry } from '../../../../../../common/connectors/is_category_entry';
|
||||
import { isNotNullish } from '../../../../../../common/utils/is_not_nullish';
|
||||
|
||||
import {
|
||||
ConnectorConfigurationApiLogic,
|
||||
PostConnectorConfigurationActions,
|
||||
} from '../../../api/connector/update_connector_configuration_api_logic';
|
||||
import {
|
||||
CachedFetchIndexApiLogic,
|
||||
CachedFetchIndexApiLogicActions,
|
||||
} from '../../../api/index/cached_fetch_index_api_logic';
|
||||
import { FetchIndexApiResponse } from '../../../api/index/fetch_index_api_logic';
|
||||
import { isConnectorIndex } from '../../../utils/indices';
|
||||
|
||||
import {
|
||||
ensureCorrectTyping,
|
||||
sortAndFilterConnectorConfiguration,
|
||||
} from './utils/connector_configuration_utils';
|
||||
|
||||
type ConnectorConfigurationActions = Pick<
|
||||
PostConnectorConfigurationActions,
|
||||
'apiSuccess' | 'makeRequest'
|
||||
> & {
|
||||
fetchIndexApiSuccess: CachedFetchIndexApiLogicActions['apiSuccess'];
|
||||
saveConfig: () => void;
|
||||
setConfigState(configState: ConnectorConfiguration): {
|
||||
configState: ConnectorConfiguration;
|
||||
};
|
||||
setIsEditing(isEditing: boolean): { isEditing: boolean };
|
||||
setLocalConfigEntry(configEntry: ConfigEntry): ConfigEntry;
|
||||
setLocalConfigState(configState: ConnectorConfiguration): {
|
||||
configState: ConnectorConfiguration;
|
||||
};
|
||||
setShouldStartInEditMode(shouldStartInEditMode: boolean): { shouldStartInEditMode: boolean };
|
||||
};
|
||||
|
||||
interface ConnectorConfigurationValues {
|
||||
configState: ConnectorConfiguration;
|
||||
configView: ConfigView;
|
||||
index: FetchIndexApiResponse;
|
||||
isEditing: boolean;
|
||||
localConfigState: ConnectorConfiguration;
|
||||
localConfigView: ConfigView;
|
||||
shouldStartInEditMode: boolean;
|
||||
}
|
||||
|
||||
interface ConfigEntry extends ConnectorConfigProperties {
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface ConfigEntryView extends ConfigEntry {
|
||||
is_valid: boolean;
|
||||
validation_errors: string[];
|
||||
}
|
||||
|
||||
export interface CategoryEntry {
|
||||
configEntries: ConfigEntryView[];
|
||||
key: string;
|
||||
label: string;
|
||||
order: number;
|
||||
}
|
||||
|
||||
export interface ConfigView {
|
||||
advancedConfigurations: ConfigEntryView[];
|
||||
categories: CategoryEntry[];
|
||||
unCategorizedItems: ConfigEntryView[];
|
||||
}
|
||||
|
||||
export const ConnectorConfigurationLogic = kea<
|
||||
MakeLogicType<ConnectorConfigurationValues, ConnectorConfigurationActions>
|
||||
>({
|
||||
actions: {
|
||||
saveConfig: true,
|
||||
setConfigState: (configState: ConnectorConfiguration) => ({ configState }),
|
||||
setIsEditing: (isEditing: boolean) => ({
|
||||
isEditing,
|
||||
}),
|
||||
setLocalConfigEntry: (configEntry: ConfigEntry) => ({ ...configEntry }),
|
||||
setLocalConfigState: (configState: ConnectorConfiguration) => ({ configState }),
|
||||
setShouldStartInEditMode: (shouldStartInEditMode: boolean) => ({ shouldStartInEditMode }),
|
||||
},
|
||||
connect: {
|
||||
actions: [
|
||||
ConnectorConfigurationApiLogic,
|
||||
['apiSuccess', 'makeRequest'],
|
||||
CachedFetchIndexApiLogic,
|
||||
['apiSuccess as fetchIndexApiSuccess'],
|
||||
],
|
||||
values: [CachedFetchIndexApiLogic, ['indexData as index']],
|
||||
},
|
||||
events: ({ actions, values }) => ({
|
||||
afterMount: () => {
|
||||
actions.setConfigState(
|
||||
isConnectorIndex(values.index) ? values.index.connector.configuration : {}
|
||||
);
|
||||
if (
|
||||
isConnectorIndex(values.index) &&
|
||||
(values.index.connector.status === ConnectorStatus.CREATED ||
|
||||
values.index.connector.status === ConnectorStatus.NEEDS_CONFIGURATION)
|
||||
) {
|
||||
// Only start in edit mode if we haven't configured yet
|
||||
// Necessary to prevent a race condition between saving config and getting updated connector
|
||||
actions.setShouldStartInEditMode(true);
|
||||
}
|
||||
},
|
||||
}),
|
||||
listeners: ({ actions, values }) => ({
|
||||
apiSuccess: ({ indexName }) => {
|
||||
CachedFetchIndexApiLogic.actions.makeRequest({ indexName });
|
||||
},
|
||||
fetchIndexApiSuccess: (index) => {
|
||||
if (!values.isEditing && isConnectorIndex(index)) {
|
||||
actions.setConfigState(index.connector.configuration);
|
||||
}
|
||||
|
||||
if (
|
||||
!values.isEditing &&
|
||||
values.shouldStartInEditMode &&
|
||||
isConnectorIndex(index) &&
|
||||
index.connector.status === ConnectorStatus.NEEDS_CONFIGURATION &&
|
||||
index.connector.configuration &&
|
||||
Object.entries(index.connector.configuration).length > 0
|
||||
) {
|
||||
actions.setIsEditing(true);
|
||||
}
|
||||
},
|
||||
saveConfig: () => {
|
||||
if (isConnectorIndex(values.index)) {
|
||||
actions.makeRequest({
|
||||
configuration: Object.keys(values.localConfigState)
|
||||
.map((key) => {
|
||||
const entry = values.localConfigState[key];
|
||||
if (isCategoryEntry(entry) || !entry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { key, value: entry.value ?? '' };
|
||||
})
|
||||
.filter(isNotNullish)
|
||||
.reduce((prev: Record<string, string | number | boolean | null>, { key, value }) => {
|
||||
prev[key] = value;
|
||||
return prev;
|
||||
}, {}),
|
||||
connectorId: values.index.connector.id,
|
||||
indexName: values.index.connector.index_name ?? '',
|
||||
});
|
||||
}
|
||||
},
|
||||
setIsEditing: (isEditing) => {
|
||||
if (isEditing) {
|
||||
actions.setLocalConfigState(values.configState);
|
||||
}
|
||||
},
|
||||
}),
|
||||
path: ['enterprise_search', 'content', 'connector_configuration'],
|
||||
reducers: () => ({
|
||||
configState: [
|
||||
{},
|
||||
{
|
||||
apiSuccess: (_, { configuration }) => configuration,
|
||||
setConfigState: (_, { configState }) => configState,
|
||||
},
|
||||
],
|
||||
isEditing: [
|
||||
false,
|
||||
{
|
||||
apiSuccess: () => false,
|
||||
setIsEditing: (_, { isEditing }) => isEditing,
|
||||
},
|
||||
],
|
||||
localConfigState: [
|
||||
{},
|
||||
{
|
||||
setLocalConfigEntry: (
|
||||
configState,
|
||||
{ key, display, type, validations, value, ...configEntry }
|
||||
) => ({
|
||||
...configState,
|
||||
[key]: {
|
||||
...configEntry,
|
||||
display,
|
||||
type,
|
||||
validations: validations ?? [],
|
||||
value: display ? ensureCorrectTyping(type, value) : value, // only check type if field had a specified eui element
|
||||
},
|
||||
}),
|
||||
setLocalConfigState: (_, { configState }) => configState,
|
||||
},
|
||||
],
|
||||
shouldStartInEditMode: [
|
||||
false,
|
||||
{
|
||||
apiSuccess: () => false,
|
||||
setShouldStartInEditMode: (_, { shouldStartInEditMode }) => shouldStartInEditMode,
|
||||
},
|
||||
],
|
||||
}),
|
||||
selectors: ({ selectors }) => ({
|
||||
configView: [
|
||||
() => [selectors.configState, selectors.index],
|
||||
(configState: ConnectorConfiguration, index: FetchIndexApiResponse) =>
|
||||
sortAndFilterConnectorConfiguration(
|
||||
configState,
|
||||
isConnectorIndex(index) ? index.connector.is_native : false
|
||||
),
|
||||
],
|
||||
localConfigView: [
|
||||
() => [selectors.localConfigState, selectors.index],
|
||||
(configState: ConnectorConfiguration, index: FetchIndexApiResponse) =>
|
||||
sortAndFilterConnectorConfiguration(
|
||||
configState,
|
||||
isConnectorIndex(index) ? index.connector.is_native : false
|
||||
),
|
||||
],
|
||||
}),
|
||||
});
|
|
@ -1,88 +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 { EuiCallOut, EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
export const ConnectorConfigurationConfig: React.FC = () => {
|
||||
return (
|
||||
<ConnectorConfigurationConfig>
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.content.indices.configurationConnector.config.description.firstParagraph"
|
||||
defaultMessage="Now that your connector is deployed, enhance the connector client for your custom data source. There are several {link} you can customize with your own additional implementation logic."
|
||||
values={{
|
||||
link: (
|
||||
<EuiLink
|
||||
href="https://github.com/elastic/connectors-python/tree/main/connectors/sources"
|
||||
target="_blank"
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.config.connectorClientLink',
|
||||
{ defaultMessage: 'connectors' }
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.config.description.secondParagraph',
|
||||
{
|
||||
defaultMessage:
|
||||
'While the connector clients in the repository are built in Ruby, there’s no technical limitation to only use Ruby. Build a connector client with the technology that works best for your skillset.',
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.content.indices.configurationConnector.config.description.thirdParagraph"
|
||||
defaultMessage="If you need help, you can always open an {issuesLink} in the repository or ask a question in our {discussLink} forum."
|
||||
values={{
|
||||
discussLink: (
|
||||
<EuiLink href="https://discuss.elastic.co/c/enterprise-search/84" target="_blank">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.config.discussLink',
|
||||
{ defaultMessage: 'Discuss' }
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
issuesLink: (
|
||||
<EuiLink href="https://github.com/elastic/connectors-python/issues" target="_blank">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.config.issuesLink',
|
||||
{ defaultMessage: 'issue' }
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<EuiCallOut
|
||||
iconType="warning"
|
||||
color="warning"
|
||||
title={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.config.warning.title',
|
||||
{ defaultMessage: 'This connector is tied to your Elastic index' }
|
||||
)}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.warning.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'If you sync at least one document before you’ve finalized your connector client, you will have to recreate your search index.',
|
||||
}
|
||||
)}
|
||||
</EuiCallOut>
|
||||
</EuiText>
|
||||
</ConnectorConfigurationConfig>
|
||||
);
|
||||
};
|
|
@ -122,6 +122,7 @@ export const NativeConnectorConfiguration: React.FC = () => {
|
|||
{
|
||||
children: (
|
||||
<NativeConnectorConfigurationConfig
|
||||
connector={index.connector}
|
||||
nativeConnector={nativeConnector}
|
||||
status={index.connector.status}
|
||||
/>
|
||||
|
|
|
@ -7,27 +7,55 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import { EuiSpacer, EuiLink, EuiText, EuiFlexGroup, EuiFlexItem, EuiCallOut } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { ConnectorStatus } from '@kbn/search-connectors';
|
||||
import { Connector, ConnectorStatus } from '@kbn/search-connectors';
|
||||
|
||||
import { ConnectorConfigurationComponent } from '@kbn/search-connectors/components/configuration/connector_configuration';
|
||||
|
||||
import { Status } from '../../../../../../../common/types/api';
|
||||
|
||||
import { docLinks } from '../../../../../shared/doc_links';
|
||||
import { HttpLogic } from '../../../../../shared/http';
|
||||
import { LicensingLogic } from '../../../../../shared/licensing';
|
||||
|
||||
import { ConnectorConfigurationConfig } from '../connector_configuration_config';
|
||||
import { ConnectorConfigurationApiLogic } from '../../../../api/connector/update_connector_configuration_api_logic';
|
||||
import { IndexNameLogic } from '../../index_name_logic';
|
||||
import { ConnectorDefinition } from '../types';
|
||||
|
||||
interface NativeConnectorConfigurationConfigProps {
|
||||
connector: Connector;
|
||||
nativeConnector: ConnectorDefinition;
|
||||
status: ConnectorStatus;
|
||||
}
|
||||
|
||||
export const NativeConnectorConfigurationConfig: React.FC<
|
||||
NativeConnectorConfigurationConfigProps
|
||||
> = ({ nativeConnector, status }) => {
|
||||
> = ({ connector, nativeConnector, status }) => {
|
||||
const { hasPlatinumLicense } = useValues(LicensingLogic);
|
||||
const { indexName } = useValues(IndexNameLogic);
|
||||
const { status: updateStatus } = useValues(ConnectorConfigurationApiLogic);
|
||||
const { makeRequest } = useActions(ConnectorConfigurationApiLogic);
|
||||
const { http } = useValues(HttpLogic);
|
||||
return (
|
||||
<ConnectorConfigurationConfig>
|
||||
<ConnectorConfigurationComponent
|
||||
connector={connector}
|
||||
hasPlatinumLicense={hasPlatinumLicense}
|
||||
isLoading={updateStatus === Status.LOADING}
|
||||
saveConfig={(configuration) =>
|
||||
makeRequest({
|
||||
configuration,
|
||||
connectorId: connector.id,
|
||||
indexName,
|
||||
})
|
||||
}
|
||||
subscriptionLink={docLinks.licenseManagement}
|
||||
stackManagementLink={http.basePath.prepend('/app/management/stack/license_management')}
|
||||
>
|
||||
<EuiText size="s">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnector.config.encryptionWarningMessage',
|
||||
|
@ -82,6 +110,6 @@ export const NativeConnectorConfigurationConfig: React.FC<
|
|||
/>
|
||||
</>
|
||||
)}
|
||||
</ConnectorConfigurationConfig>
|
||||
</ConnectorConfigurationComponent>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -11,9 +11,7 @@ import { isEqual } from 'lodash';
|
|||
|
||||
import {
|
||||
FilteringConfig,
|
||||
FilteringPolicy,
|
||||
FilteringRule,
|
||||
FilteringRuleRule,
|
||||
FilteringValidation,
|
||||
FilteringValidationState,
|
||||
} from '@kbn/search-connectors';
|
||||
|
@ -90,15 +88,15 @@ interface ConnectorFilteringValues {
|
|||
status: Status;
|
||||
}
|
||||
|
||||
function createDefaultRule(order: number) {
|
||||
function createDefaultRule(order: number): FilteringRule {
|
||||
const now = new Date().toISOString();
|
||||
return {
|
||||
created_at: now,
|
||||
field: '_',
|
||||
id: 'DEFAULT',
|
||||
order,
|
||||
policy: FilteringPolicy.INCLUDE,
|
||||
rule: FilteringRuleRule.REGEX,
|
||||
policy: 'include',
|
||||
rule: 'regex',
|
||||
updated_at: now,
|
||||
value: '.*',
|
||||
};
|
||||
|
@ -237,7 +235,7 @@ export const ConnectorFilteringLogic = kea<
|
|||
[],
|
||||
{
|
||||
addFilteringRule: (filteringRules, filteringRule) => {
|
||||
const newFilteringRules = filteringRules.length
|
||||
const newFilteringRules: FilteringRule[] = filteringRules.length
|
||||
? [
|
||||
...filteringRules.slice(0, filteringRules.length - 1),
|
||||
filteringRule,
|
||||
|
|
|
@ -23,7 +23,12 @@ import {
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { FilteringPolicy, FilteringRule, FilteringRuleRule } from '@kbn/search-connectors';
|
||||
import {
|
||||
filteringPolicyToText,
|
||||
filteringRuleToText,
|
||||
FilteringRule,
|
||||
FilteringRuleRuleValues,
|
||||
} from '@kbn/search-connectors';
|
||||
|
||||
import { docLinks } from '../../../../../shared/doc_links';
|
||||
|
||||
|
@ -38,11 +43,6 @@ import {
|
|||
} from '../../../../../shared/tables/inline_editable_table/types';
|
||||
import { ItemWithAnID } from '../../../../../shared/tables/types';
|
||||
|
||||
import {
|
||||
filteringPolicyToText,
|
||||
filteringRuleToText,
|
||||
} from '../../../../utils/filtering_rule_helpers';
|
||||
|
||||
import { IndexViewLogic } from '../../index_view_logic';
|
||||
|
||||
import { ConnectorFilteringLogic } from './connector_filtering_logic';
|
||||
|
@ -50,7 +50,7 @@ import { ConnectorFilteringLogic } from './connector_filtering_logic';
|
|||
const instanceId = 'FilteringRulesTable';
|
||||
|
||||
function validateItem(filteringRule: FilteringRule): FormErrors {
|
||||
if (filteringRule.rule === FilteringRuleRule.REGEX) {
|
||||
if (filteringRule.rule === 'regex') {
|
||||
try {
|
||||
new RegExp(filteringRule.value);
|
||||
return {};
|
||||
|
@ -97,12 +97,12 @@ export const SyncRulesTable: React.FC = () => {
|
|||
onChange={(e) => onChange(e.target.value)}
|
||||
options={[
|
||||
{
|
||||
text: filteringPolicyToText(FilteringPolicy.INCLUDE),
|
||||
value: FilteringPolicy.INCLUDE,
|
||||
text: filteringPolicyToText('include'),
|
||||
value: 'include',
|
||||
},
|
||||
{
|
||||
text: filteringPolicyToText(FilteringPolicy.EXCLUDE),
|
||||
value: FilteringPolicy.EXCLUDE,
|
||||
text: filteringPolicyToText('exclude'),
|
||||
value: 'exclude',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
@ -140,7 +140,7 @@ export const SyncRulesTable: React.FC = () => {
|
|||
fullWidth
|
||||
value={filteringRule.rule}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
options={Object.values(FilteringRuleRule).map((rule) => ({
|
||||
options={Object.values(FilteringRuleRuleValues).map((rule) => ({
|
||||
text: filteringRuleToText(rule),
|
||||
value: rule,
|
||||
}))}
|
||||
|
@ -184,8 +184,8 @@ export const SyncRulesTable: React.FC = () => {
|
|||
)}
|
||||
columns={columns}
|
||||
defaultItem={{
|
||||
policy: FilteringPolicy.INCLUDE,
|
||||
rule: FilteringRuleRule.EQUALS,
|
||||
policy: 'include',
|
||||
rule: 'equals',
|
||||
value: '',
|
||||
}}
|
||||
description={description}
|
||||
|
|
|
@ -25,6 +25,10 @@ import {
|
|||
StartIncrementalSyncArgs,
|
||||
} from '../../api/connector/start_incremental_sync_api_logic';
|
||||
import { StartSyncApiLogic, StartSyncArgs } from '../../api/connector/start_sync_api_logic';
|
||||
import {
|
||||
ConnectorConfigurationApiLogic,
|
||||
PostConnectorConfigurationActions,
|
||||
} from '../../api/connector/update_connector_configuration_api_logic';
|
||||
import {
|
||||
CachedFetchIndexApiLogic,
|
||||
CachedFetchIndexApiLogicActions,
|
||||
|
@ -69,6 +73,7 @@ export interface IndexViewActions {
|
|||
startSync(): void;
|
||||
stopFetchIndexPoll(): CachedFetchIndexApiLogicActions['stopPolling'];
|
||||
stopFetchIndexPoll(): void;
|
||||
updateConfigurationApiSuccess: PostConnectorConfigurationActions['apiSuccess'];
|
||||
}
|
||||
|
||||
export interface IndexViewValues {
|
||||
|
@ -124,6 +129,8 @@ export const IndexViewLogic = kea<MakeLogicType<IndexViewValues, IndexViewAction
|
|||
'startPolling as startFetchIndexPoll',
|
||||
'stopPolling as stopFetchIndexPoll',
|
||||
],
|
||||
ConnectorConfigurationApiLogic,
|
||||
['apiSuccess as updateConfigurationApiSuccess'],
|
||||
StartSyncApiLogic,
|
||||
['apiSuccess as startSyncApiSuccess', 'makeRequest as makeStartSyncRequest'],
|
||||
StartIncrementalSyncApiLogic,
|
||||
|
@ -204,6 +211,14 @@ export const IndexViewLogic = kea<MakeLogicType<IndexViewValues, IndexViewAction
|
|||
actions.makeStartSyncRequest({ connectorId: values.fetchIndexApiData.connector.id });
|
||||
}
|
||||
},
|
||||
updateConfigurationApiSuccess: ({ configuration }) => {
|
||||
if (isConnectorIndex(values.fetchIndexApiData)) {
|
||||
actions.fetchIndexApiSuccess({
|
||||
...values.fetchIndexApiData,
|
||||
connector: { ...values.fetchIndexApiData.connector, configuration },
|
||||
});
|
||||
}
|
||||
},
|
||||
}),
|
||||
path: ['enterprise_search', 'content', 'index_view_logic'],
|
||||
reducers: {
|
||||
|
|
|
@ -1,58 +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 { syncJobView } from '../../../__mocks__/sync_job.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { SyncStatus, TriggerMethod } from '@kbn/search-connectors';
|
||||
|
||||
import { SyncJobCallouts } from './sync_callouts';
|
||||
|
||||
describe('SyncCalloutsPanel', () => {
|
||||
it('renders', () => {
|
||||
const wrapper = shallow(<SyncJobCallouts syncJob={syncJobView} />);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it('renders error job', () => {
|
||||
const wrapper = shallow(
|
||||
<SyncJobCallouts syncJob={{ ...syncJobView, status: SyncStatus.ERROR }} />
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it('renders canceled job', () => {
|
||||
const wrapper = shallow(
|
||||
<SyncJobCallouts syncJob={{ ...syncJobView, status: SyncStatus.CANCELED }} />
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it('renders in progress job', () => {
|
||||
const wrapper = shallow(
|
||||
<SyncJobCallouts syncJob={{ ...syncJobView, status: SyncStatus.IN_PROGRESS }} />
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it('renders different trigger method', () => {
|
||||
const wrapper = shallow(
|
||||
<SyncJobCallouts
|
||||
syncJob={{
|
||||
...syncJobView,
|
||||
status: SyncStatus.IN_PROGRESS,
|
||||
trigger_method: TriggerMethod.SCHEDULED,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -5,19 +5,22 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { useValues } from 'kea';
|
||||
import { type } from 'io-ts';
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import { EuiButtonGroup } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { SyncJobsTable } from '@kbn/search-connectors';
|
||||
|
||||
import { KibanaLogic } from '../../../../shared/kibana';
|
||||
|
||||
import { IndexViewLogic } from '../index_view_logic';
|
||||
|
||||
import { SyncJobsHistoryTable } from './sync_jobs_history_table';
|
||||
import { SyncJobsViewLogic } from './sync_jobs_view_logic';
|
||||
|
||||
export const SyncJobs: React.FC = () => {
|
||||
const { hasDocumentLevelSecurityFeature } = useValues(IndexViewLogic);
|
||||
|
@ -25,6 +28,19 @@ export const SyncJobs: React.FC = () => {
|
|||
const [selectedSyncJobCategory, setSelectedSyncJobCategory] = useState<string>('content');
|
||||
const shouldShowAccessSyncs =
|
||||
productFeatures.hasDocumentLevelSecurityEnabled && hasDocumentLevelSecurityFeature;
|
||||
const { connectorId, syncJobsPagination: pagination, syncJobs } = useValues(SyncJobsViewLogic);
|
||||
const { fetchSyncJobs } = useActions(SyncJobsViewLogic);
|
||||
|
||||
useEffect(() => {
|
||||
if (connectorId) {
|
||||
fetchSyncJobs({
|
||||
connectorId,
|
||||
from: pagination.pageIndex * (pagination.pageSize || 0),
|
||||
size: pagination.pageSize ?? 10,
|
||||
type: selectedSyncJobCategory as 'access_control' | 'content',
|
||||
});
|
||||
}
|
||||
}, [connectorId, selectedSyncJobCategory, type]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -62,9 +78,37 @@ export const SyncJobs: React.FC = () => {
|
|||
/>
|
||||
)}
|
||||
{selectedSyncJobCategory === 'content' ? (
|
||||
<SyncJobsHistoryTable type="content" />
|
||||
<SyncJobsTable
|
||||
onPaginate={({ page: { index, size } }) => {
|
||||
if (connectorId) {
|
||||
fetchSyncJobs({
|
||||
connectorId,
|
||||
from: index * size,
|
||||
size,
|
||||
type: selectedSyncJobCategory,
|
||||
});
|
||||
}
|
||||
}}
|
||||
pagination={pagination}
|
||||
syncJobs={syncJobs}
|
||||
type="content"
|
||||
/>
|
||||
) : (
|
||||
<SyncJobsHistoryTable type="access_control" />
|
||||
<SyncJobsTable
|
||||
onPaginate={({ page: { index, size } }) => {
|
||||
if (connectorId) {
|
||||
fetchSyncJobs({
|
||||
connectorId,
|
||||
from: index * size,
|
||||
size,
|
||||
type: 'access_control',
|
||||
});
|
||||
}
|
||||
}}
|
||||
pagination={pagination}
|
||||
syncJobs={syncJobs}
|
||||
type="access_control"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,179 +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, { useEffect, useState } from 'react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import { EuiBadge, EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { SyncJobType, SyncStatus } from '@kbn/search-connectors';
|
||||
|
||||
import { syncJobTypeToText, syncStatusToColor, syncStatusToText } from '@kbn/search-connectors';
|
||||
|
||||
import { FormattedDateTime } from '../../../../shared/formatted_date_time';
|
||||
import { pageToPagination } from '../../../../shared/pagination/page_to_pagination';
|
||||
|
||||
import { durationToText } from '../../../utils/duration_to_text';
|
||||
|
||||
import { IndexViewLogic } from '../index_view_logic';
|
||||
|
||||
import { SyncJobFlyout } from './sync_job_flyout';
|
||||
import { SyncJobsViewLogic, SyncJobView } from './sync_jobs_view_logic';
|
||||
|
||||
interface SyncJobHistoryTableProps {
|
||||
type: 'content' | 'access_control';
|
||||
}
|
||||
|
||||
export const SyncJobsHistoryTable: React.FC<SyncJobHistoryTableProps> = ({ type }) => {
|
||||
const { connectorId } = useValues(IndexViewLogic);
|
||||
const { fetchSyncJobs } = useActions(SyncJobsViewLogic);
|
||||
const { syncJobs, syncJobsLoading, syncJobsPagination } = useValues(SyncJobsViewLogic);
|
||||
const [syncJobFlyout, setSyncJobFlyout] = useState<SyncJobView | undefined>(undefined);
|
||||
|
||||
const columns: Array<EuiBasicTableColumn<SyncJobView>> = [
|
||||
{
|
||||
field: 'lastSync',
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.syncJobs.lastSync.columnTitle', {
|
||||
defaultMessage: 'Last sync',
|
||||
}),
|
||||
render: (lastSync: string) => <FormattedDateTime date={new Date(lastSync)} />,
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
field: 'duration',
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.syncJobs.syncDuration.columnTitle', {
|
||||
defaultMessage: 'Sync duration',
|
||||
}),
|
||||
render: (duration: moment.Duration) => durationToText(duration),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
...(type === 'content'
|
||||
? [
|
||||
{
|
||||
field: 'indexed_document_count',
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndices.addedDocs.columnTitle',
|
||||
{
|
||||
defaultMessage: 'Docs added',
|
||||
}
|
||||
),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
field: 'deleted_document_count',
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndices.deletedDocs.columnTitle',
|
||||
{
|
||||
defaultMessage: 'Docs deleted',
|
||||
}
|
||||
),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
field: 'job_type',
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndices.syncJobType.columnTitle',
|
||||
{
|
||||
defaultMessage: 'Content sync type',
|
||||
}
|
||||
),
|
||||
render: (syncType: SyncJobType) => {
|
||||
const syncJobTypeText = syncJobTypeToText(syncType);
|
||||
if (syncJobTypeText.length === 0) return null;
|
||||
return <EuiBadge color="hollow">{syncJobTypeText}</EuiBadge>;
|
||||
},
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(type === 'access_control'
|
||||
? [
|
||||
{
|
||||
field: 'indexed_document_count',
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.searchIndices.identitySync.columnTitle',
|
||||
{
|
||||
defaultMessage: 'Identities synced',
|
||||
}
|
||||
),
|
||||
sortable: true,
|
||||
truncateText: true,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
field: 'status',
|
||||
name: i18n.translate('xpack.enterpriseSearch.content.searchIndices.syncStatus.columnTitle', {
|
||||
defaultMessage: 'Status',
|
||||
}),
|
||||
render: (syncStatus: SyncStatus) => (
|
||||
<EuiBadge color={syncStatusToColor(syncStatus)}>{syncStatusToText(syncStatus)}</EuiBadge>
|
||||
),
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
description: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.syncJobs.actions.viewJob.title',
|
||||
{
|
||||
defaultMessage: 'View this sync job',
|
||||
}
|
||||
),
|
||||
icon: 'eye',
|
||||
isPrimary: false,
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.syncJobs.actions.viewJob.caption',
|
||||
{
|
||||
defaultMessage: 'View this sync job',
|
||||
}
|
||||
),
|
||||
onClick: (job) => setSyncJobFlyout(job),
|
||||
type: 'icon',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (connectorId) {
|
||||
fetchSyncJobs({
|
||||
connectorId,
|
||||
from: syncJobsPagination.from ?? 0,
|
||||
size: syncJobsPagination.size ?? 10,
|
||||
type,
|
||||
});
|
||||
}
|
||||
}, [connectorId, type]);
|
||||
return (
|
||||
<>
|
||||
<SyncJobFlyout onClose={() => setSyncJobFlyout(undefined)} syncJob={syncJobFlyout} />
|
||||
<EuiBasicTable
|
||||
data-test-subj={`entSearchContent-index-${type}-syncJobs-table`}
|
||||
items={syncJobs}
|
||||
columns={columns}
|
||||
hasActions
|
||||
onChange={({ page: { index, size } }: { page: { index: number; size: number } }) => {
|
||||
if (connectorId) {
|
||||
fetchSyncJobs({ connectorId, from: index * size, size, type });
|
||||
}
|
||||
}}
|
||||
pagination={pageToPagination(syncJobsPagination)}
|
||||
tableLayout="fixed"
|
||||
loading={syncJobsLoading}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -28,10 +28,9 @@ const DEFAULT_VALUES = {
|
|||
syncJobsData: undefined,
|
||||
syncJobsLoading: true,
|
||||
syncJobsPagination: {
|
||||
from: 0,
|
||||
has_more_hits_than_total: false,
|
||||
size: 10,
|
||||
total: 0,
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
totalItemCount: 0,
|
||||
},
|
||||
syncJobsStatus: Status.IDLE,
|
||||
};
|
||||
|
@ -116,10 +115,9 @@ describe('SyncJobsViewLogic', () => {
|
|||
},
|
||||
syncJobsLoading: false,
|
||||
syncJobsPagination: {
|
||||
from: 40,
|
||||
has_more_hits_than_total: false,
|
||||
size: 20,
|
||||
total: 50,
|
||||
pageIndex: 2,
|
||||
pageSize: 20,
|
||||
totalItemCount: 50,
|
||||
},
|
||||
syncJobsStatus: Status.SUCCESS,
|
||||
});
|
||||
|
@ -176,10 +174,9 @@ describe('SyncJobsViewLogic', () => {
|
|||
},
|
||||
syncJobsLoading: false,
|
||||
syncJobsPagination: {
|
||||
from: 40,
|
||||
has_more_hits_than_total: false,
|
||||
size: 20,
|
||||
total: 50,
|
||||
pageIndex: 2,
|
||||
pageSize: 20,
|
||||
totalItemCount: 50,
|
||||
},
|
||||
syncJobsStatus: Status.SUCCESS,
|
||||
});
|
||||
|
|
|
@ -9,11 +9,12 @@ import { kea, MakeLogicType } from 'kea';
|
|||
|
||||
import moment from 'moment';
|
||||
|
||||
import { ConnectorSyncJob } from '@kbn/search-connectors';
|
||||
import { Pagination } from '@elastic/eui';
|
||||
import { ConnectorSyncJob, pageToPagination } from '@kbn/search-connectors';
|
||||
|
||||
import { Status } from '../../../../../../common/types/api';
|
||||
|
||||
import { Page, Paginate } from '../../../../../../common/types/pagination';
|
||||
import { Paginate } from '../../../../../../common/types/pagination';
|
||||
import { Actions } from '../../../../shared/api_logic/create_api_logic';
|
||||
import {
|
||||
FetchSyncJobsApiLogic,
|
||||
|
@ -38,7 +39,7 @@ export interface IndexViewValues {
|
|||
syncJobs: SyncJobView[];
|
||||
syncJobsData: Paginate<ConnectorSyncJob> | null;
|
||||
syncJobsLoading: boolean;
|
||||
syncJobsPagination: Page;
|
||||
syncJobsPagination: Pagination;
|
||||
syncJobsStatus: Status;
|
||||
}
|
||||
|
||||
|
@ -84,14 +85,13 @@ export const SyncJobsViewLogic = kea<MakeLogicType<IndexViewValues, IndexViewAct
|
|||
],
|
||||
syncJobsPagination: [
|
||||
() => [selectors.syncJobsData],
|
||||
(data?: Paginate<ConnectorSyncJob>) =>
|
||||
(data?: Paginate<ConnectorSyncJob>): Pagination =>
|
||||
data
|
||||
? data._meta.page
|
||||
? pageToPagination(data._meta.page)
|
||||
: {
|
||||
from: 0,
|
||||
has_more_hits_than_total: false,
|
||||
size: 10,
|
||||
total: 0,
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
totalItemCount: 0,
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
|
|
@ -11,9 +11,13 @@ import { EuiBasicTable, EuiBasicTableColumn, EuiCode } from '@elastic/eui';
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { FilteringRule, FilteringPolicy, FilteringRuleRule } from '@kbn/search-connectors';
|
||||
|
||||
import { filteringPolicyToText, filteringRuleToText } from '../../../utils/filtering_rule_helpers';
|
||||
import {
|
||||
filteringPolicyToText,
|
||||
filteringRuleToText,
|
||||
FilteringRule,
|
||||
FilteringPolicy,
|
||||
FilteringRuleRule,
|
||||
} from '@kbn/search-connectors';
|
||||
|
||||
interface FilteringRulesTableProps {
|
||||
filteringRules: FilteringRule[];
|
||||
|
|
|
@ -1,19 +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 moment from 'moment';
|
||||
|
||||
export function durationToText(input?: moment.Duration): string {
|
||||
if (input) {
|
||||
const hours = input.hours();
|
||||
const minutes = input.minutes();
|
||||
const seconds = input.seconds();
|
||||
return `${hours}h ${minutes}m ${seconds}s`;
|
||||
} else {
|
||||
return '--';
|
||||
}
|
||||
}
|
|
@ -1,78 +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 { i18n } from '@kbn/i18n';
|
||||
|
||||
import { FilteringPolicy, FilteringRuleRule } from '@kbn/search-connectors';
|
||||
|
||||
const filteringRuleStringMap: Record<FilteringRuleRule, string> = {
|
||||
[FilteringRuleRule.CONTAINS]: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.filteringRules.rules.contains',
|
||||
{
|
||||
defaultMessage: 'Contains',
|
||||
}
|
||||
),
|
||||
[FilteringRuleRule.ENDS_WITH]: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.filteringRules.rules.endsWith',
|
||||
{
|
||||
defaultMessage: 'Ends with',
|
||||
}
|
||||
),
|
||||
[FilteringRuleRule.EQUALS]: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.filteringRules.rules.equals',
|
||||
{
|
||||
defaultMessage: 'Equals',
|
||||
}
|
||||
),
|
||||
[FilteringRuleRule.GT]: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.filteringRules.rules.greaterThan',
|
||||
{
|
||||
defaultMessage: 'Greater than',
|
||||
}
|
||||
),
|
||||
[FilteringRuleRule.LT]: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.filteringRules.rules.lessThan',
|
||||
{
|
||||
defaultMessage: 'Less than',
|
||||
}
|
||||
),
|
||||
[FilteringRuleRule.REGEX]: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.filteringRules.rules.regEx',
|
||||
{
|
||||
defaultMessage: 'Regular expression',
|
||||
}
|
||||
),
|
||||
[FilteringRuleRule.STARTS_WITH]: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.filteringRules.rules.startsWith',
|
||||
{
|
||||
defaultMessage: 'Starts with',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
export function filteringRuleToText(filteringRule: FilteringRuleRule): string {
|
||||
return filteringRuleStringMap[filteringRule];
|
||||
}
|
||||
|
||||
const filteringPolicyStringMap: Record<FilteringPolicy, string> = {
|
||||
[FilteringPolicy.EXCLUDE]: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.filteringRules.policy.exclude',
|
||||
{
|
||||
defaultMessage: 'Exclude',
|
||||
}
|
||||
),
|
||||
[FilteringPolicy.INCLUDE]: i18n.translate(
|
||||
'xpack.enterpriseSearch.content.filteringRules.policy.include',
|
||||
{
|
||||
defaultMessage: 'Include',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
export function filteringPolicyToText(filteringPolicy: FilteringPolicy): string {
|
||||
return filteringPolicyStringMap[filteringPolicy];
|
||||
}
|
|
@ -19,13 +19,7 @@ import {
|
|||
updateFilteringDraft,
|
||||
} from '@kbn/search-connectors';
|
||||
|
||||
import {
|
||||
ConnectorStatus,
|
||||
FilteringPolicy,
|
||||
FilteringRule,
|
||||
FilteringRuleRule,
|
||||
SyncJobType,
|
||||
} from '@kbn/search-connectors';
|
||||
import { ConnectorStatus, FilteringRule, SyncJobType } from '@kbn/search-connectors';
|
||||
import { cancelSyncs } from '@kbn/search-connectors/lib/cancel_syncs';
|
||||
|
||||
import { ErrorCode } from '../../../common/types/error_codes';
|
||||
|
@ -39,7 +33,6 @@ import { RouteDependencies } from '../../plugin';
|
|||
import { createError } from '../../utils/create_error';
|
||||
import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler';
|
||||
import { isAccessControlDisabledException } from '../../utils/identify_exceptions';
|
||||
import { validateEnum } from '../../utils/validate_enum';
|
||||
|
||||
export function registerConnectorRoutes({ router, log }: RouteDependencies) {
|
||||
router.post(
|
||||
|
@ -391,12 +384,8 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) {
|
|||
field: schema.string(),
|
||||
id: schema.string(),
|
||||
order: schema.number(),
|
||||
policy: schema.string({
|
||||
validate: validateEnum(FilteringPolicy, 'policy'),
|
||||
}),
|
||||
rule: schema.string({
|
||||
validate: validateEnum(FilteringRuleRule, 'rule'),
|
||||
}),
|
||||
policy: schema.string(),
|
||||
rule: schema.string(),
|
||||
updated_at: schema.string(),
|
||||
value: schema.string(),
|
||||
})
|
||||
|
@ -433,12 +422,8 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) {
|
|||
field: schema.string(),
|
||||
id: schema.string(),
|
||||
order: schema.number(),
|
||||
policy: schema.string({
|
||||
validate: validateEnum(FilteringPolicy, 'policy'),
|
||||
}),
|
||||
rule: schema.string({
|
||||
validate: validateEnum(FilteringRuleRule, 'rule'),
|
||||
}),
|
||||
policy: schema.string(),
|
||||
rule: schema.string(),
|
||||
updated_at: schema.string(),
|
||||
value: schema.string(),
|
||||
})
|
||||
|
|
|
@ -23,6 +23,10 @@ export const SAVE_LABEL: string = i18n.translate('xpack.serverlessSearch.save',
|
|||
defaultMessage: 'Save',
|
||||
});
|
||||
|
||||
export const UPDATE_LABEL: string = i18n.translate('xpack.serverlessSearch.update', {
|
||||
defaultMessage: 'Update',
|
||||
});
|
||||
|
||||
export const BACK_LABEL: string = i18n.translate('xpack.serverlessSearch.back', {
|
||||
defaultMessage: 'Back',
|
||||
});
|
||||
|
@ -74,3 +78,14 @@ export const COPY_CONNECTOR_ID_LABEL = i18n.translate(
|
|||
defaultMessage: 'Copy connector id',
|
||||
}
|
||||
);
|
||||
|
||||
export const OVERVIEW_LABEL = i18n.translate('xpack.serverlessSearch.connectors.overviewLabel', {
|
||||
defaultMessage: 'Overview',
|
||||
});
|
||||
|
||||
export const CONFIGURATION_LABEL = i18n.translate(
|
||||
'xpack.serverlessSearch.connectors.configurationLabel',
|
||||
{
|
||||
defaultMessage: 'Configuration',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -27,8 +27,8 @@ import React, { useState } from 'react';
|
|||
import { useKibanaServices } from '../../hooks/use_kibana';
|
||||
import { MANAGEMENT_API_KEYS } from '../../../../common/routes';
|
||||
import { CreateApiKeyFlyout } from './create_api_key_flyout';
|
||||
import { CreateApiKeyResponse } from './types';
|
||||
import './api_key.scss';
|
||||
import { CreateApiKeyResponse } from '../../hooks/api/use_create_api_key';
|
||||
|
||||
export const ApiKeyPanel = ({ setClientApiKey }: { setClientApiKey: (value: string) => void }) => {
|
||||
const { http, user } = useKibanaServices();
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
import {
|
||||
|
@ -29,7 +29,6 @@ import {
|
|||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import {
|
||||
CANCEL_LABEL,
|
||||
|
@ -38,14 +37,11 @@ import {
|
|||
INVALID_JSON_ERROR,
|
||||
REQUIRED_LABEL,
|
||||
} from '../../../../common/i18n_string';
|
||||
import { CreateAPIKeyArgs } from '../../../../common/types';
|
||||
import { useKibanaServices } from '../../hooks/use_kibana';
|
||||
import { CREATE_API_KEY_PATH } from '../../../../common/routes';
|
||||
import { isApiError } from '../../../utils/api';
|
||||
import { BasicSetupForm, DEFAULT_EXPIRES_VALUE } from './basic_setup_form';
|
||||
import { MetadataForm } from './metadata_form';
|
||||
import { SecurityPrivilegesForm } from './security_privileges_form';
|
||||
import { CreateApiKeyResponse } from './types';
|
||||
import { CreateApiKeyResponse, useCreateApiKey } from '../../hooks/api/use_create_api_key';
|
||||
|
||||
const DEFAULT_ROLE_DESCRIPTORS = `{
|
||||
"serverless_search": {
|
||||
|
@ -84,7 +80,6 @@ export const CreateApiKeyFlyout: React.FC<CreateApiKeyFlyoutProps> = ({
|
|||
setApiKey,
|
||||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const { http } = useKibanaServices();
|
||||
const [name, setName] = useState('');
|
||||
const [expires, setExpires] = useState<string | null>(DEFAULT_EXPIRES_VALUE);
|
||||
const [roleDescriptors, setRoleDescriptors] = useState(DEFAULT_ROLE_DESCRIPTORS);
|
||||
|
@ -142,18 +137,15 @@ export const CreateApiKeyFlyout: React.FC<CreateApiKeyFlyoutProps> = ({
|
|||
});
|
||||
};
|
||||
|
||||
const { isLoading, isError, error, mutate } = useMutation({
|
||||
mutationFn: async (input: CreateAPIKeyArgs) => {
|
||||
const result = await http.post<CreateApiKeyResponse>(CREATE_API_KEY_PATH, {
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
return result;
|
||||
},
|
||||
onSuccess: (apiKey) => {
|
||||
setApiKey(apiKey);
|
||||
const { data, isLoading, isError, isSuccess, error, mutate } = useCreateApiKey();
|
||||
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
setApiKey(data);
|
||||
onClose();
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
const createError = parseCreateError(error);
|
||||
return (
|
||||
<EuiFlyout
|
||||
|
|
|
@ -1,15 +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.
|
||||
*/
|
||||
|
||||
export interface CreateApiKeyResponse {
|
||||
id: string;
|
||||
name: string;
|
||||
expiration?: number;
|
||||
api_key: string;
|
||||
encoded?: string;
|
||||
beats_logstash_format: string;
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiPanel,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiTitle,
|
||||
EuiBadge,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiCode,
|
||||
EuiButton,
|
||||
EuiCodeBlock,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Connector, CONNECTORS_INDEX } from '@kbn/search-connectors';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { OPTIONAL_LABEL } from '../../../../../common/i18n_string';
|
||||
import { useCreateApiKey } from '../../../hooks/api/use_create_api_key';
|
||||
interface ApiKeyPanelProps {
|
||||
connector: Connector;
|
||||
}
|
||||
export const ApiKeyPanel: React.FC<ApiKeyPanelProps> = ({ connector }) => {
|
||||
const { data, isLoading, mutate } = useCreateApiKey();
|
||||
return (
|
||||
<EuiPanel hasBorder>
|
||||
<EuiFlexGroup direction="row" justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.config.apiKeyTitle', {
|
||||
defaultMessage: 'Prepare an API key',
|
||||
})}
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge>{OPTIONAL_LABEL}</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<EuiText color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.serverlessSearch.connectors.config.apiKeyDescription"
|
||||
defaultMessage="You can limit the connector's API key to only have access to the above index. Once created, use this key to set the {apiKey} variable in your {config} file."
|
||||
values={{
|
||||
apiKey: <EuiCode>api_key</EuiCode>,
|
||||
config: <EuiCode>config.yml</EuiCode>,
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
<span>
|
||||
<EuiButton
|
||||
isDisabled={!connector.index_name}
|
||||
isLoading={isLoading}
|
||||
iconType="plusInCircle"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
if (connector.index_name) {
|
||||
mutate({
|
||||
name: `${connector.index_name}-connector`,
|
||||
role_descriptors: {
|
||||
[`${connector.index_name}-connector-role`]: {
|
||||
cluster: ['monitor'],
|
||||
index: [
|
||||
{
|
||||
names: [
|
||||
connector.index_name,
|
||||
connector.index_name.replace(
|
||||
/^(?:search-)?(.*)$/,
|
||||
'.search-acl-filter-$1'
|
||||
),
|
||||
`${CONNECTORS_INDEX}*`,
|
||||
],
|
||||
privileges: ['all'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.config.createApikeyLabel', {
|
||||
defaultMessage: 'New API key',
|
||||
})}
|
||||
</EuiButton>
|
||||
</span>
|
||||
<EuiSpacer />
|
||||
{Boolean(data) && <EuiCodeBlock isCopyable>{data?.encoded}</EuiCodeBlock>}
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 { EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Connector, ConnectorConfigurationComponent } from '@kbn/search-connectors';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useConnector } from '../../../hooks/api/use_connector';
|
||||
import { useEditConnectorConfiguration } from '../../../hooks/api/use_connector_configuration';
|
||||
|
||||
interface ConnectorConfigFieldsProps {
|
||||
connector: Connector;
|
||||
}
|
||||
|
||||
export const ConnectorConfigFields: React.FC<ConnectorConfigFieldsProps> = ({ connector }) => {
|
||||
const { data, isLoading, isSuccess, mutate, reset } = useEditConnectorConfiguration(connector.id);
|
||||
const { queryKey } = useConnector(connector.id);
|
||||
const queryClient = useQueryClient();
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
queryClient.setQueryData(queryKey, { connector: { ...connector, configuration: data } });
|
||||
queryClient.invalidateQueries(queryKey);
|
||||
reset();
|
||||
}
|
||||
}, [data, isSuccess, connector, queryClient, queryKey, reset]);
|
||||
return (
|
||||
<EuiFlexGroup direction="column" alignItems="center" justifyContent="center">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="s">
|
||||
<h2>
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.config.connectorConfigTitle', {
|
||||
defaultMessage: 'Configure your connector',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText color="subdued">
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.config.connectorConfigDescription', {
|
||||
defaultMessage:
|
||||
'Your connector is set up. Now you can enter access details for your data source. This ensures the connector can find content and is authorized to access it.',
|
||||
})}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<ConnectorConfigurationComponent
|
||||
connector={connector}
|
||||
hasPlatinumLicense={false}
|
||||
isLoading={isLoading}
|
||||
saveConfig={mutate}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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 { EuiPanel, EuiSpacer } from '@elastic/eui';
|
||||
import { Connector, ConnectorConfigurationComponent } from '@kbn/search-connectors';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useConnector } from '../../../hooks/api/use_connector';
|
||||
import { useEditConnectorConfiguration } from '../../../hooks/api/use_connector_configuration';
|
||||
import { ConnectorIndexnamePanel } from './connector_index_name_panel';
|
||||
|
||||
interface ConnectorConfigurationPanels {
|
||||
connector: Connector;
|
||||
}
|
||||
|
||||
export const ConnectorConfigurationPanels: React.FC<ConnectorConfigurationPanels> = ({
|
||||
connector,
|
||||
}) => {
|
||||
const { data, isLoading, isSuccess, mutate, reset } = useEditConnectorConfiguration(connector.id);
|
||||
const { queryKey } = useConnector(connector.id);
|
||||
const queryClient = useQueryClient();
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
queryClient.setQueryData(queryKey, { connector: { ...connector, configuration: data } });
|
||||
queryClient.invalidateQueries(queryKey);
|
||||
reset();
|
||||
}
|
||||
}, [data, isSuccess, connector, queryClient, queryKey, reset]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiPanel hasBorder>
|
||||
<ConnectorConfigurationComponent
|
||||
connector={connector}
|
||||
hasPlatinumLicense={false}
|
||||
isLoading={isLoading}
|
||||
saveConfig={mutate}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
</EuiPanel>
|
||||
<EuiSpacer />
|
||||
<EuiPanel hasBorder>
|
||||
<ConnectorIndexnamePanel connector={connector} />
|
||||
</EuiPanel>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* 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, { useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
Connector,
|
||||
ConnectorStatus,
|
||||
pageToPagination,
|
||||
SyncJobsTable,
|
||||
} from '@kbn/search-connectors';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiStepsHorizontal,
|
||||
EuiStepsHorizontalProps,
|
||||
EuiTabbedContent,
|
||||
EuiTabbedContentTab,
|
||||
Pagination,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CONFIGURATION_LABEL, OVERVIEW_LABEL } from '../../../../../common/i18n_string';
|
||||
import { ConnectorLinkElasticsearch } from './connector_link';
|
||||
import { ConnectorConfigFields } from './connector_config_fields';
|
||||
import { ConnectorIndexName } from './connector_index_name';
|
||||
import { useSyncJobs } from '../../../hooks/api/use_sync_jobs';
|
||||
import { ConnectorConfigurationPanels } from './connector_config_panels';
|
||||
|
||||
interface ConnectorConfigurationProps {
|
||||
connector: Connector;
|
||||
}
|
||||
|
||||
type ConnectorConfigurationStep = 'link' | 'configure' | 'connect' | 'connected';
|
||||
|
||||
export const ConnectorConfiguration: React.FC<ConnectorConfigurationProps> = ({ connector }) => {
|
||||
const [currentStep, setCurrentStep] = useState<ConnectorConfigurationStep>('link');
|
||||
useEffect(() => {
|
||||
const step =
|
||||
connector.status === ConnectorStatus.CREATED
|
||||
? 'link'
|
||||
: connector.status === ConnectorStatus.NEEDS_CONFIGURATION
|
||||
? 'configure'
|
||||
: connector.status === ConnectorStatus.CONFIGURED
|
||||
? 'connect'
|
||||
: 'connected';
|
||||
setCurrentStep(step);
|
||||
}, [connector.status, setCurrentStep]);
|
||||
const steps: EuiStepsHorizontalProps['steps'] = [
|
||||
{
|
||||
title: i18n.translate('xpack.serverlessSearch.connectors.config.linkToElasticTitle', {
|
||||
defaultMessage: 'Link to Elasticsearch',
|
||||
}),
|
||||
status:
|
||||
currentStep === 'link'
|
||||
? 'current'
|
||||
: connector.status === ConnectorStatus.CREATED
|
||||
? 'incomplete'
|
||||
: 'complete',
|
||||
onClick: () => setCurrentStep('link'),
|
||||
size: 's',
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.serverlessSearch.connectors.config.configureTitle', {
|
||||
defaultMessage: 'Configure',
|
||||
}),
|
||||
status:
|
||||
currentStep === 'configure'
|
||||
? 'current'
|
||||
: connector.status === ConnectorStatus.CONFIGURED ||
|
||||
connector.status === ConnectorStatus.CONNECTED
|
||||
? 'complete'
|
||||
: 'incomplete',
|
||||
onClick: () => setCurrentStep('configure'),
|
||||
size: 's',
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.serverlessSearch.connectors.config.connectTitle', {
|
||||
defaultMessage: 'Connect Index',
|
||||
}),
|
||||
status:
|
||||
currentStep === 'connect'
|
||||
? 'current'
|
||||
: connector.status === ConnectorStatus.CONNECTED && connector.index_name
|
||||
? 'complete'
|
||||
: 'incomplete',
|
||||
onClick: () => setCurrentStep('connect'),
|
||||
size: 's',
|
||||
},
|
||||
];
|
||||
const [pagination, setPagination] = useState<Omit<Pagination, 'totalItemCount'>>({
|
||||
pageIndex: 0,
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
const { data: syncJobsData, isLoading: syncJobsLoading } = useSyncJobs(connector.id, pagination);
|
||||
|
||||
const tabs: EuiTabbedContentTab[] = [
|
||||
{
|
||||
content: (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<SyncJobsTable
|
||||
isLoading={syncJobsLoading}
|
||||
onPaginate={({ page }) => setPagination({ pageIndex: page.index, pageSize: page.size })}
|
||||
pagination={
|
||||
syncJobsData
|
||||
? pageToPagination(syncJobsData?._meta.page)
|
||||
: { pageIndex: 0, pageSize: 20, totalItemCount: 0 }
|
||||
}
|
||||
syncJobs={syncJobsData?.data || []}
|
||||
type="content"
|
||||
/>
|
||||
</>
|
||||
),
|
||||
id: 'overview',
|
||||
name: OVERVIEW_LABEL,
|
||||
},
|
||||
{
|
||||
content: (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<ConnectorConfigurationPanels connector={connector} />
|
||||
</>
|
||||
),
|
||||
id: 'configuration',
|
||||
name: CONFIGURATION_LABEL,
|
||||
},
|
||||
];
|
||||
|
||||
return currentStep === 'connected' ? (
|
||||
<EuiTabbedContent tabs={tabs} />
|
||||
) : (
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiStepsHorizontal size="s" steps={steps} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
{currentStep === 'link' && (
|
||||
<ConnectorLinkElasticsearch
|
||||
connectorId={connector.id}
|
||||
serviceType={connector.service_type || ''}
|
||||
status={connector.status}
|
||||
/>
|
||||
)}
|
||||
{currentStep === 'configure' && <ConnectorConfigFields connector={connector} />}
|
||||
{currentStep === 'connect' && <ConnectorIndexName connector={connector} />}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* 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 { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Connector } from '@kbn/search-connectors';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useQueryClient, useMutation } from '@tanstack/react-query';
|
||||
import { isValidIndexName } from '../../../../utils/validate_index_name';
|
||||
import { SAVE_LABEL } from '../../../../../common/i18n_string';
|
||||
import { useConnector } from '../../../hooks/api/use_connector';
|
||||
import { useKibanaServices } from '../../../hooks/use_kibana';
|
||||
import { ApiKeyPanel } from './api_key_panel';
|
||||
import { ConnectorIndexNameForm } from './connector_index_name_form';
|
||||
|
||||
interface ConnectorIndexNameProps {
|
||||
connector: Connector;
|
||||
}
|
||||
|
||||
export const ConnectorIndexName: React.FC<ConnectorIndexNameProps> = ({ connector }) => {
|
||||
const { http, notifications } = useKibanaServices();
|
||||
const queryClient = useQueryClient();
|
||||
const { queryKey } = useConnector(connector.id);
|
||||
const { data, error, isLoading, isSuccess, mutate, reset } = useMutation({
|
||||
mutationFn: async ({ inputName, sync }: { inputName: string | null; sync?: boolean }) => {
|
||||
if (inputName && inputName !== connector.index_name) {
|
||||
const body = { index_name: inputName };
|
||||
await http.post(`/internal/serverless_search/connectors/${connector.id}/index_name`, {
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
if (sync) {
|
||||
await http.post(`/internal/serverless_search/connectors/${connector.id}/sync`);
|
||||
}
|
||||
return inputName;
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
queryClient.setQueryData(queryKey, { connector: { ...connector, index_name: data } });
|
||||
queryClient.invalidateQueries(queryKey);
|
||||
reset();
|
||||
}
|
||||
}, [data, isSuccess, connector, queryClient, queryKey, reset]);
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
notifications.toasts.addError(error as Error, {
|
||||
title: i18n.translate('xpack.serverlessSearch.connectors.config.connectorIndexNameError', {
|
||||
defaultMessage: 'Error updating index name',
|
||||
}),
|
||||
});
|
||||
}
|
||||
}, [error, notifications]);
|
||||
|
||||
const [newIndexName, setNewIndexname] = useState(connector.index_name);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup direction="column" alignItems="center" justifyContent="center">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="s">
|
||||
<h2>
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.config.connectorIndexNameTitle', {
|
||||
defaultMessage: 'Link index',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText color="subdued">
|
||||
{i18n.translate(
|
||||
'xpack.serverlessSearch.connectors.config.connectorIndexNameDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Pick an index where your documents will be synced, or create a new one for this connector.',
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<ConnectorIndexNameForm
|
||||
indexName={newIndexName || ''}
|
||||
onChange={(name) => setNewIndexname(name)}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<ApiKeyPanel connector={connector} />
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<span>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
isDisabled={!isValidIndexName(newIndexName)}
|
||||
isLoading={isLoading}
|
||||
onClick={() => mutate({ inputName: newIndexName, sync: false })}
|
||||
>
|
||||
{SAVE_LABEL}
|
||||
</EuiButton>
|
||||
</span>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<span>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
disabled={!isValidIndexName(newIndexName)}
|
||||
fill
|
||||
isLoading={isLoading}
|
||||
onClick={() => mutate({ inputName: newIndexName, sync: true })}
|
||||
>
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.config.saveSyncLabel', {
|
||||
defaultMessage: 'Save and sync',
|
||||
})}
|
||||
</EuiButton>
|
||||
</span>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 { EuiForm, EuiFormRow, EuiComboBox } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { isValidIndexName } from '../../../../utils/validate_index_name';
|
||||
import { useIndexNameSearch } from '../../../hooks/api/use_index_name_search';
|
||||
|
||||
interface ConnectorIndexNameFormProps {
|
||||
indexName: string;
|
||||
isDisabled?: boolean;
|
||||
onChange: (output: string) => void;
|
||||
}
|
||||
|
||||
export const ConnectorIndexNameForm: React.FC<ConnectorIndexNameFormProps> = ({
|
||||
indexName,
|
||||
onChange,
|
||||
isDisabled,
|
||||
}) => {
|
||||
const [query, setQuery] = useState('');
|
||||
const { data: indexNames, isLoading: isLoadingIndices, refetch } = useIndexNameSearch(query);
|
||||
|
||||
useEffect(() => {
|
||||
refetch();
|
||||
}, [query, refetch]);
|
||||
|
||||
const [newIndexName, setNewIndexName] = useState(indexName);
|
||||
|
||||
useEffect(() => {
|
||||
onChange(newIndexName);
|
||||
}, [newIndexName, onChange]);
|
||||
|
||||
return (
|
||||
<EuiForm fullWidth>
|
||||
<EuiFormRow
|
||||
error={
|
||||
!isValidIndexName(newIndexName || '')
|
||||
? i18n.translate('xpack.serverlessSearch.connectors.indexNameErrorText', {
|
||||
defaultMessage:
|
||||
'Names should be lowercase and cannot contain spaces or special characters.',
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
isInvalid={!!newIndexName && !isValidIndexName(newIndexName)}
|
||||
label={i18n.translate('xpack.serverlessSearch.connectors.config.indexNameLabel', {
|
||||
defaultMessage: 'Create or select an index',
|
||||
})}
|
||||
fullWidth
|
||||
helpText={i18n.translate('xpack.serverlessSearch.connectors.indexNameInputHelpText', {
|
||||
defaultMessage:
|
||||
'Names should be lowercase and cannot contain spaces or special characters.',
|
||||
})}
|
||||
>
|
||||
<EuiComboBox
|
||||
async
|
||||
isClearable={false}
|
||||
customOptionText={i18n.translate(
|
||||
'xpack.serverlessSearch.connectors.config.createIndexLabel',
|
||||
{
|
||||
defaultMessage: 'The connector will create the index {searchValue}',
|
||||
values: { searchValue: '{searchValue}' },
|
||||
}
|
||||
)}
|
||||
isDisabled={isDisabled}
|
||||
isLoading={isLoadingIndices}
|
||||
onChange={(values) => {
|
||||
if (values[0].value) {
|
||||
setNewIndexName(values[0].value);
|
||||
}
|
||||
}}
|
||||
onCreateOption={(value) => setNewIndexName(value)}
|
||||
onSearchChange={(value) => setQuery(value)}
|
||||
options={(indexNames?.index_names || []).map((name) => ({
|
||||
label: name,
|
||||
value: name,
|
||||
}))}
|
||||
selectedOptions={newIndexName ? [{ label: newIndexName, value: newIndexName }] : []}
|
||||
singleSelection
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
);
|
||||
};
|
|
@ -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 { EuiFlexGroup, EuiFlexItem, EuiButton, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Connector } from '@kbn/search-connectors';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { isValidIndexName } from '../../../../utils/validate_index_name';
|
||||
import { UPDATE_LABEL } from '../../../../../common/i18n_string';
|
||||
import { useConnector } from '../../../hooks/api/use_connector';
|
||||
import { useKibanaServices } from '../../../hooks/use_kibana';
|
||||
import { ConnectorIndexNameForm } from './connector_index_name_form';
|
||||
|
||||
interface ConnectorIndexNamePanelProps {
|
||||
connector: Connector;
|
||||
}
|
||||
|
||||
export const ConnectorIndexnamePanel: React.FC<ConnectorIndexNamePanelProps> = ({ connector }) => {
|
||||
const { http, notifications } = useKibanaServices();
|
||||
const { data, error, isLoading, isSuccess, mutate, reset } = useMutation({
|
||||
mutationFn: async (inputName: string) => {
|
||||
if (inputName && inputName !== connector.index_name) {
|
||||
const body = { index_name: inputName };
|
||||
await http.post(`/internal/serverless_search/connectors/${connector.id}/index_name`, {
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
return inputName;
|
||||
},
|
||||
});
|
||||
const queryClient = useQueryClient();
|
||||
const { queryKey } = useConnector(connector.id);
|
||||
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
queryClient.setQueryData(queryKey, { connector: { ...connector, index_name: data } });
|
||||
queryClient.invalidateQueries(queryKey);
|
||||
reset();
|
||||
}
|
||||
}, [data, isSuccess, connector, queryClient, queryKey, reset]);
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
notifications.toasts.addError(error as Error, {
|
||||
title: i18n.translate('xpack.serverlessSearch.connectors.config.connectorIndexNameError', {
|
||||
defaultMessage: 'Error updating index name',
|
||||
}),
|
||||
});
|
||||
}
|
||||
}, [error, notifications]);
|
||||
|
||||
const [newIndexName, setNewIndexName] = useState(connector.index_name || '');
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConnectorIndexNameForm
|
||||
isDisabled={isLoading}
|
||||
indexName={newIndexName}
|
||||
onChange={(name) => setNewIndexName(name)}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup alignItems="flexEnd" direction="row">
|
||||
<EuiFlexItem>
|
||||
<span>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
disabled={!isValidIndexName(newIndexName)}
|
||||
fill
|
||||
isLoading={isLoading}
|
||||
onClick={() => mutate(newIndexName)}
|
||||
>
|
||||
{UPDATE_LABEL}
|
||||
</EuiButton>
|
||||
</span>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiButton,
|
||||
EuiCallOut,
|
||||
EuiCode,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ConnectorStatus } from '@kbn/search-connectors';
|
||||
import React from 'react';
|
||||
import { docLinks } from '../../../../../common/doc_links';
|
||||
import { useAssetBasePath } from '../../../hooks/use_asset_base_path';
|
||||
|
||||
interface ConnectorLinkElasticsearchProps {
|
||||
connectorId: string;
|
||||
serviceType: string;
|
||||
status: ConnectorStatus;
|
||||
}
|
||||
|
||||
export const ConnectorLinkElasticsearch: React.FC<ConnectorLinkElasticsearchProps> = ({
|
||||
connectorId,
|
||||
serviceType,
|
||||
status,
|
||||
}) => {
|
||||
const assetBasePath = useAssetBasePath();
|
||||
return (
|
||||
<EuiFlexGroup direction="column" alignItems="center" justifyContent="center">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="s">
|
||||
<h2>
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.config.link.linkToElasticTitle', {
|
||||
defaultMessage: 'Link your connector to Elasticsearch',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText color="subdued">
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.config.linkToElasticDescription', {
|
||||
defaultMessage:
|
||||
'You need to run the connector code on your own infrastructure and link it to your Elasticsearch instance. You have two options:',
|
||||
})}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="row" alignItems="center" justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<span>
|
||||
<EuiButton iconType={`${assetBasePath}/docker.svg`} href={docLinks.connectors} fill>
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.runWithDockerLink', {
|
||||
defaultMessage: 'Run with Docker',
|
||||
})}
|
||||
</EuiButton>
|
||||
</span>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<span>
|
||||
<EuiButton
|
||||
iconType={`${assetBasePath}/github_white.svg`}
|
||||
href="https://github.com/elastic/connectors-python"
|
||||
fill
|
||||
>
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.runFromSourceLink', {
|
||||
defaultMessage: 'Run from source',
|
||||
})}
|
||||
</EuiButton>
|
||||
</span>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<EuiFlexItem>
|
||||
<EuiPanel hasBorder>
|
||||
<EuiTitle size="xs">
|
||||
<h3>
|
||||
{i18n.translate('xpack.serverlessSearch.connectors.variablesTitle', {
|
||||
defaultMessage: 'Variables for your ',
|
||||
})}
|
||||
<EuiCode>connectors-python/config.yml</EuiCode>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">
|
||||
<strong>connector_id</strong>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiCode>{connectorId}</EuiCode>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">
|
||||
<strong>service_type</strong>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{Boolean(serviceType) && <EuiCode>{serviceType}</EuiCode>}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{status === ConnectorStatus.CREATED && (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.serverlessSearch.connectors.waitingForConnection', {
|
||||
defaultMessage: 'Waiting for connection',
|
||||
})}
|
||||
color="warning"
|
||||
iconType="iInCircle"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -11,12 +11,11 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPageTemplate,
|
||||
EuiPanel,
|
||||
EuiPopover,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Connector } from '@kbn/search-connectors';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -31,6 +30,8 @@ import { EditName } from './edit_name';
|
|||
import { EditServiceType } from './edit_service_type';
|
||||
import { EditDescription } from './edit_description';
|
||||
import { DeleteConnectorModal } from './delete_connector_modal';
|
||||
import { ConnectorConfiguration } from './connector_config/connector_configuration';
|
||||
import { useConnector } from '../../hooks/api/use_connector';
|
||||
|
||||
export const EditConnector: React.FC = () => {
|
||||
const [deleteModalIsOpen, setDeleteModalIsOpen] = useState(false);
|
||||
|
@ -41,14 +42,9 @@ export const EditConnector: React.FC = () => {
|
|||
useEffect(() => setDeleteModalIsOpen(false), [id, setDeleteModalIsOpen]);
|
||||
const {
|
||||
application: { navigateToUrl },
|
||||
http,
|
||||
} = useKibanaServices();
|
||||
|
||||
const { data, isLoading, refetch } = useQuery({
|
||||
queryKey: [`fetchConnector${id}`],
|
||||
queryFn: () =>
|
||||
http.fetch<{ connector: Connector }>(`/internal/serverless_search/connector/${id}`),
|
||||
});
|
||||
const { data, isLoading, refetch } = useConnector(id);
|
||||
|
||||
if (isLoading) {
|
||||
<EuiPageTemplate offset={0} grow restrictWidth data-test-subj="svlSearchEditConnectorsPage">
|
||||
|
@ -91,7 +87,7 @@ export const EditConnector: React.FC = () => {
|
|||
|
||||
return (
|
||||
<EuiPageTemplate offset={0} grow restrictWidth data-test-subj="svlSearchEditConnectorsPage">
|
||||
<EuiPageTemplate.Section grow={false}>
|
||||
<EuiPageTemplate.Section grow={false} color="subdued">
|
||||
<EuiText size="s">{CONNECTOR_LABEL}</EuiText>
|
||||
<EuiFlexGroup direction="row" justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
|
@ -154,7 +150,7 @@ export const EditConnector: React.FC = () => {
|
|||
</EuiPageTemplate.Section>
|
||||
<EuiPageTemplate.Section>
|
||||
<EuiFlexGroup direction="row">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexItem grow={1}>
|
||||
<EditServiceType
|
||||
connectorId={id}
|
||||
serviceType={connector.service_type ?? ''}
|
||||
|
@ -167,6 +163,11 @@ export const EditConnector: React.FC = () => {
|
|||
onSuccess={refetch}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={2}>
|
||||
<EuiPanel hasBorder hasShadow={false}>
|
||||
<ConnectorConfiguration connector={connector} />
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPageTemplate.Section>
|
||||
</EuiPageTemplate>
|
||||
|
|
|
@ -16,7 +16,7 @@ export const EDIT_CONNECTOR_PATH = `${BASE_CONNECTORS_PATH}/:id`;
|
|||
export const ConnectorsRouter: React.FC = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path={'/:id'}>
|
||||
<Route exact path={'/:id'}>
|
||||
<EditConnector />
|
||||
</Route>
|
||||
<Route exact path="/">
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 '@kbn/search-connectors';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useKibanaServices } from '../use_kibana';
|
||||
|
||||
export const useConnector = (id: string) => {
|
||||
const { http } = useKibanaServices();
|
||||
const queryKey = ['fetchConnector', id];
|
||||
const result = useQuery({
|
||||
queryKey,
|
||||
queryFn: () =>
|
||||
http.fetch<{ connector: Connector }>(`/internal/serverless_search/connector/${id}`),
|
||||
});
|
||||
return { queryKey, ...result };
|
||||
};
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 { useMutation } from '@tanstack/react-query';
|
||||
import { useKibanaServices } from '../use_kibana';
|
||||
|
||||
export const useEditConnectorConfiguration = (connectorId: string) => {
|
||||
const { http } = useKibanaServices();
|
||||
return useMutation({
|
||||
mutationFn: async (configuration: Record<string, string | number | boolean | null>) => {
|
||||
const body = { configuration };
|
||||
const result = await http.post(
|
||||
`/internal/serverless_search/connectors/${connectorId}/configuration`,
|
||||
{
|
||||
body: JSON.stringify(body),
|
||||
}
|
||||
);
|
||||
return result;
|
||||
},
|
||||
});
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 { useMutation } from '@tanstack/react-query';
|
||||
import { CREATE_API_KEY_PATH } from '../../../../common/routes';
|
||||
import { CreateAPIKeyArgs } from '../../../../common/types';
|
||||
import { useKibanaServices } from '../use_kibana';
|
||||
|
||||
export interface CreateApiKeyResponse {
|
||||
id: string;
|
||||
name: string;
|
||||
expiration?: number;
|
||||
api_key: string;
|
||||
encoded?: string;
|
||||
beats_logstash_format: string;
|
||||
}
|
||||
|
||||
export const useCreateApiKey = () => {
|
||||
const { http } = useKibanaServices();
|
||||
return useMutation({
|
||||
mutationFn: async (input: CreateAPIKeyArgs) => {
|
||||
const result = await http.post<CreateApiKeyResponse>(CREATE_API_KEY_PATH, {
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
return result;
|
||||
},
|
||||
});
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { useQuery } from '@tanstack/react-query';
|
||||
import { useKibanaServices } from '../use_kibana';
|
||||
|
||||
export const useIndexNameSearch = (query: string) => {
|
||||
const { http } = useKibanaServices();
|
||||
return useQuery({
|
||||
queryKey: ['fetchIndexNames', query],
|
||||
queryFn: async () =>
|
||||
http.fetch<{ index_names: string[] }>('/internal/serverless_search/index_names', {
|
||||
query: { query },
|
||||
}),
|
||||
});
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 { Pagination } from '@elastic/eui';
|
||||
import { ConnectorSyncJob, Paginate } from '@kbn/search-connectors';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useKibanaServices } from '../use_kibana';
|
||||
|
||||
export const useSyncJobs = (
|
||||
connectorId: string,
|
||||
pagination: Omit<Pagination, 'totalItemCount'>
|
||||
) => {
|
||||
const { http } = useKibanaServices();
|
||||
return useQuery({
|
||||
keepPreviousData: true,
|
||||
queryKey: ['fetchSyncJobs', pagination],
|
||||
queryFn: async () =>
|
||||
http.fetch<Paginate<ConnectorSyncJob>>(
|
||||
`/internal/serverless_search/connectors/${connectorId}/sync_jobs`,
|
||||
{
|
||||
query: {
|
||||
from: pagination.pageIndex * (pagination.pageSize || 10),
|
||||
size: pagination.pageSize || 10,
|
||||
},
|
||||
}
|
||||
),
|
||||
});
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
<svg width="16" height="12" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.04961 5.554H10.7016V4.0675H9.05011V5.554H9.04961ZM7.09711 5.554H8.74911V4.0675H7.09711V5.554ZM5.14461 5.554H6.79661V4.0675H5.14511V5.554H5.14461ZM3.19211 5.554H4.84511V4.0675H3.19211V5.554ZM1.24011 5.554H2.89211V4.0675H1.24011V5.554ZM3.19211 3.77H4.84511V2.284H3.19211V3.77ZM5.14461 3.77H6.79661V2.284H5.14511V3.77H5.14461ZM7.09711 3.77H8.74911V2.284H7.09711V3.77ZM7.09711 1.9865H8.74911V0.5H7.09711V1.9865ZM15.6666 4.6875C15.3056 4.4485 14.4766 4.361 13.8386 4.48C13.7566 3.885 13.4216 3.3695 12.8126 2.9035L12.4626 2.672L12.2286 3.019C11.9296 3.4655 11.7801 4.084 11.8286 4.6775C11.8511 4.8865 11.9201 5.2595 12.1371 5.5875C11.9206 5.703 11.4921 5.862 10.9271 5.8515H0.062113L0.040613 5.975C-0.061387 6.5715 -0.059387 8.432 1.16061 9.862C2.08911 10.949 3.48011 11.5 5.29511 11.5C9.23011 11.5 12.1416 9.707 13.5051 6.448C14.0416 6.458 15.1956 6.451 15.7886 5.3295C15.8041 5.304 15.8396 5.2365 15.9436 5.0245L16.0001 4.9075L15.6666 4.6875Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 0C3.58203 0 0 3.67167 0 8.2002C0 11.8238 2.292 14.8969 5.47119 15.981C5.87109 16.0561 6.01709 15.8028 6.01709 15.5866C6.01709 15.3914 6.00977 14.7447 6.00586 14.0601C3.78125 14.5555 3.31103 13.0931 3.31103 13.0931C2.94678 12.1461 2.42284 11.8939 2.42284 11.8939C1.6958 11.3854 2.478 11.3954 2.478 11.3954C3.28122 11.4524 3.70409 12.2402 3.70409 12.2402C4.41797 13.4935 5.57714 13.1311 6.03222 12.9209C6.10494 12.3924 6.31197 12.03 6.54003 11.8258C4.76416 11.6186 2.896 10.9149 2.896 7.77276C2.896 6.87686 3.20802 6.14615 3.71875 5.57207C3.6372 5.36386 3.36181 4.52952 3.79784 3.4009C3.79784 3.4009 4.46875 3.18069 5.99803 4.24175C6.63572 4.05907 7.31981 3.96898 7.99998 3.96597C8.67967 3.96898 9.36423 4.06006 10.0029 4.24274C11.5293 3.18068 12.2012 3.4019 12.2012 3.4019C12.6387 4.53152 12.3633 5.36485 12.2812 5.57207C12.7939 6.14615 13.1035 6.87688 13.1035 7.77276C13.1035 10.9229 11.2324 11.6166 9.4502 11.8198C9.7383 12.0741 9.99317 12.5726 9.99317 13.3373C9.99317 14.4334 9.98242 15.3173 9.98242 15.5876C9.98242 15.8058 10.1279 16.0611 10.5332 15.981C13.71 14.8949 16 11.8218 16 8.2002C16 3.67167 12.418 0 8 0Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html for the current rules
|
||||
|
||||
export function isValidIndexName(name: string | undefined | null): boolean {
|
||||
if (!name) return false;
|
||||
const byteLength = encodeURI(name).split(/%(?:u[0-9A-F]{2})?[0-9A-F]{2}|./).length - 1;
|
||||
const reg = new RegExp('[\\\\/:*?"<>|\\s,#]+');
|
||||
const indexPatternInvalid =
|
||||
byteLength > 255 || // name can't be greater than 255 bytes
|
||||
name !== name.toLowerCase() || // name should be lowercase
|
||||
name === '.' ||
|
||||
name === '..' || // name can't be . or ..
|
||||
name.match(/^[-_+]/) !== null || // name can't start with these chars
|
||||
name.match(reg) !== null; // name can't contain these chars
|
||||
|
||||
return !indexPatternInvalid;
|
||||
}
|
|
@ -12,7 +12,10 @@ import {
|
|||
deleteConnectorById,
|
||||
fetchConnectorById,
|
||||
fetchConnectors,
|
||||
fetchSyncJobsByConnectorId,
|
||||
startConnectorSync,
|
||||
updateConnectorConfiguration,
|
||||
updateConnectorIndexName,
|
||||
updateConnectorNameAndDescription,
|
||||
updateConnectorServiceType,
|
||||
} from '@kbn/search-connectors';
|
||||
|
@ -170,6 +173,38 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
|
|||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
{
|
||||
path: '/internal/serverless_search/connectors/{connectorId}/index_name',
|
||||
validate: {
|
||||
body: schema.object({
|
||||
index_name: schema.string(),
|
||||
}),
|
||||
params: schema.object({
|
||||
connectorId: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
try {
|
||||
const result = await updateConnectorIndexName(
|
||||
client.asCurrentUser,
|
||||
request.params.connectorId,
|
||||
request.body.index_name
|
||||
);
|
||||
return response.ok({
|
||||
body: {
|
||||
result,
|
||||
},
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
} catch (e) {
|
||||
return response.conflict({ body: e });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
{
|
||||
path: '/internal/serverless_search/connectors/{connectorId}/service_type',
|
||||
|
@ -244,9 +279,60 @@ export const registerConnectorsRoutes = ({ http, router }: RouteDependencies) =>
|
|||
);
|
||||
|
||||
return response.ok({
|
||||
body: {
|
||||
result,
|
||||
},
|
||||
body: result,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
{
|
||||
path: '/internal/serverless_search/connectors/{connectorId}/sync',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
connectorId: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
const result = await startConnectorSync(client.asCurrentUser, {
|
||||
connectorId: request.params.connectorId,
|
||||
});
|
||||
|
||||
return response.ok({
|
||||
body: result,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/serverless_search/connectors/{connectorId}/sync_jobs',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
connectorId: schema.string(),
|
||||
}),
|
||||
query: schema.object({
|
||||
from: schema.maybe(schema.number()),
|
||||
size: schema.maybe(schema.number()),
|
||||
type: schema.maybe(schema.string()),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
const result = await fetchSyncJobsByConnectorId(
|
||||
client.asCurrentUser,
|
||||
request.params.connectorId,
|
||||
request.query.from || 0,
|
||||
request.query.size || 20,
|
||||
(request.query.type as 'content' | 'access_control' | 'all' | undefined) || 'all'
|
||||
);
|
||||
|
||||
return response.ok({
|
||||
body: result,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { IndicesIndexState } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { fetchIndices } from '../lib/indices/fetch_indices';
|
||||
|
@ -44,4 +45,41 @@ export const registerIndicesRoutes = ({ router, security }: RouteDependencies) =
|
|||
});
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/serverless_search/index_names',
|
||||
validate: {
|
||||
query: schema.object({
|
||||
query: schema.maybe(schema.string()),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const client = (await context.core).elasticsearch.client.asCurrentUser;
|
||||
|
||||
const result = await client.indices.get({
|
||||
expand_wildcards: 'open',
|
||||
index: request.query.query ? `*${request.query.query}*` : '*',
|
||||
});
|
||||
return response.ok({
|
||||
body: {
|
||||
index_names: Object.keys(result || {}).filter(
|
||||
(indexName) => !isHidden(result[indexName]) && !isClosed(result[indexName])
|
||||
),
|
||||
},
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function isHidden(index?: IndicesIndexState): boolean {
|
||||
return index?.settings?.index?.hidden === true || index?.settings?.index?.hidden === 'true';
|
||||
}
|
||||
function isClosed(index?: IndicesIndexState): boolean {
|
||||
return (
|
||||
index?.settings?.index?.verified_before_close === true ||
|
||||
index?.settings?.index?.verified_before_close === 'true'
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12037,13 +12037,8 @@
|
|||
"xpack.enterpriseSearch.content.index.connector.syncRules.flyout.errorTitle": "{ids} {idsLength, plural, one {règle} many {règles} other {règles}} de synchronisation {idsLength, plural, one {est} many {sont} other {sont}} non valide(s).",
|
||||
"xpack.enterpriseSearch.content.index.pipelines.copyCustomizeCallout.description": "Votre index utilise notre pipeline d'ingestion par défaut {defaultPipeline}. Copiez ce pipeline dans une configuration spécifique à l'index pour déverrouiller la possibilité de créer des pipelines d'ingestion et d'inférence personnalisés.",
|
||||
"xpack.enterpriseSearch.content.index.pipelines.ingestFlyout.modalBodyAPIText": "{apiIndex} Les modifications apportées aux paramètres ci-dessous sont uniquement fournies à titre indicatif. Ces paramètres ne seront pas conservés dans votre index ou pipeline.",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.volume.aboutLabel": "À propos de {volume}",
|
||||
"xpack.enterpriseSearch.content.indices.callout.text": "Vos index Elasticsearch sont maintenant au premier plan d'Enterprise Search. Vous pouvez créer des index et lancer directement des expériences de recherche avec ces index. Pour en savoir plus sur l'utilisation des index de Elasticsearch dans Enterprise Search {docLink}",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.description": "D'abord, générez une clé d'API Elasticsearch. Cette clé {apiKeyName} permet d'activer les autorisations de lecture et d'écriture du connecteur pour qu'il puisse indexer les documents dans l'index {indexName} créé. Enregistrez cette clé en lieu sûr, car vous en aurez besoin pour configurer votre connecteur.",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.defaultValue": "Si cette option est laissée vide, la valeur par défaut {defaultValue} sera utilisée.",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.description.firstParagraph": "Maintenant que votre connecteur est déployé, améliorez le client du connecteur pour votre source de données personnalisée. Il y en a plusieurs {link} que vous pouvez personnaliser avec votre propre logique d'implémentation supplémentaire.",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.description.thirdParagraph": "Si vous avez besoin d'aide, vous pouvez toujours ouvrir un {issuesLink} dans le référentiel ou bien poser une question dans notre forum {discussLink}.",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.invalidInteger": "{label} doit être un nombre entier.",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.connectorConnected": "Votre connecteur {name} est connecté à Enterprise Search.",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.description.secondParagraph": "Le référentiel de connecteurs contient plusieurs {link}. Utilisez notre framework de développement accéléré avec des sources de données personnalisées.",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.description.thirdParagraph": "Dans cette étape, vous devez cloner ou dupliquer le référentiel, puis copier la clé d'API et l'ID de connecteur générés au {link} associé. L'ID de connecteur identifie ce connecteur auprès d'Enterprise Search. Le type de service détermine pour quel type de source de données le connecteur est configuré.",
|
||||
|
@ -12084,11 +12079,6 @@
|
|||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.onlySearchOptimized.tooltipContent": "Les index optimisés pour la recherche sont précédés du préfixe {code}. Ils sont gérés par des mécanismes d'ingestion tels que des robots d'indexation, des connecteurs ou des API d'ingestion.",
|
||||
"xpack.enterpriseSearch.content.settings.description": "Ces paramètres s'appliquent à tous les nouveaux index Elasticsearch créés par des mécanismes d'ingestion Enterprise Search. Pour les index basés sur l'ingestion d'API, n'oubliez pas d'inclure le pipeline lorsque vous ingérez des documents. Ces fonctionnalités sont alimentées par {link}.",
|
||||
"xpack.enterpriseSearch.content.shared.result.header.metadata.icon.ariaLabel": "Métadonnées pour le document : {id}",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.canceledDescription": "Synchronisation annulée à {date}",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.completedDescription": "Terminé à {date}",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.failureDescription": "Échec de la synchronisation : {error}.",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.inProgressDescription": "La synchronisation est en cours depuis {duration}.",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.startedAtDescription": "Démarré à {date}",
|
||||
"xpack.enterpriseSearch.crawler.action.deleteDomain.confirmationPopupMessage": "Voulez-vous vraiment supprimer le domaine \"{domainUrl}\" et tous ses paramètres ?",
|
||||
"xpack.enterpriseSearch.crawler.addDomainForm.entryPointLabel": "Le point d'entrée du robot d'indexation a été défini sur {entryPointValue}",
|
||||
"xpack.enterpriseSearch.crawler.authenticationPanel.emptyPrompt.description": "Cliquer sur {addAuthenticationButtonLabel} afin de fournir les informations d'identification nécessaires pour indexer le contenu protégé",
|
||||
|
@ -13165,8 +13155,6 @@
|
|||
"xpack.enterpriseSearch.betaLabel": "Bêta",
|
||||
"xpack.enterpriseSearch.connector.connectorTypePanel.title": "Type de connecteur",
|
||||
"xpack.enterpriseSearch.connector.connectorTypePanel.unknown.label": "Inconnu",
|
||||
"xpack.enterpriseSearch.connector.documentLevelSecurity.enablePanel.description": "Vous permet de contrôler les documents auxquels peuvent accéder les utilisateurs, selon leurs autorisations. Cela permet de vous assurer que les résultats de recherche ne renvoient que des informations pertinentes et autorisées pour les utilisateurs, selon leurs rôles.",
|
||||
"xpack.enterpriseSearch.connector.documentLevelSecurity.enablePanel.heading": "Sécurité au niveau du document",
|
||||
"xpack.enterpriseSearch.connector.ingestionStatus.title": "Statut de l'ingestion",
|
||||
"xpack.enterpriseSearch.content,overview.documentExample.clientLibraries.label": "Bibliothèques de clients",
|
||||
"xpack.enterpriseSearch.content.analytics.api.generateAnalyticsApiKeyModal.apiKeyWarning": "Elastic ne stocke pas les clés d’API. Une fois la clé générée, vous ne pourrez la visualiser qu'une seule fois. Veillez à l'enregistrer dans un endroit sûr. Si vous n'y avez plus accès, vous devrez générer une nouvelle clé d’API à partir de cet écran.",
|
||||
|
@ -13210,15 +13198,6 @@
|
|||
"xpack.enterpriseSearch.content.crawler.extractionRulesTable.emptyMessageTitle": "Il n'existe aucune règle d'extraction de contenu",
|
||||
"xpack.enterpriseSearch.content.crawler.siteMaps": "Plans de site",
|
||||
"xpack.enterpriseSearch.content.description": "Enterprise Search offre un certain nombre de moyens de rendre vos données facilement interrogeables. Vous pouvez choisir entre le robot d'indexation, les indices Elasticsearch, l'API, les téléchargements directs ou les connecteurs tiers.",
|
||||
"xpack.enterpriseSearch.content.filteringRules.policy.exclude": "Exclure",
|
||||
"xpack.enterpriseSearch.content.filteringRules.policy.include": "Inclure",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.contains": "Contient",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.endsWith": "Se termine par",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.equals": "Est égal à",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.greaterThan": "Supérieur à",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.lessThan": "Inférieur à",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.regEx": "Expression régulière",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.startsWith": "Commence par",
|
||||
"xpack.enterpriseSearch.content.index.connector.filtering.successToastRules.title": "Règles de synchronisation mises à jour",
|
||||
"xpack.enterpriseSearch.content.index.connector.filteringRules.regExError": "La valeur doit être une expression régulière",
|
||||
"xpack.enterpriseSearch.content.index.connector.syncRules.advancedFiltersDescription": "Ces règles s'appliquent avant l'obtention des données auprès de la source de données.",
|
||||
|
@ -13285,48 +13264,12 @@
|
|||
"xpack.enterpriseSearch.content.index.syncButton.label": "Sync",
|
||||
"xpack.enterpriseSearch.content.index.syncButton.syncing.label": "Synchronisation en cours",
|
||||
"xpack.enterpriseSearch.content.index.syncButton.waitingForSync.label": "En attente de la synchronisation",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.actions.viewJob.caption": "Afficher cette tâche de synchronisation",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.actions.viewJob.title": "Afficher cette tâche de synchronisation",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.added": "Ajouté",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.removed": "Retiré",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.title": "Documents",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.total": "Total",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.value": "Valeur",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.volume": "Volume",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.volume.lessThanOneMBLabel": "Inférieur à 1 Mo",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.cancelationRequested": "Annulation demandée",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.canceled": "Annulé",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.completed": "Terminé",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.lastUpdated": "Dernière mise à jour",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.state": "État",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.syncRequestedManually": "Synchronisation demandée manuellement",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.syncRequestedScheduled": "Synchronisation demandée par planification",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.syncStarted": "Synchronisation démarrée",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.time": "Heure",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.title": "Événements",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.pipeline.extractBinaryContent": "Extraire le contenu binaire",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.pipeline.name": "Nom du pipeline",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.pipeline.reduceWhitespace": "Réduire l'espace",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.pipeline.runMlInference": "Inférence de Machine Learning",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.pipeline.setting": "Paramètre de pipeline",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.pipeline.title": "Pipeline",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.syncRulesAdvancedTitle": "Règles de synchronisation avancées",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.syncRulesTitle": "Règles de synchronisation",
|
||||
"xpack.enterpriseSearch.content.indices.callout.docLink": "lire la documentation",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.button.label": "Générer une clé API",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.cancelButton.label": "Annuler",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.confirmButton.label": "Générer une clé API",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.description": "La génération d'une nouvelle clé d’API invalidera la clé précédente. Êtes-vous sûr de vouloir générer une nouvelle clé d’API ? Cette action ne peut pas être annulée.",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.title": "Générer une clé d'API Elasticsearch",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.cancelEditingButton.title": "Annuler",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.connectorClientLink": "connecteurs",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.description.secondParagraph": "Bien que les clients connecteurs dans le référentiel soient créés dans Ruby, il n'y a aucune limitation technique à n'utiliser que Ruby. Créez un client connecteur avec la technologie qui convient le mieux à vos compétences.",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.discussLink": "Discuter",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.editButton.title": "Modifier la configuration",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.error.title": "Erreur de connecteur",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.issuesLink": "problème",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.submitButton.title": "Enregistrer la configuration",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.warning.title": "Ce connecteur est lié à votre index Elastic",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.configuration.successToast.title": "Configuration mise à jour",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.clientExamplesLink": "exemples de clients connecteurs",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.configurationFileLink": "fichier de configuration",
|
||||
|
@ -13372,7 +13315,6 @@
|
|||
"xpack.enterpriseSearch.content.indices.configurationConnector.support.readme.label": "Fichier readme du connecteur",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.support.title": "Support technique et documentation",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.support.viewDocumentation.label": "Afficher la documentation",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.warning.description": "Si vous synchronisez au moins un document avant d'avoir finalisé votre client connecteur, vous devrez recréer votre index de recherche.",
|
||||
"xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.error": "Le format JSON n'est pas valide",
|
||||
"xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.title": "Règles avancées",
|
||||
"xpack.enterpriseSearch.content.indices.connectorScheduling.accordion.accessControlSync.description": "Planifiez des synchronisations de contrôles d’accès pour garder les mappings d’autorisation à jour.",
|
||||
|
@ -13727,9 +13669,7 @@
|
|||
"xpack.enterpriseSearch.content.searchIndices.actions.columnTitle": "Actions",
|
||||
"xpack.enterpriseSearch.content.searchIndices.actions.deleteIndex.title": "Supprimer cet index",
|
||||
"xpack.enterpriseSearch.content.searchIndices.actions.viewIndex.title": "Afficher cet index",
|
||||
"xpack.enterpriseSearch.content.searchIndices.addedDocs.columnTitle": "Documents ajoutés",
|
||||
"xpack.enterpriseSearch.content.searchIndices.create.buttonTitle": "Créer un nouvel index",
|
||||
"xpack.enterpriseSearch.content.searchIndices.deletedDocs.columnTitle": "Documents supprimés",
|
||||
"xpack.enterpriseSearch.content.searchIndices.deleteModal.cancelButton.title": "Annuler",
|
||||
"xpack.enterpriseSearch.content.searchIndices.deleteModal.closeButton.title": "Fermer",
|
||||
"xpack.enterpriseSearch.content.searchIndices.deleteModal.confirmButton.title": "Supprimer l'index",
|
||||
|
@ -13738,7 +13678,6 @@
|
|||
"xpack.enterpriseSearch.content.searchIndices.deleteModal.syncsWarning.title": "Synchronisations en cours",
|
||||
"xpack.enterpriseSearch.content.searchIndices.docsCount.columnTitle": "Nombre de documents",
|
||||
"xpack.enterpriseSearch.content.searchIndices.health.columnTitle": "Intégrité des index",
|
||||
"xpack.enterpriseSearch.content.searchIndices.identitySync.columnTitle": "Identités synchronisées",
|
||||
"xpack.enterpriseSearch.content.searchIndices.ingestionMethod.api": "API",
|
||||
"xpack.enterpriseSearch.content.searchIndices.ingestionMethod.columnTitle": "Méthodes d'ingestion",
|
||||
"xpack.enterpriseSearch.content.searchIndices.ingestionMethod.connector": "Connecteur",
|
||||
|
@ -13769,8 +13708,6 @@
|
|||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.searchBar.placeHolder": "Filtrer les index Elasticsearch",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.stepsTitle": "Créer de belles expériences de recherche avec Enterprise Search",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.tableTitle": "Index disponibles",
|
||||
"xpack.enterpriseSearch.content.searchIndices.syncJobType.columnTitle": "Type de synchronisation de contenu",
|
||||
"xpack.enterpriseSearch.content.searchIndices.syncStatus.columnTitle": "Statut",
|
||||
"xpack.enterpriseSearch.content.settings.breadcrumb": "Paramètres",
|
||||
"xpack.enterpriseSearch.content.settings.contactExtraction.label": "Extraction du contenu",
|
||||
"xpack.enterpriseSearch.content.settings.contentExtraction.description": "Extrayez du contenu interrogeable de fichiers binaires, tels que des PDF et des documents Word.",
|
||||
|
@ -13805,21 +13742,10 @@
|
|||
"xpack.enterpriseSearch.content.supportedLanguages.spanishLabel": "Espagnol",
|
||||
"xpack.enterpriseSearch.content.supportedLanguages.thaiLabel": "Thaï",
|
||||
"xpack.enterpriseSearch.content.supportedLanguages.universalLabel": "Universel",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.canceledTitle": "Synchronisation annulée",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.completedTitle": "Synchronisation terminée",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.failureTitle": "Échec de la synchronisation",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.inProgressTitle": "En cours",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.sync": "Sync",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.sync.id": "ID",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.syncStartedManually": "Synchronisation démarrée manuellement",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.syncStartedScheduled": "Synchronisation démarrée par planification",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.title": "Log d'événements",
|
||||
"xpack.enterpriseSearch.content.syncJobs.lastSync.columnTitle": "Dernière synchronisation",
|
||||
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.accessControl.label": "Synchronisations de contrôle d'accès",
|
||||
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.content.label": "Synchronisations de contenu",
|
||||
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.legend": "Sélectionnez le type de tâche de synchronisation à afficher.",
|
||||
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.name": "Type de tâche de synchronisation",
|
||||
"xpack.enterpriseSearch.content.syncJobs.syncDuration.columnTitle": "Durée de synchronisation",
|
||||
"xpack.enterpriseSearch.crawler.addDomainFlyout.description": "Vous pouvez ajouter plusieurs domaines au robot d'indexation de cet index. Ajoutez un autre domaine ici et modifiez les points d'entrée et les règles d'indexation à partir de la page \"Gérer\".",
|
||||
"xpack.enterpriseSearch.crawler.addDomainFlyout.openButtonLabel": "Ajouter un domaine",
|
||||
"xpack.enterpriseSearch.crawler.addDomainFlyout.title": "Ajouter un nouveau domaine",
|
||||
|
|
|
@ -12051,13 +12051,8 @@
|
|||
"xpack.enterpriseSearch.content.index.connector.syncRules.flyout.errorTitle": "同期{idsLength, plural, other {ルール}}{ids}{idsLength, plural, other {あります}}無効です。",
|
||||
"xpack.enterpriseSearch.content.index.pipelines.copyCustomizeCallout.description": "インデックスはデフォルトインジェストパイプライン\"{defaultPipeline}\"を使用しています。パイプラインをインデックス固有の構成にコピーし、カスタムインジェストと推論パイプラインを作成できるようにします。",
|
||||
"xpack.enterpriseSearch.content.index.pipelines.ingestFlyout.modalBodyAPIText": "{apiIndex}以下の設定に行われた変更は参照専用です。これらの設定は、インデックスまたはパイプラインまで永続しません。",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.volume.aboutLabel": "{volume}の概要",
|
||||
"xpack.enterpriseSearch.content.indices.callout.text": "Elasticsearchインデックスは、現在、エンタープライズ サーチの中心です。直接そのインデックスを使用して、新しいインデックスを作成し、検索エクスペリエンスを構築できます。エンタープライズ サーチでのElasticsearchの使用方法の詳細については、{docLink}をご覧ください",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.description": "まず、Elasticsearch APIキーを生成します。この{apiKeyName}は、コネクターがドキュメントを作成された{indexName}インデックスにインデックスするための読み書き権限を有効にします。キーは安全な場所に保管してください。コネクターを構成するときに必要になります。",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.defaultValue": "空にする場合は、デフォルト値の{defaultValue}が使用されます。",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.description.firstParagraph": "コネクターがデプロイされたので、カスタムデータソースのコネクタークライアントを強化します。独自の追加の実装ロジックを使用してカスタマイズ可能な複数の{link}があります。",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.description.thirdParagraph": "サポートが必要な場合は、いつでもリポジトリで{issuesLink}をオープンするか、{discussLink}フォーラムで質問することができます。",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.invalidInteger": "{label}は整数でなければなりません。",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.connectorConnected": "コネクター{name}は、正常にエンタープライズ サーチに接続されました。",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.description.secondParagraph": "コネクターリポジトリには複数の{link}が含まれています。カスタムデータソースに対して高速開発のフレームワークを使用します。",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.description.thirdParagraph": "このステップでは、リポジトリを複製またはフォークし、生成されたAPIキーとコネクターIDを、関連付けられた{link}にコピーする必要があります。コネクターIDは、エンタープライズ サーチに対するこのコネクターを特定します。サービスタイプは、コネクターが構成されているデータソースのタイプを決定します。",
|
||||
|
@ -12098,11 +12093,6 @@
|
|||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.onlySearchOptimized.tooltipContent": "検索用に最適化されたインデックスには、{code}がプレフィックスとして付けられます。これらは、クローラー、コネクター、インジェストAPIなどのインジェストメカニズムによって管理されます。",
|
||||
"xpack.enterpriseSearch.content.settings.description": "これらの設定は、エンタープライズ サーチインジェストメカニズムで作成されたすべての新しいElasticsearchインデックスに適用されます。APIインジェストベースのインデックスの場合は、ドキュメントをインジェストするときに、必ずパイプラインを含めてください。これらの機能は{link}によって実現されます。",
|
||||
"xpack.enterpriseSearch.content.shared.result.header.metadata.icon.ariaLabel": "ドキュメント{id}のメタデータ",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.canceledDescription": "{date}に同期がキャンセルされました",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.completedDescription": "完了日時:{date}",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.failureDescription": "同期失敗:{error}",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.inProgressDescription": "同期は{duration}実行中です。",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.startedAtDescription": "{date}に開始しました",
|
||||
"xpack.enterpriseSearch.crawler.action.deleteDomain.confirmationPopupMessage": "ドメイン\"{domainUrl}\"とすべての設定を削除しますか?",
|
||||
"xpack.enterpriseSearch.crawler.addDomainForm.entryPointLabel": "Webクローラーエントリポイントが{entryPointValue}として設定されました",
|
||||
"xpack.enterpriseSearch.crawler.authenticationPanel.emptyPrompt.description": "{addAuthenticationButtonLabel}をクリックすると、保護されたコンテンツのクローリングに必要な資格情報を提供します",
|
||||
|
@ -13179,8 +13169,6 @@
|
|||
"xpack.enterpriseSearch.betaLabel": "ベータ",
|
||||
"xpack.enterpriseSearch.connector.connectorTypePanel.title": "コネクタータイプ",
|
||||
"xpack.enterpriseSearch.connector.connectorTypePanel.unknown.label": "不明",
|
||||
"xpack.enterpriseSearch.connector.documentLevelSecurity.enablePanel.description": "権限に基づいて、ユーザーがアクセスできるドキュメントを制御できます。これにより、検索結果は、ユーザーの役割に基づき、関連性のある許可された情報のみを返すようになります。",
|
||||
"xpack.enterpriseSearch.connector.documentLevelSecurity.enablePanel.heading": "ドキュメントレベルのセキュリティ",
|
||||
"xpack.enterpriseSearch.connector.ingestionStatus.title": "インジェスチョンステータス",
|
||||
"xpack.enterpriseSearch.content,overview.documentExample.clientLibraries.label": "クライアントライブラリ",
|
||||
"xpack.enterpriseSearch.content.analytics.api.generateAnalyticsApiKeyModal.apiKeyWarning": "ElasticはAPIキーを保存しません。生成後は、1回だけキーを表示できます。必ず安全に保管してください。アクセスできなくなった場合は、この画面から新しいAPIキーを生成する必要があります。",
|
||||
|
@ -13224,15 +13212,6 @@
|
|||
"xpack.enterpriseSearch.content.crawler.extractionRulesTable.emptyMessageTitle": "コンテンツ抽出ルールがありません",
|
||||
"xpack.enterpriseSearch.content.crawler.siteMaps": "サイトマップ",
|
||||
"xpack.enterpriseSearch.content.description": "エンタープライズ サーチでは、さまざまな方法で簡単にデータを検索可能にできます。Webクローラー、Elasticsearchインデックス、API、直接アップロード、サードパーティコネクターから選択します。",
|
||||
"xpack.enterpriseSearch.content.filteringRules.policy.exclude": "除外",
|
||||
"xpack.enterpriseSearch.content.filteringRules.policy.include": "含める",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.contains": "を含む",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.endsWith": "で終了",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.equals": "一致する",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.greaterThan": "より大きい",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.lessThan": "より小さい",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.regEx": "正規表現",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.startsWith": "で始まる",
|
||||
"xpack.enterpriseSearch.content.index.connector.filtering.successToastRules.title": "同期ルールが更新されました",
|
||||
"xpack.enterpriseSearch.content.index.connector.filteringRules.regExError": "値は正規表現にしてください",
|
||||
"xpack.enterpriseSearch.content.index.connector.syncRules.advancedFiltersDescription": "これらのルールは、データがデータソースから取得される前に適用されます。",
|
||||
|
@ -13299,48 +13278,12 @@
|
|||
"xpack.enterpriseSearch.content.index.syncButton.label": "同期",
|
||||
"xpack.enterpriseSearch.content.index.syncButton.syncing.label": "同期中",
|
||||
"xpack.enterpriseSearch.content.index.syncButton.waitingForSync.label": "同期を待機しています",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.actions.viewJob.caption": "この同期ジョブを表示",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.actions.viewJob.title": "この同期ジョブを表示",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.added": "追加",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.removed": "削除しました",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.title": "ドキュメント",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.total": "合計",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.value": "値",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.volume": "量",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.volume.lessThanOneMBLabel": "1mb未満",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.cancelationRequested": "キャンセルがリクエストされました",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.canceled": "キャンセル",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.completed": "完了",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.lastUpdated": "最終更新",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.state": "ステータス",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.syncRequestedManually": "同期が手動でリクエストされました",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.syncRequestedScheduled": "同期がスケジュールでリクエストされました",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.syncStarted": "同期が開始しました",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.time": "時間",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.title": "イベント",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.pipeline.extractBinaryContent": "バイナリコンテンツを抽出",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.pipeline.name": "パイプライン名",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.pipeline.reduceWhitespace": "空白の削除",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.pipeline.runMlInference": "機械学習推論",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.pipeline.setting": "パイプライン設定",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.pipeline.title": "パイプライン",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.syncRulesAdvancedTitle": "詳細同期ルール",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.syncRulesTitle": "同期ルール",
|
||||
"xpack.enterpriseSearch.content.indices.callout.docLink": "ドキュメントを読む",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.button.label": "APIキーを生成",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.cancelButton.label": "キャンセル",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.confirmButton.label": "APIキーを生成",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.description": "新しいAPIキーを生成すると、前のキーが無効になります。新しいAPIキーを生成しますか?この操作は元に戻せません。",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.title": "Elasticsearch APIキーを生成",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.cancelEditingButton.title": "キャンセル",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.connectorClientLink": "コネクター",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.description.secondParagraph": "リポジトリのコネクタークライアントはRubyで構築されていますが、技術的な制限事項はないため、Ruby以外も使用できます。ご自身のスキルセットに最適な技術でコネクタークライアントを構築してください。",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.discussLink": "ディスカッション",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.editButton.title": "構成の編集",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.error.title": "コネクターエラー",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.issuesLink": "問題",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.submitButton.title": "構成を保存",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.warning.title": "このコネクターは、Elasticインデックスに関連付けられています",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.configuration.successToast.title": "構成が更新されました",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.clientExamplesLink": "コネクタークライアントの例",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.configurationFileLink": "構成ファイル",
|
||||
|
@ -13386,7 +13329,6 @@
|
|||
"xpack.enterpriseSearch.content.indices.configurationConnector.support.readme.label": "コネクターReadme",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.support.title": "サポートとドキュメント",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.support.viewDocumentation.label": "ドキュメンテーションを表示",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.warning.description": "コネクタークライアントを確定する前に、1つ以上のドキュメントを同期した場合、検索インデックスを再作成する必要があります。",
|
||||
"xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.error": "JSON形式が無効です",
|
||||
"xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.title": "詳細ルール",
|
||||
"xpack.enterpriseSearch.content.indices.connectorScheduling.accordion.accessControlSync.description": "アクセス制御同期をスケジュールし、権限マッピングを常に最新の状態に保ちます。",
|
||||
|
@ -13741,9 +13683,7 @@
|
|||
"xpack.enterpriseSearch.content.searchIndices.actions.columnTitle": "アクション",
|
||||
"xpack.enterpriseSearch.content.searchIndices.actions.deleteIndex.title": "このインデックスを削除",
|
||||
"xpack.enterpriseSearch.content.searchIndices.actions.viewIndex.title": "このインデックスを表示",
|
||||
"xpack.enterpriseSearch.content.searchIndices.addedDocs.columnTitle": "ドキュメントが追加されました",
|
||||
"xpack.enterpriseSearch.content.searchIndices.create.buttonTitle": "新しいインデックスを作成",
|
||||
"xpack.enterpriseSearch.content.searchIndices.deletedDocs.columnTitle": "ドキュメントが削除されました",
|
||||
"xpack.enterpriseSearch.content.searchIndices.deleteModal.cancelButton.title": "キャンセル",
|
||||
"xpack.enterpriseSearch.content.searchIndices.deleteModal.closeButton.title": "閉じる",
|
||||
"xpack.enterpriseSearch.content.searchIndices.deleteModal.confirmButton.title": "インデックスの削除",
|
||||
|
@ -13752,7 +13692,6 @@
|
|||
"xpack.enterpriseSearch.content.searchIndices.deleteModal.syncsWarning.title": "同期実行中",
|
||||
"xpack.enterpriseSearch.content.searchIndices.docsCount.columnTitle": "ドキュメント数",
|
||||
"xpack.enterpriseSearch.content.searchIndices.health.columnTitle": "インデックス正常性",
|
||||
"xpack.enterpriseSearch.content.searchIndices.identitySync.columnTitle": "IDが同期されました",
|
||||
"xpack.enterpriseSearch.content.searchIndices.ingestionMethod.api": "API",
|
||||
"xpack.enterpriseSearch.content.searchIndices.ingestionMethod.columnTitle": "インジェスチョン方法",
|
||||
"xpack.enterpriseSearch.content.searchIndices.ingestionMethod.connector": "コネクター",
|
||||
|
@ -13783,8 +13722,6 @@
|
|||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.searchBar.placeHolder": "Elasticsearchインデックスをフィルター",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.stepsTitle": "エンタープライズ サーチで構築する優れた検索エクスペリエンス",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.tableTitle": "利用可能なインデックス",
|
||||
"xpack.enterpriseSearch.content.searchIndices.syncJobType.columnTitle": "コンテンツ同期タイプ",
|
||||
"xpack.enterpriseSearch.content.searchIndices.syncStatus.columnTitle": "ステータス",
|
||||
"xpack.enterpriseSearch.content.settings.breadcrumb": "設定",
|
||||
"xpack.enterpriseSearch.content.settings.contactExtraction.label": "コンテンツ抽出",
|
||||
"xpack.enterpriseSearch.content.settings.contentExtraction.description": "PDFやWordドキュメントなどの検索可能なコンテンツのみをバイナリファイルから抽出します。",
|
||||
|
@ -13819,21 +13756,10 @@
|
|||
"xpack.enterpriseSearch.content.supportedLanguages.spanishLabel": "スペイン語",
|
||||
"xpack.enterpriseSearch.content.supportedLanguages.thaiLabel": "タイ語",
|
||||
"xpack.enterpriseSearch.content.supportedLanguages.universalLabel": "ユニバーサル",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.canceledTitle": "同期がキャンセルされました",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.completedTitle": "同期完了",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.failureTitle": "同期失敗",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.inProgressTitle": "進行中",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.sync": "同期",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.sync.id": "ID",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.syncStartedManually": "同期は手動で開始しました",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.syncStartedScheduled": "同期はスケジュールで開始しました",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.title": "イベントログ",
|
||||
"xpack.enterpriseSearch.content.syncJobs.lastSync.columnTitle": "前回の同期",
|
||||
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.accessControl.label": "アクセス制御同期",
|
||||
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.content.label": "コンテンツ同期",
|
||||
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.legend": "表示する同期ジョブタイプを選択します。",
|
||||
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.name": "同期ジョブタイプ",
|
||||
"xpack.enterpriseSearch.content.syncJobs.syncDuration.columnTitle": "同期時間",
|
||||
"xpack.enterpriseSearch.crawler.addDomainFlyout.description": "複数のドメインをこのインデックスのWebクローラーに追加できます。ここで別のドメインを追加して、[管理]ページからエントリポイントとクロールルールを変更します。",
|
||||
"xpack.enterpriseSearch.crawler.addDomainFlyout.openButtonLabel": "ドメインを追加",
|
||||
"xpack.enterpriseSearch.crawler.addDomainFlyout.title": "新しいドメインを追加",
|
||||
|
|
|
@ -12051,13 +12051,8 @@
|
|||
"xpack.enterpriseSearch.content.index.connector.syncRules.flyout.errorTitle": "同步{idsLength, plural, other {规则}} {ids}{idsLength, plural, other {有}}无效。",
|
||||
"xpack.enterpriseSearch.content.index.pipelines.copyCustomizeCallout.description": "您的索引正使用默认采集管道 {defaultPipeline}。将该管道复制到特定于索引的配置中,以解锁创建定制采集和推理管道的功能。",
|
||||
"xpack.enterpriseSearch.content.index.pipelines.ingestFlyout.modalBodyAPIText": "{apiIndex}对以下设置所做的更改仅供参考。这些设置不会持续用于您的索引或管道。",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.volume.aboutLabel": "关于 {volume}",
|
||||
"xpack.enterpriseSearch.content.indices.callout.text": "您的 Elasticsearch 索引如今在 Enterprise Search 中位于前排和中心位置。您可以创建新索引,直接通过它们构建搜索体验。有关如何在 Enterprise Search 中使用 Elasticsearch 索引的详情,{docLink}",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.description": "首先,生成一个 Elasticsearch API 密钥。此 {apiKeyName} 密钥将为连接器启用读取和写入权限,以便将文档索引到已创建的 {indexName} 索引。请将该密钥保存到安全位置,因为您需要它来配置连接器。",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.defaultValue": "如果留空,将使用默认值 {defaultValue}。",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.description.firstParagraph": "部署连接器后,请为您的定制数据源增强连接器客户端。您可以通过自己的其他实施逻辑定制几个 {link}。",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.description.thirdParagraph": "如果需要帮助,您始终可以在存储库中打开 {issuesLink},或在我们的 {discussLink} 论坛中提出问题。",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.invalidInteger": "{label} 必须为整数。",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.connectorConnected": "您的连接器 {name} 已成功连接到 Enterprise Search。",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.description.secondParagraph": "连接器存储库包含几个 {link}。使用我们的框架针对定制数据源进行加速开发。",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.description.thirdParagraph": "在此步骤中,您需要克隆或分叉存储库,然后将生成的 API 密钥和连接器 ID 复制到关联的 {link}。连接器 ID 会将此连接器标识到 Enterprise Search。此服务类型将决定要将连接器配置用于哪些类型的数据源。",
|
||||
|
@ -12098,11 +12093,6 @@
|
|||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.onlySearchOptimized.tooltipContent": "搜索优化索引以 {code} 为前缀。它们由网络爬虫、连接器或采集 API 等采集机制进行管理。",
|
||||
"xpack.enterpriseSearch.content.settings.description": "这些设置适用于由 Enterprise Search 采集机制创建的所有新 Elasticsearch 索引。对于基于 API 采集的索引,在采集文档时请记得包括管道。这些功能由 {link} 提供支持。",
|
||||
"xpack.enterpriseSearch.content.shared.result.header.metadata.icon.ariaLabel": "以下文档的元数据:{id}",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.canceledDescription": "同步已于 {date}取消",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.completedDescription": "已于 {date} 完成",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.failureDescription": "同步失败:{error}。",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.inProgressDescription": "同步已运行 {duration}。",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.startedAtDescription": "已于 {date}启动",
|
||||
"xpack.enterpriseSearch.crawler.action.deleteDomain.confirmationPopupMessage": "确定要移除域“{domainUrl}”及其所有设置?",
|
||||
"xpack.enterpriseSearch.crawler.addDomainForm.entryPointLabel": "网络爬虫入口点已设置为 {entryPointValue}",
|
||||
"xpack.enterpriseSearch.crawler.authenticationPanel.emptyPrompt.description": "单击 {addAuthenticationButtonLabel} 以提供爬网受保护内容所需的凭据",
|
||||
|
@ -13179,8 +13169,6 @@
|
|||
"xpack.enterpriseSearch.betaLabel": "公测版",
|
||||
"xpack.enterpriseSearch.connector.connectorTypePanel.title": "连接器类型",
|
||||
"xpack.enterpriseSearch.connector.connectorTypePanel.unknown.label": "未知",
|
||||
"xpack.enterpriseSearch.connector.documentLevelSecurity.enablePanel.description": "允许您基于用户的权限,控制他们可以访问的文档。这确保了搜索结果将基于用户角色,仅为其返回相关授权信息。",
|
||||
"xpack.enterpriseSearch.connector.documentLevelSecurity.enablePanel.heading": "文档级别安全性",
|
||||
"xpack.enterpriseSearch.connector.ingestionStatus.title": "采集状态",
|
||||
"xpack.enterpriseSearch.content,overview.documentExample.clientLibraries.label": "客户端库",
|
||||
"xpack.enterpriseSearch.content.analytics.api.generateAnalyticsApiKeyModal.apiKeyWarning": "Elastic 不会存储 API 密钥。一旦生成,您只能查看密钥一次。请确保将其保存在某个安全位置。如果失去它的访问权限,您需要从此屏幕生成新的 API 密钥。",
|
||||
|
@ -13224,15 +13212,6 @@
|
|||
"xpack.enterpriseSearch.content.crawler.extractionRulesTable.emptyMessageTitle": "没有内容提取规则",
|
||||
"xpack.enterpriseSearch.content.crawler.siteMaps": "站点地图",
|
||||
"xpack.enterpriseSearch.content.description": "Enterprise Search 提供了各种方法以便您轻松搜索数据。从网络爬虫、Elasticsearch 索引、API、直接上传或第三方连接器中选择。",
|
||||
"xpack.enterpriseSearch.content.filteringRules.policy.exclude": "排除",
|
||||
"xpack.enterpriseSearch.content.filteringRules.policy.include": "包括",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.contains": "Contains",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.endsWith": "结束于",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.equals": "等于",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.greaterThan": "大于",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.lessThan": "小于",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.regEx": "正则表达式",
|
||||
"xpack.enterpriseSearch.content.filteringRules.rules.startsWith": "开头为",
|
||||
"xpack.enterpriseSearch.content.index.connector.filtering.successToastRules.title": "同步规则已更新",
|
||||
"xpack.enterpriseSearch.content.index.connector.filteringRules.regExError": "值应为正则表达式",
|
||||
"xpack.enterpriseSearch.content.index.connector.syncRules.advancedFiltersDescription": "从数据源获取数据之前,这些规则适用。",
|
||||
|
@ -13299,48 +13278,12 @@
|
|||
"xpack.enterpriseSearch.content.index.syncButton.label": "同步",
|
||||
"xpack.enterpriseSearch.content.index.syncButton.syncing.label": "正在同步",
|
||||
"xpack.enterpriseSearch.content.index.syncButton.waitingForSync.label": "等待同步",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.actions.viewJob.caption": "查看此同步作业",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.actions.viewJob.title": "查看此同步作业",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.added": "已添加",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.removed": "已移除",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.title": "文档",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.total": "合计",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.value": "值",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.volume": "卷",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.documents.volume.lessThanOneMBLabel": "小于 1mb",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.cancelationRequested": "已请求取消",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.canceled": "已取消",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.completed": "已完成",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.lastUpdated": "上次更新时间",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.state": "状态",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.syncRequestedManually": "已手动请求同步",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.syncRequestedScheduled": "已按计划请求同步",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.syncStarted": "已启动同步",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.time": "时间",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.events.title": "事件",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.pipeline.extractBinaryContent": "提取二进制内容",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.pipeline.name": "管道名称",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.pipeline.reduceWhitespace": "减少空白",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.pipeline.runMlInference": "Machine Learning 推理",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.pipeline.setting": "管道设置",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.pipeline.title": "管道",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.syncRulesAdvancedTitle": "高级同步规则",
|
||||
"xpack.enterpriseSearch.content.index.syncJobs.syncRulesTitle": "同步规则",
|
||||
"xpack.enterpriseSearch.content.indices.callout.docLink": "阅读文档",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.button.label": "生成 API 密钥",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.cancelButton.label": "取消",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.confirmButton.label": "生成 API 密钥",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.description": "生成新的 API 密钥将使之前的密钥失效。是否确定要生成新的 API 密钥?此操作无法撤消。",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.apiKey.confirmModal.title": "生成 Elasticsearch API 密钥",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.cancelEditingButton.title": "取消",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.connectorClientLink": "连接器",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.description.secondParagraph": "虽然存储库中的连接器客户端是在 Ruby 中构建的,但仅使用 Ruby 并不存在技术限制。通过最适合您技能集的技术来构建连接器客户端。",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.discussLink": "讨论",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.editButton.title": "编辑配置",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.error.title": "连接器错误",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.issuesLink": "问题",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.submitButton.title": "保存配置",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.config.warning.title": "此连接器将绑定到您的 Elastic 索引",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.configuration.successToast.title": "已更新配置",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.clientExamplesLink": "连接器客户端示例",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.connectorPackage.configurationFileLink": "配置文件",
|
||||
|
@ -13386,7 +13329,6 @@
|
|||
"xpack.enterpriseSearch.content.indices.configurationConnector.support.readme.label": "连接器自述文件",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.support.title": "支持和文档",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.support.viewDocumentation.label": "查看文档",
|
||||
"xpack.enterpriseSearch.content.indices.configurationConnector.warning.description": "如果您在完成连接器客户端之前至少同步一个文档,您必须重新创建搜索索引。",
|
||||
"xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.error": "JSON 格式无效",
|
||||
"xpack.enterpriseSearch.content.indices.connector.syncRules.advancedRules.title": "高级规则",
|
||||
"xpack.enterpriseSearch.content.indices.connectorScheduling.accordion.accessControlSync.description": "计划访问控制同步以使权限映射保持最新。",
|
||||
|
@ -13741,9 +13683,7 @@
|
|||
"xpack.enterpriseSearch.content.searchIndices.actions.columnTitle": "操作",
|
||||
"xpack.enterpriseSearch.content.searchIndices.actions.deleteIndex.title": "删除此索引",
|
||||
"xpack.enterpriseSearch.content.searchIndices.actions.viewIndex.title": "查看此索引",
|
||||
"xpack.enterpriseSearch.content.searchIndices.addedDocs.columnTitle": "已添加文档",
|
||||
"xpack.enterpriseSearch.content.searchIndices.create.buttonTitle": "创建新索引",
|
||||
"xpack.enterpriseSearch.content.searchIndices.deletedDocs.columnTitle": "文档已删除",
|
||||
"xpack.enterpriseSearch.content.searchIndices.deleteModal.cancelButton.title": "取消",
|
||||
"xpack.enterpriseSearch.content.searchIndices.deleteModal.closeButton.title": "关闭",
|
||||
"xpack.enterpriseSearch.content.searchIndices.deleteModal.confirmButton.title": "删除索引",
|
||||
|
@ -13752,7 +13692,6 @@
|
|||
"xpack.enterpriseSearch.content.searchIndices.deleteModal.syncsWarning.title": "进行中的同步",
|
||||
"xpack.enterpriseSearch.content.searchIndices.docsCount.columnTitle": "文档计数",
|
||||
"xpack.enterpriseSearch.content.searchIndices.health.columnTitle": "索引运行状况",
|
||||
"xpack.enterpriseSearch.content.searchIndices.identitySync.columnTitle": "身份已同步",
|
||||
"xpack.enterpriseSearch.content.searchIndices.ingestionMethod.api": "API",
|
||||
"xpack.enterpriseSearch.content.searchIndices.ingestionMethod.columnTitle": "采集方法",
|
||||
"xpack.enterpriseSearch.content.searchIndices.ingestionMethod.connector": "连接器",
|
||||
|
@ -13783,8 +13722,6 @@
|
|||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.searchBar.placeHolder": "筛选 Elasticsearch 索引",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.stepsTitle": "通过 Enterprise Search 构建出色的搜索体验",
|
||||
"xpack.enterpriseSearch.content.searchIndices.searchIndices.tableTitle": "可用索引",
|
||||
"xpack.enterpriseSearch.content.searchIndices.syncJobType.columnTitle": "内容同步类型",
|
||||
"xpack.enterpriseSearch.content.searchIndices.syncStatus.columnTitle": "状态",
|
||||
"xpack.enterpriseSearch.content.settings.breadcrumb": "设置",
|
||||
"xpack.enterpriseSearch.content.settings.contactExtraction.label": "内容提取",
|
||||
"xpack.enterpriseSearch.content.settings.contentExtraction.description": "从二进制文件(如 PDF 和 Word 文档)提取可搜索内容。",
|
||||
|
@ -13819,21 +13756,10 @@
|
|||
"xpack.enterpriseSearch.content.supportedLanguages.spanishLabel": "西班牙语",
|
||||
"xpack.enterpriseSearch.content.supportedLanguages.thaiLabel": "泰语",
|
||||
"xpack.enterpriseSearch.content.supportedLanguages.universalLabel": "通用",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.canceledTitle": "同步已取消",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.completedTitle": "同步已完成",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.failureTitle": "同步失败",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.inProgressTitle": "进行中",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.sync": "同步",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.sync.id": "ID",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.syncStartedManually": "已手动启动同步",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.syncStartedScheduled": "已按计划启动同步",
|
||||
"xpack.enterpriseSearch.content.syncJobs.flyout.title": "事件日志",
|
||||
"xpack.enterpriseSearch.content.syncJobs.lastSync.columnTitle": "上次同步",
|
||||
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.accessControl.label": "访问控制同步",
|
||||
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.content.label": "内容同步",
|
||||
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.legend": "选择要显示的同步作业类型。",
|
||||
"xpack.enterpriseSearch.content.syncJobs.lastSync.tableSelector.name": "同步作业类型",
|
||||
"xpack.enterpriseSearch.content.syncJobs.syncDuration.columnTitle": "同步持续时间",
|
||||
"xpack.enterpriseSearch.crawler.addDomainFlyout.description": "可以将多个域添加到此索引的网络爬虫。在此添加其他域并从“管理”页面修改入口点和爬网规则。",
|
||||
"xpack.enterpriseSearch.crawler.addDomainFlyout.openButtonLabel": "添加域",
|
||||
"xpack.enterpriseSearch.crawler.addDomainFlyout.title": "添加新域",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue