mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[Search] New search connector creation flow (#187582)](https://github.com/elastic/kibana/pull/187582) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"José Luis González","email":"joseluisgj@gmail.com"},"sourceCommit":{"committedDate":"2024-10-15T12:09:30Z","message":"[Search] New search connector creation flow (#187582)\n\n## Summary\r\n\r\nThis PR brings a new and dedicated search connector creation flow for\r\nES3 and ESS.\r\n[Figma\r\nPrototype](https://www.figma.com/proto/eKQr4HYlz0v9pTofRPWIyH/Ingestion-methods-flow?page-id=411%3A158867&node-id=411-158870&viewport=3831%2C-1905%2C1.23&t=ZP9e3LtaSeJ5FMAz-9&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=411%3A158870&show-proto-sidebar=1)\r\n\r\n\r\n\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [ ] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n- [ ] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [ ] [Flaky Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\r\nused on any tests changed\r\n- [ ] Any UI touched in this PR is usable by keyboard only (learn more\r\nabout [keyboard accessibility](https://webaim.org/techniques/keyboard/))\r\n- [ ] Any UI touched in this PR does not create any new axe failures\r\n(run axe in browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n- [ ] If a plugin configuration key changed, check if it needs to be\r\nallowlisted in the cloud and added to the [docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n- [ ] This renders correctly on smaller devices using a responsive\r\nlayout. (You can test this [in your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n- [ ] This was checked for [cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n\r\n### Risk Matrix\r\n\r\nDelete this section if it is not applicable to this PR.\r\n\r\nBefore closing this PR, invite QA, stakeholders, and other developers to\r\nidentify risks that should be tested prior to the change/feature\r\nrelease.\r\n\r\nWhen forming the risk matrix, consider some of the following examples\r\nand how they may potentially impact the change:\r\n\r\n| Risk | Probability | Severity | Mitigation/Notes |\r\n\r\n|---------------------------|-------------|----------|-------------------------|\r\n| Multiple Spaces—unexpected behavior in non-default Kibana Space.\r\n| Low | High | Integration tests will verify that all features are still\r\nsupported in non-default Kibana Space and when user switches between\r\nspaces. |\r\n| Multiple nodes—Elasticsearch polling might have race conditions\r\nwhen multiple Kibana nodes are polling for the same tasks. | High | Low\r\n| Tasks are idempotent, so executing them multiple times will not result\r\nin logical error, but will degrade performance. To test for this case we\r\nadd plenty of unit tests around this logic and document manual testing\r\nprocedure. |\r\n| Code should gracefully handle cases when feature X or plugin Y are\r\ndisabled. | Medium | High | Unit tests will verify that any feature flag\r\nor plugin combination still results in our service operational. |\r\n| [See more potential risk\r\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) |\r\n\r\n\r\n### For maintainers\r\n\r\n- [ ] This was checked for breaking API changes and was [labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by: Efe Gürkan YALAMAN <efeguerkan.yalaman@elastic.co>\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"63e116bb078c29c70e4e23cba1c88d0ac022801d","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:EnterpriseSearch","backport:prev-minor","v8.16.0"],"title":"[Search] New search connector creation flow","number":187582,"url":"https://github.com/elastic/kibana/pull/187582","mergeCommit":{"message":"[Search] New search connector creation flow (#187582)\n\n## Summary\r\n\r\nThis PR brings a new and dedicated search connector creation flow for\r\nES3 and ESS.\r\n[Figma\r\nPrototype](https://www.figma.com/proto/eKQr4HYlz0v9pTofRPWIyH/Ingestion-methods-flow?page-id=411%3A158867&node-id=411-158870&viewport=3831%2C-1905%2C1.23&t=ZP9e3LtaSeJ5FMAz-9&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=411%3A158870&show-proto-sidebar=1)\r\n\r\n\r\n\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [ ] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n- [ ] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [ ] [Flaky Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\r\nused on any tests changed\r\n- [ ] Any UI touched in this PR is usable by keyboard only (learn more\r\nabout [keyboard accessibility](https://webaim.org/techniques/keyboard/))\r\n- [ ] Any UI touched in this PR does not create any new axe failures\r\n(run axe in browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n- [ ] If a plugin configuration key changed, check if it needs to be\r\nallowlisted in the cloud and added to the [docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n- [ ] This renders correctly on smaller devices using a responsive\r\nlayout. (You can test this [in your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n- [ ] This was checked for [cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n\r\n### Risk Matrix\r\n\r\nDelete this section if it is not applicable to this PR.\r\n\r\nBefore closing this PR, invite QA, stakeholders, and other developers to\r\nidentify risks that should be tested prior to the change/feature\r\nrelease.\r\n\r\nWhen forming the risk matrix, consider some of the following examples\r\nand how they may potentially impact the change:\r\n\r\n| Risk | Probability | Severity | Mitigation/Notes |\r\n\r\n|---------------------------|-------------|----------|-------------------------|\r\n| Multiple Spaces—unexpected behavior in non-default Kibana Space.\r\n| Low | High | Integration tests will verify that all features are still\r\nsupported in non-default Kibana Space and when user switches between\r\nspaces. |\r\n| Multiple nodes—Elasticsearch polling might have race conditions\r\nwhen multiple Kibana nodes are polling for the same tasks. | High | Low\r\n| Tasks are idempotent, so executing them multiple times will not result\r\nin logical error, but will degrade performance. To test for this case we\r\nadd plenty of unit tests around this logic and document manual testing\r\nprocedure. |\r\n| Code should gracefully handle cases when feature X or plugin Y are\r\ndisabled. | Medium | High | Unit tests will verify that any feature flag\r\nor plugin combination still results in our service operational. |\r\n| [See more potential risk\r\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) |\r\n\r\n\r\n### For maintainers\r\n\r\n- [ ] This was checked for breaking API changes and was [labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by: Efe Gürkan YALAMAN <efeguerkan.yalaman@elastic.co>\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"63e116bb078c29c70e4e23cba1c88d0ac022801d"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/187582","number":187582,"mergeCommit":{"message":"[Search] New search connector creation flow (#187582)\n\n## Summary\r\n\r\nThis PR brings a new and dedicated search connector creation flow for\r\nES3 and ESS.\r\n[Figma\r\nPrototype](https://www.figma.com/proto/eKQr4HYlz0v9pTofRPWIyH/Ingestion-methods-flow?page-id=411%3A158867&node-id=411-158870&viewport=3831%2C-1905%2C1.23&t=ZP9e3LtaSeJ5FMAz-9&scaling=min-zoom&content-scaling=fixed&starting-point-node-id=411%3A158870&show-proto-sidebar=1)\r\n\r\n\r\n\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [ ] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n- [ ] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n- [ ] [Flaky Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\r\nused on any tests changed\r\n- [ ] Any UI touched in this PR is usable by keyboard only (learn more\r\nabout [keyboard accessibility](https://webaim.org/techniques/keyboard/))\r\n- [ ] Any UI touched in this PR does not create any new axe failures\r\n(run axe in browser:\r\n[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),\r\n[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))\r\n- [ ] If a plugin configuration key changed, check if it needs to be\r\nallowlisted in the cloud and added to the [docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n- [ ] This renders correctly on smaller devices using a responsive\r\nlayout. (You can test this [in your\r\nbrowser](https://www.browserstack.com/guide/responsive-testing-on-local-server))\r\n- [ ] This was checked for [cross-browser\r\ncompatibility](https://www.elastic.co/support/matrix#matrix_browsers)\r\n\r\n\r\n### Risk Matrix\r\n\r\nDelete this section if it is not applicable to this PR.\r\n\r\nBefore closing this PR, invite QA, stakeholders, and other developers to\r\nidentify risks that should be tested prior to the change/feature\r\nrelease.\r\n\r\nWhen forming the risk matrix, consider some of the following examples\r\nand how they may potentially impact the change:\r\n\r\n| Risk | Probability | Severity | Mitigation/Notes |\r\n\r\n|---------------------------|-------------|----------|-------------------------|\r\n| Multiple Spaces—unexpected behavior in non-default Kibana Space.\r\n| Low | High | Integration tests will verify that all features are still\r\nsupported in non-default Kibana Space and when user switches between\r\nspaces. |\r\n| Multiple nodes—Elasticsearch polling might have race conditions\r\nwhen multiple Kibana nodes are polling for the same tasks. | High | Low\r\n| Tasks are idempotent, so executing them multiple times will not result\r\nin logical error, but will degrade performance. To test for this case we\r\nadd plenty of unit tests around this logic and document manual testing\r\nprocedure. |\r\n| Code should gracefully handle cases when feature X or plugin Y are\r\ndisabled. | Medium | High | Unit tests will verify that any feature flag\r\nor plugin combination still results in our service operational. |\r\n| [See more potential risk\r\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) |\r\n\r\n\r\n### For maintainers\r\n\r\n- [ ] This was checked for breaking API changes and was [labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n---------\r\n\r\nCo-authored-by: Efe Gürkan YALAMAN <efeguerkan.yalaman@elastic.co>\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"63e116bb078c29c70e4e23cba1c88d0ac022801d"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: José Luis González <joseluisgj@gmail.com>
This commit is contained in:
parent
0e205fae8f
commit
529d04f2fb
34 changed files with 2336 additions and 158 deletions
|
@ -45,6 +45,7 @@ interface ConnectorConfigurationProps {
|
|||
hasPlatinumLicense: boolean;
|
||||
isLoading: boolean;
|
||||
saveConfig: (configuration: Record<string, string | number | boolean | null>) => void;
|
||||
saveAndSync?: (configuration: Record<string, string | number | boolean | null>) => void;
|
||||
stackManagementLink?: string;
|
||||
subscriptionLink?: string;
|
||||
children?: React.ReactNode;
|
||||
|
@ -90,6 +91,7 @@ export const ConnectorConfigurationComponent: FC<
|
|||
hasPlatinumLicense,
|
||||
isLoading,
|
||||
saveConfig,
|
||||
saveAndSync,
|
||||
subscriptionLink,
|
||||
stackManagementLink,
|
||||
}) => {
|
||||
|
@ -166,6 +168,12 @@ export const ConnectorConfigurationComponent: FC<
|
|||
saveConfig(config);
|
||||
setIsEditing(false);
|
||||
}}
|
||||
{...(saveAndSync && {
|
||||
saveAndSync: (config) => {
|
||||
saveAndSync(config);
|
||||
setIsEditing(false);
|
||||
},
|
||||
})}
|
||||
/>
|
||||
) : (
|
||||
uncategorizedDisplayList.length > 0 && (
|
||||
|
|
|
@ -36,6 +36,7 @@ interface ConnectorConfigurationForm {
|
|||
isLoading: boolean;
|
||||
isNative: boolean;
|
||||
saveConfig: (config: Record<string, string | number | boolean | null>) => void;
|
||||
saveAndSync?: (config: Record<string, string | number | boolean | null>) => void;
|
||||
stackManagementHref?: string;
|
||||
subscriptionLink?: string;
|
||||
}
|
||||
|
@ -60,6 +61,7 @@ export const ConnectorConfigurationForm: React.FC<ConnectorConfigurationForm> =
|
|||
isLoading,
|
||||
isNative,
|
||||
saveConfig,
|
||||
saveAndSync,
|
||||
}) => {
|
||||
const [localConfig, setLocalConfig] = useState<ConnectorConfiguration>(configuration);
|
||||
const [configView, setConfigView] = useState<ConfigView>(
|
||||
|
@ -167,19 +169,7 @@ export const ConnectorConfigurationForm: React.FC<ConnectorConfigurationForm> =
|
|||
)}
|
||||
<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>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-telemetry-id="entSearchContent-connector-configuration-cancelEdit"
|
||||
|
@ -196,6 +186,38 @@ export const ConnectorConfigurationForm: React.FC<ConnectorConfigurationForm> =
|
|||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<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',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
{saveAndSync && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="entSearchContent-connector-configuration-saveConfiguration"
|
||||
data-telemetry-id="entSearchContent-connector-configuration-saveConfiguration"
|
||||
isLoading={isLoading}
|
||||
fill
|
||||
onClick={() => {
|
||||
saveAndSync(configViewToConfigValues(configView));
|
||||
}}
|
||||
>
|
||||
{i18n.translate(
|
||||
'searchConnectors.configurationConnector.config.submitButton.title',
|
||||
{
|
||||
defaultMessage: 'Save and sync',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import dedent from 'dedent';
|
||||
|
||||
import {
|
||||
ENTERPRISE_SEARCH_APP_ID,
|
||||
ENTERPRISE_SEARCH_CONTENT_APP_ID,
|
||||
|
@ -210,6 +212,58 @@ export const SEARCH_RELEVANCE_PLUGIN = {
|
|||
SUPPORT_URL: 'https://discuss.elastic.co/c/enterprise-search/',
|
||||
};
|
||||
|
||||
export const CREATE_CONNECTOR_PLUGIN = {
|
||||
CLI_SNIPPET: dedent`./bin/connectors connector create
|
||||
--index-name my-index
|
||||
--index-language en
|
||||
--from-file config.yml
|
||||
`,
|
||||
CONSOLE_SNIPPET: dedent`# Create an index
|
||||
PUT /my-index-000001
|
||||
{
|
||||
"settings": {
|
||||
"index": {
|
||||
"number_of_shards": 3,
|
||||
"number_of_replicas": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Create an API key
|
||||
POST /_security/api_key
|
||||
{
|
||||
"name": "my-api-key",
|
||||
"expiration": "1d",
|
||||
"role_descriptors":
|
||||
{
|
||||
"role-a": {
|
||||
"cluster": ["all"],
|
||||
"indices": [
|
||||
{
|
||||
"names": ["index-a*"],
|
||||
"privileges": ["read"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"role-b": {
|
||||
"cluster": ["all"],
|
||||
"indices": [
|
||||
{
|
||||
"names": ["index-b*"],
|
||||
"privileges": ["all"]
|
||||
}]
|
||||
}
|
||||
}, "metadata":
|
||||
{ "application": "my-application",
|
||||
"environment": {
|
||||
"level": 1,
|
||||
"trusted": true,
|
||||
"tags": ["dev", "staging"]
|
||||
}
|
||||
}
|
||||
}`,
|
||||
};
|
||||
|
||||
export const LICENSED_SUPPORT_URL = 'https://support.elastic.co';
|
||||
|
||||
export const JSON_HEADER = {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { createApiLogic } from '../../../shared/api_logic/create_api_logic';
|
||||
import { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic';
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
|
||||
interface AddConnectorValue {
|
||||
|
@ -20,11 +20,17 @@ export interface AddConnectorApiLogicArgs {
|
|||
language: string | null;
|
||||
name: string;
|
||||
serviceType?: string;
|
||||
// Without a proper refactoring there is no good way to chain actions.
|
||||
// This prop is simply passed back with the result to let listeners
|
||||
// know what was the intent of the request. And call the next action
|
||||
// accordingly.
|
||||
uiFlags?: Record<string, boolean>;
|
||||
}
|
||||
|
||||
export interface AddConnectorApiLogicResponse {
|
||||
id: string;
|
||||
indexName: string;
|
||||
uiFlags?: Record<string, boolean>;
|
||||
}
|
||||
|
||||
export const addConnector = async ({
|
||||
|
@ -34,6 +40,7 @@ export const addConnector = async ({
|
|||
isNative,
|
||||
language,
|
||||
serviceType,
|
||||
uiFlags,
|
||||
}: AddConnectorApiLogicArgs): Promise<AddConnectorApiLogicResponse> => {
|
||||
const route = '/internal/enterprise_search/connectors';
|
||||
|
||||
|
@ -54,7 +61,12 @@ export const addConnector = async ({
|
|||
return {
|
||||
id: result.id,
|
||||
indexName: result.index_name,
|
||||
uiFlags,
|
||||
};
|
||||
};
|
||||
|
||||
export const AddConnectorApiLogic = createApiLogic(['add_connector_api_logic'], addConnector);
|
||||
export type AddConnectorApiLogicActions = Actions<
|
||||
AddConnectorApiLogicArgs,
|
||||
AddConnectorApiLogicResponse
|
||||
>;
|
||||
|
|
|
@ -5,13 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { createApiLogic } from '../../../shared/api_logic/create_api_logic';
|
||||
import { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic';
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
|
||||
export interface GenerateConfigApiArgs {
|
||||
connectorId: string;
|
||||
}
|
||||
|
||||
export type GenerateConfigApiActions = Actions<GenerateConfigApiArgs, {}>;
|
||||
|
||||
export const generateConnectorConfig = async ({ connectorId }: GenerateConfigApiArgs) => {
|
||||
const route = `/internal/enterprise_search/connectors/${connectorId}/generate_config`;
|
||||
return await HttpLogic.values.http.post(route);
|
||||
|
|
|
@ -4,23 +4,38 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { createApiLogic } from '../../../shared/api_logic/create_api_logic';
|
||||
import { Actions, createApiLogic } from '../../../shared/api_logic/create_api_logic';
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
|
||||
export interface GenerateConnectorNamesApiArgs {
|
||||
connectorName?: string;
|
||||
connectorType?: string;
|
||||
}
|
||||
|
||||
export interface GenerateConnectorNamesApiResponse {
|
||||
apiKeyName: string;
|
||||
connectorName: string;
|
||||
indexName: string;
|
||||
}
|
||||
|
||||
export const generateConnectorNames = async (
|
||||
{ connectorType }: GenerateConnectorNamesApiArgs = { connectorType: 'custom' }
|
||||
{ connectorType, connectorName }: GenerateConnectorNamesApiArgs = { connectorType: 'custom' }
|
||||
) => {
|
||||
if (connectorType === '') {
|
||||
connectorType = 'custom';
|
||||
}
|
||||
const route = `/internal/enterprise_search/connectors/generate_connector_name`;
|
||||
return await HttpLogic.values.http.post(route, {
|
||||
body: JSON.stringify({ connectorType }),
|
||||
body: JSON.stringify({ connectorName, connectorType }),
|
||||
});
|
||||
};
|
||||
|
||||
export const GenerateConnectorNamesApiLogic = createApiLogic(
|
||||
['generate_config_api_logic'],
|
||||
['generate_connector_names_api_logic'],
|
||||
generateConnectorNames
|
||||
);
|
||||
|
||||
export type GenerateConnectorNamesApiLogicActions = Actions<
|
||||
GenerateConnectorNamesApiArgs,
|
||||
GenerateConnectorNamesApiResponse
|
||||
>;
|
||||
|
|
|
@ -12,13 +12,15 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
export interface GenerateConfigButtonProps {
|
||||
connectorId: string;
|
||||
disabled?: boolean;
|
||||
generateConfiguration: (params: { connectorId: string }) => void;
|
||||
isGenerateLoading: boolean;
|
||||
}
|
||||
export const GenerateConfigButton: React.FC<GenerateConfigButtonProps> = ({
|
||||
connectorId,
|
||||
disabled,
|
||||
generateConfiguration,
|
||||
isGenerateLoading,
|
||||
isGenerateLoading = false,
|
||||
}) => {
|
||||
return (
|
||||
<EuiFlexGroup direction="row" gutterSize="xs" responsive={false} alignItems="center">
|
||||
|
@ -26,6 +28,7 @@ export const GenerateConfigButton: React.FC<GenerateConfigButtonProps> = ({
|
|||
<EuiButton
|
||||
data-test-subj="entSearchContent-connector-configuration-generateConfigButton"
|
||||
data-telemetry-id="entSearchContent-connector-configuration-generateConfigButton"
|
||||
disabled={disabled}
|
||||
fill
|
||||
iconType="sparkles"
|
||||
isLoading={isGenerateLoading}
|
||||
|
|
|
@ -36,7 +36,7 @@ import { CONNECTOR_DETAIL_PATH, SEARCH_INDEX_PATH } from '../../../routes';
|
|||
export interface GeneratedConfigFieldsProps {
|
||||
apiKey?: ApiKey;
|
||||
connector: Connector;
|
||||
generateApiKey: () => void;
|
||||
generateApiKey?: () => void;
|
||||
isGenerateLoading: boolean;
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ export const GeneratedConfigFields: React.FC<GeneratedConfigFieldsProps> = ({
|
|||
};
|
||||
|
||||
const onConfirm = () => {
|
||||
generateApiKey();
|
||||
if (generateApiKey) generateApiKey();
|
||||
setIsModalVisible(false);
|
||||
};
|
||||
|
||||
|
@ -222,16 +222,18 @@ export const GeneratedConfigFields: React.FC<GeneratedConfigFieldsProps> = ({
|
|||
<EuiFlexItem>
|
||||
<EuiCode>{apiKey?.encoded}</EuiCode>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
data-test-subj="enterpriseSearchGeneratedConfigFieldsButton"
|
||||
size="xs"
|
||||
iconType="refresh"
|
||||
isLoading={isGenerateLoading}
|
||||
onClick={refreshButtonClick}
|
||||
disabled={!connector.index_name}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{generateApiKey && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
data-test-subj="enterpriseSearchGeneratedConfigFieldsButton"
|
||||
size="xs"
|
||||
iconType="refresh"
|
||||
isLoading={isGenerateLoading}
|
||||
onClick={refreshButtonClick}
|
||||
disabled={!connector.index_name}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
size="xs"
|
||||
|
@ -245,16 +247,18 @@ export const GeneratedConfigFields: React.FC<GeneratedConfigFieldsProps> = ({
|
|||
</EuiCopy>
|
||||
</EuiFlexItem>
|
||||
) : (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
data-test-subj="enterpriseSearchGeneratedConfigFieldsButton"
|
||||
size="xs"
|
||||
iconType="refresh"
|
||||
isLoading={isGenerateLoading}
|
||||
onClick={refreshButtonClick}
|
||||
disabled={!connector.index_name}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
generateApiKey && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
data-test-subj="enterpriseSearchGeneratedConfigFieldsButton"
|
||||
size="xs"
|
||||
iconType="refresh"
|
||||
isLoading={isGenerateLoading}
|
||||
onClick={refreshButtonClick}
|
||||
disabled={!connector.index_name}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -61,6 +61,22 @@ export const ConnectorDeployment: React.FC = () => {
|
|||
Record<string, { deploymentMethod: 'docker' | 'source' }>
|
||||
>('search:connector-ui-options', {});
|
||||
|
||||
useEffect(() => {
|
||||
if (connectorId && connector && connector.api_key_id) {
|
||||
getApiKeyById(connector.api_key_id);
|
||||
}
|
||||
}, [connector, connectorId]);
|
||||
|
||||
const selectDeploymentMethod = (deploymentMethod: 'docker' | 'source') => {
|
||||
if (connector) {
|
||||
setSelectedDeploymentMethod(deploymentMethod);
|
||||
setConnectorUiOptions({
|
||||
...connectorUiOptions,
|
||||
[connector.id]: { deploymentMethod },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (connectorUiOptions && connectorId && connectorUiOptions[connectorId]) {
|
||||
setSelectedDeploymentMethod(connectorUiOptions[connectorId].deploymentMethod);
|
||||
|
@ -68,25 +84,10 @@ export const ConnectorDeployment: React.FC = () => {
|
|||
selectDeploymentMethod('docker');
|
||||
}
|
||||
}, [connectorUiOptions, connectorId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (connectorId && connector && connector.api_key_id) {
|
||||
getApiKeyById(connector.api_key_id);
|
||||
}
|
||||
}, [connector, connectorId]);
|
||||
|
||||
if (!connector || connector.is_native) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const selectDeploymentMethod = (deploymentMethod: 'docker' | 'source') => {
|
||||
setSelectedDeploymentMethod(deploymentMethod);
|
||||
setConnectorUiOptions({
|
||||
...connectorUiOptions,
|
||||
[connector.id]: { deploymentMethod },
|
||||
});
|
||||
};
|
||||
|
||||
const hasApiKey = !!(connector.api_key_id ?? generatedData?.apiKey);
|
||||
|
||||
const isWaitingForConnector = !connector.status || connector.status === ConnectorStatus.CREATED;
|
||||
|
|
|
@ -10,15 +10,12 @@ import { kea, MakeLogicType } from 'kea';
|
|||
import { Connector } from '@kbn/search-connectors';
|
||||
|
||||
import { HttpError, Status } from '../../../../../common/types/api';
|
||||
import { Actions } from '../../../shared/api_logic/create_api_logic';
|
||||
import {
|
||||
GenerateConfigApiArgs,
|
||||
GenerateConfigApiActions,
|
||||
GenerateConfigApiLogic,
|
||||
} from '../../api/connector/generate_connector_config_api_logic';
|
||||
import { APIKeyResponse } from '../../api/generate_api_key/generate_api_key_logic';
|
||||
|
||||
type GenerateConfigApiActions = Actions<GenerateConfigApiArgs, {}>;
|
||||
|
||||
export interface DeploymentLogicValues {
|
||||
generateConfigurationError: HttpError;
|
||||
generateConfigurationStatus: Status;
|
||||
|
|
|
@ -44,8 +44,8 @@ import { ConnectorStats } from './connector_stats';
|
|||
import { ConnectorsLogic } from './connectors_logic';
|
||||
import { ConnectorsTable } from './connectors_table';
|
||||
import { CrawlerEmptyState } from './crawler_empty_state';
|
||||
import { CreateConnector } from './create_connector';
|
||||
import { DeleteConnectorModal } from './delete_connector_modal';
|
||||
import { SelectConnector } from './select_connector/select_connector';
|
||||
|
||||
export const connectorsBreadcrumbs = [
|
||||
i18n.translate('xpack.enterpriseSearch.content.connectors.breadcrumb', {
|
||||
|
@ -81,7 +81,7 @@ export const Connectors: React.FC<ConnectorsProps> = ({ isCrawler }) => {
|
|||
}, [searchParams.from, searchParams.size, searchQuery, isCrawler]);
|
||||
|
||||
return !isLoading && isEmpty && !isCrawler ? (
|
||||
<SelectConnector />
|
||||
<CreateConnector />
|
||||
) : (
|
||||
<>
|
||||
<DeleteConnectorModal isCrawler={isCrawler} />
|
||||
|
|
|
@ -13,23 +13,27 @@ import {
|
|||
CONNECTORS_PATH,
|
||||
NEW_INDEX_SELECT_CONNECTOR_PATH,
|
||||
NEW_CONNECTOR_PATH,
|
||||
NEW_CONNECTOR_FLOW_PATH,
|
||||
CONNECTOR_DETAIL_PATH,
|
||||
} from '../../routes';
|
||||
import { ConnectorDetailRouter } from '../connector_detail/connector_detail_router';
|
||||
import { NewSearchIndexPage } from '../new_index/new_search_index_page';
|
||||
|
||||
import { Connectors } from './connectors';
|
||||
import { SelectConnector } from './select_connector/select_connector';
|
||||
import { CreateConnector } from './create_connector';
|
||||
|
||||
export const ConnectorsRouter: React.FC = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path={NEW_INDEX_SELECT_CONNECTOR_PATH}>
|
||||
<SelectConnector />
|
||||
<CreateConnector />
|
||||
</Route>
|
||||
<Route path={NEW_CONNECTOR_PATH}>
|
||||
<NewSearchIndexPage type="connector" />
|
||||
</Route>
|
||||
<Route path={NEW_CONNECTOR_FLOW_PATH}>
|
||||
<CreateConnector />
|
||||
</Route>
|
||||
<Route path={CONNECTORS_PATH} exact>
|
||||
<Connectors isCrawler={false} />
|
||||
</Route>
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<svg width="41" height="40" viewBox="0 0 41 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1064_10468)">
|
||||
<path d="M17.2647 32.0743L8.42591 23.2352L11.2142 22.4883L18.0118 29.2859L27.2975 26.7978L29.7856 17.5121L22.9881 10.7146L23.7353 7.92622L32.574 16.7649L29.7124 27.4446L35.8997 33.632L35.5262 35.026L34.1319 35.3996L27.9448 29.2125L17.2647 32.0743Z" fill="#535766"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.9192 24.4199L18.8822 26.0375L14.4628 21.6181L15.7069 16.9752L5.10035 6.36864L5.47397 4.97427L6.86799 4.60074L17.4747 15.2074L22.1175 13.9634L26.5369 18.3828L24.9192 24.4199ZM23.7485 19.13L22.878 22.3786L19.6294 23.2491L17.2512 20.8709L18.1217 17.6223L21.3703 16.7518L23.7485 19.13Z" fill="#00BFB3"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1064_10468">
|
||||
<rect width="40" height="40" fill="white" transform="translate(0.5)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 897 B |
Binary file not shown.
After Width: | Height: | Size: 79 KiB |
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* 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, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiInputPopover,
|
||||
EuiSelectable,
|
||||
EuiSelectableOption,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { KibanaLogic } from '../../../../../shared/kibana';
|
||||
import { NewConnectorLogic } from '../../../new_index/method_connector/new_connector_logic';
|
||||
import { SelfManagePreference } from '../create_connector';
|
||||
|
||||
interface ChooseConnectorSelectableProps {
|
||||
selfManaged: SelfManagePreference;
|
||||
}
|
||||
interface OptionData {
|
||||
secondaryContent?: string;
|
||||
}
|
||||
|
||||
export const ChooseConnectorSelectable: React.FC<ChooseConnectorSelectableProps> = ({
|
||||
selfManaged,
|
||||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectableOptions, selectableSetOptions] = useState<
|
||||
Array<EuiSelectableOption<OptionData>>
|
||||
>([]);
|
||||
const { connectorTypes } = useValues(KibanaLogic);
|
||||
const allConnectors = useMemo(
|
||||
() => connectorTypes.sort((a, b) => a.name.localeCompare(b.name)),
|
||||
[connectorTypes]
|
||||
);
|
||||
const { selectedConnector } = useValues(NewConnectorLogic);
|
||||
const { setSelectedConnector } = useActions(NewConnectorLogic);
|
||||
|
||||
const getInitialOptions = () => {
|
||||
return allConnectors.map((connector, key) => {
|
||||
const append: JSX.Element[] = [];
|
||||
if (connector.isTechPreview) {
|
||||
append.push(
|
||||
<EuiBadge key={key + '-preview'} iconType="beaker" color="hollow">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.thechPreviewBadgeLabel',
|
||||
{ defaultMessage: 'Tech preview' }
|
||||
)}
|
||||
</EuiBadge>
|
||||
);
|
||||
}
|
||||
if (connector.isBeta) {
|
||||
append.push(
|
||||
<EuiBadge key={key + '-beta'} iconType={'beta'} color="hollow">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.BetaBadgeLabel',
|
||||
{
|
||||
defaultMessage: 'Beta',
|
||||
}
|
||||
)}
|
||||
</EuiBadge>
|
||||
);
|
||||
}
|
||||
if (selfManaged === 'native' && !connector.isNative) {
|
||||
append.push(
|
||||
<EuiBadge key={key + '-self'} iconType={'warning'} color="warning">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.OnlySelfManagedBadgeLabel',
|
||||
{
|
||||
defaultMessage: 'Self managed',
|
||||
}
|
||||
)}
|
||||
</EuiBadge>
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
append,
|
||||
key: key.toString(),
|
||||
label: connector.name,
|
||||
prepend: <EuiIcon size="l" type={connector.iconPath} />,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const initialOptions = getInitialOptions();
|
||||
|
||||
useEffect(() => {
|
||||
selectableSetOptions(initialOptions);
|
||||
}, [selfManaged]);
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
|
||||
const openPopover = useCallback(() => {
|
||||
setIsOpen(true);
|
||||
}, []);
|
||||
const closePopover = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<EuiFlexItem>
|
||||
<EuiSelectable
|
||||
aria-label={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.euiSelectable.selectableInputPopoverLabel',
|
||||
{ defaultMessage: 'Select a data source for your connector to use.' }
|
||||
)}
|
||||
options={selectableOptions}
|
||||
onChange={(newOptions, _, changedOption) => {
|
||||
selectableSetOptions(newOptions);
|
||||
closePopover();
|
||||
if (changedOption.checked === 'on') {
|
||||
const keySelected = Number(changedOption.key);
|
||||
setSelectedConnector(allConnectors[keySelected]);
|
||||
setSearchValue(allConnectors[keySelected].name);
|
||||
} else {
|
||||
setSelectedConnector(null);
|
||||
setSearchValue('');
|
||||
}
|
||||
}}
|
||||
listProps={{
|
||||
isVirtualized: true,
|
||||
rowHeight: Number(euiTheme.base * 3),
|
||||
showIcons: false,
|
||||
}}
|
||||
singleSelection
|
||||
searchable
|
||||
searchProps={{
|
||||
fullWidth: true,
|
||||
isClearable: true,
|
||||
onChange: (value) => {
|
||||
if (value !== selectedConnector?.name) {
|
||||
setSearchValue(value);
|
||||
}
|
||||
},
|
||||
onClick: openPopover,
|
||||
onFocus: openPopover,
|
||||
placeholder: i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.chooseConnectorSelectable.placeholder.text',
|
||||
{ defaultMessage: 'Choose a data source' }
|
||||
),
|
||||
value: searchValue,
|
||||
}}
|
||||
>
|
||||
{(list, search) => (
|
||||
<EuiInputPopover
|
||||
fullWidth
|
||||
closePopover={closePopover}
|
||||
disableFocusTrap
|
||||
closeOnScroll
|
||||
isOpen={isOpen}
|
||||
input={search!}
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
{list}
|
||||
</EuiInputPopover>
|
||||
)}
|
||||
</EuiSelectable>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* 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, { useState } from 'react';
|
||||
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiCallOut,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiPanel,
|
||||
EuiPopover,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import connectorLogo from '../../../../../../assets/images/connector_logo_network_drive_version.svg';
|
||||
|
||||
const nativePopoverPanels = [
|
||||
{
|
||||
description: i18n.translate(
|
||||
'xpack.enterpriseSearch.connectorDescriptionPopover.connectorDescriptionBadge.native.chooseADataSourceLabel',
|
||||
{ defaultMessage: 'Choose a data source you would like to sync' }
|
||||
),
|
||||
icons: [<EuiIcon type="documents" />],
|
||||
id: 'native-choose-source',
|
||||
},
|
||||
{
|
||||
description: i18n.translate(
|
||||
'xpack.enterpriseSearch.connectorDescriptionPopover.connectorDescriptionBadge.native.configureConnectorLabel',
|
||||
{ defaultMessage: 'Configure your connector using our Kibana UI' }
|
||||
),
|
||||
icons: [<EuiIcon type={connectorLogo} />, <EuiIcon type="logoElastic" />],
|
||||
id: 'native-configure-connector',
|
||||
},
|
||||
];
|
||||
|
||||
const connectorClientPopoverPanels = [
|
||||
{
|
||||
description: i18n.translate(
|
||||
'xpack.enterpriseSearch.connectorDescriptionPopover.connectorDescriptionBadge.client.chooseADataSourceLabel',
|
||||
{ defaultMessage: 'Choose a data source you would like to sync' }
|
||||
),
|
||||
icons: [<EuiIcon type="documents" />],
|
||||
id: 'client-choose-source',
|
||||
},
|
||||
{
|
||||
description: i18n.translate(
|
||||
'xpack.enterpriseSearch.connectorDescriptionPopover.connectorDescriptionBadge.client.configureConnectorLabel',
|
||||
{
|
||||
defaultMessage:
|
||||
'Deploy connector code on your own infrastructure by running from source or using Docker',
|
||||
}
|
||||
),
|
||||
icons: [
|
||||
<EuiIcon type={connectorLogo} />,
|
||||
<EuiIcon type="sortRight" />,
|
||||
<EuiIcon type="launch" />,
|
||||
],
|
||||
id: 'client-deploy',
|
||||
},
|
||||
{
|
||||
description: i18n.translate(
|
||||
'xpack.enterpriseSearch.connectorDescriptionPopover.connectorDescriptionBadge.client.enterDetailsLabel',
|
||||
{
|
||||
defaultMessage: 'Enter access and connection details for your data source',
|
||||
}
|
||||
),
|
||||
icons: [
|
||||
<EuiIcon type="documents" />,
|
||||
<EuiIcon type="sortRight" />,
|
||||
<EuiIcon type={connectorLogo} />,
|
||||
<EuiIcon type="sortRight" />,
|
||||
<EuiIcon type="logoElastic" />,
|
||||
],
|
||||
id: 'client-configure-connector',
|
||||
},
|
||||
];
|
||||
|
||||
export interface ConnectorDescriptionPopoverProps {
|
||||
isDisabled: boolean;
|
||||
isNative: boolean;
|
||||
}
|
||||
|
||||
export const ConnectorDescriptionPopover: React.FC<ConnectorDescriptionPopoverProps> = ({
|
||||
isNative,
|
||||
isDisabled,
|
||||
}) => {
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const panels = isNative ? nativePopoverPanels : connectorClientPopoverPanels;
|
||||
return (
|
||||
<EuiPopover
|
||||
anchorPosition="upCenter"
|
||||
button={
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.translate('xpack.enterpriseSearch.createConnector.iInCircle', {
|
||||
defaultMessage: 'More information',
|
||||
})}
|
||||
data-test-subj="enterpriseSearchConnectorDescriptionPopoverButton"
|
||||
iconType="iInCircle"
|
||||
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
|
||||
/>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={() => {
|
||||
setIsPopoverOpen(false);
|
||||
}}
|
||||
>
|
||||
<EuiPanel hasBorder={false} hasShadow={false}>
|
||||
{isDisabled && (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.connectorDescriptionBadge.notAvailableTitle',
|
||||
{
|
||||
defaultMessage:
|
||||
'This connector is not available as an Elastic-managed Connector',
|
||||
}
|
||||
)}
|
||||
size="s"
|
||||
iconType="warning"
|
||||
color="warning"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup>
|
||||
{panels.map((panel) => {
|
||||
return (
|
||||
<EuiFlexItem grow={false} key={panel.id}>
|
||||
<EuiFlexGroup
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
gutterSize="s"
|
||||
style={{ maxWidth: 200 }}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup responsive={false} gutterSize="s">
|
||||
{panel.icons.map((icon, index) => (
|
||||
<EuiFlexItem grow={false} key={index}>
|
||||
{icon}
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s" grow={false} textAlign="center">
|
||||
<p>{panel.description}</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* 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, { useState } from 'react';
|
||||
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiContextMenuItem,
|
||||
EuiContextMenuPanel,
|
||||
EuiPopover,
|
||||
useGeneratedHtmlId,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { SelfManagePreference } from '../create_connector';
|
||||
|
||||
import { ManualConfigurationFlyout } from './manual_configuration_flyout';
|
||||
|
||||
export interface ManualConfigurationProps {
|
||||
isDisabled: boolean;
|
||||
selfManagePreference: SelfManagePreference;
|
||||
}
|
||||
|
||||
export const ManualConfiguration: React.FC<ManualConfigurationProps> = ({
|
||||
isDisabled,
|
||||
selfManagePreference,
|
||||
}) => {
|
||||
const [isPopoverOpen, setPopover] = useState(false);
|
||||
const splitButtonPopoverId = useGeneratedHtmlId({
|
||||
prefix: 'splitButtonPopover',
|
||||
});
|
||||
const onButtonClick = () => {
|
||||
setPopover(!isPopoverOpen);
|
||||
};
|
||||
|
||||
const closePopover = () => {
|
||||
setPopover(false);
|
||||
};
|
||||
|
||||
const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
|
||||
const [flyoutContent, setFlyoutContent] = useState<'manual_config' | 'client'>();
|
||||
|
||||
const items = [
|
||||
<EuiContextMenuItem
|
||||
key="copy"
|
||||
icon="wrench"
|
||||
onClick={() => {
|
||||
setFlyoutContent('manual_config');
|
||||
setIsFlyoutVisible(true);
|
||||
closePopover();
|
||||
}}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.manageAttachedIndexContextMenuItemLabel',
|
||||
{ defaultMessage: 'Manual configuration' }
|
||||
)}
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
key="share"
|
||||
icon="console"
|
||||
onClick={() => {
|
||||
setFlyoutContent('client');
|
||||
setIsFlyoutVisible(true);
|
||||
closePopover();
|
||||
}}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.scheduleASyncContextMenuItemLabel',
|
||||
{
|
||||
defaultMessage: 'Try with CLI',
|
||||
}
|
||||
)}
|
||||
</EuiContextMenuItem>,
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiPopover
|
||||
id={splitButtonPopoverId}
|
||||
button={
|
||||
<EuiButtonIcon
|
||||
data-test-subj="enterpriseSearchFinishUpStepButton"
|
||||
display="fill"
|
||||
disabled={isDisabled}
|
||||
size="m"
|
||||
iconType="boxesVertical"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.euiButtonIcon.moreLabel',
|
||||
{ defaultMessage: 'More' }
|
||||
)}
|
||||
onClick={onButtonClick}
|
||||
/>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={closePopover}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenuPanel items={items} />
|
||||
</EuiPopover>
|
||||
{isFlyoutVisible && (
|
||||
<ManualConfigurationFlyout
|
||||
setIsFlyoutVisible={setIsFlyoutVisible}
|
||||
flyoutContent={flyoutContent}
|
||||
selfManagePreference={selfManagePreference}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
* 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,
|
||||
EuiCode,
|
||||
EuiCodeBlock,
|
||||
EuiFieldText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyout,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiFlyoutHeader,
|
||||
EuiFormRow,
|
||||
EuiLink,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
useGeneratedHtmlId,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { CREATE_CONNECTOR_PLUGIN } from '../../../../../../../common/constants';
|
||||
import { NewConnectorLogic } from '../../../new_index/method_connector/new_connector_logic';
|
||||
|
||||
import { SelfManagePreference } from '../create_connector';
|
||||
|
||||
const CLI_LABEL = i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.manualConfiguration.cliLabel',
|
||||
{
|
||||
defaultMessage: 'CLI',
|
||||
}
|
||||
);
|
||||
|
||||
export interface ManualConfigurationFlyoutProps {
|
||||
flyoutContent: string | undefined;
|
||||
selfManagePreference: SelfManagePreference;
|
||||
setIsFlyoutVisible: (value: boolean) => void;
|
||||
}
|
||||
export const ManualConfigurationFlyout: React.FC<ManualConfigurationFlyoutProps> = ({
|
||||
flyoutContent,
|
||||
selfManagePreference,
|
||||
setIsFlyoutVisible,
|
||||
}) => {
|
||||
const simpleFlyoutTitleId = useGeneratedHtmlId({
|
||||
prefix: 'simpleFlyoutTitle',
|
||||
});
|
||||
|
||||
const { connectorName } = useValues(NewConnectorLogic);
|
||||
const { setRawName, createConnector } = useActions(NewConnectorLogic);
|
||||
|
||||
return (
|
||||
<EuiFlyout
|
||||
ownFocus
|
||||
onClose={() => setIsFlyoutVisible(false)}
|
||||
aria-labelledby={simpleFlyoutTitleId}
|
||||
size="s"
|
||||
>
|
||||
{flyoutContent === 'manual_config' && (
|
||||
<>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<h2 id={simpleFlyoutTitleId}>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.manualConfiguration.h2.cliLabel',
|
||||
{
|
||||
defaultMessage: 'Manual configuration',
|
||||
}
|
||||
)}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText color="subdued" size="s">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.createConnector.flyoutManualConfigContent.p.thisManualOptionIsLabel"
|
||||
defaultMessage="This manual option is an alternative to the {generateConfig} option, here you can bring your already existing index or API key."
|
||||
values={{
|
||||
generateConfig: (
|
||||
<b>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.manualConfiguration.generateConfigLinkLabel',
|
||||
{
|
||||
defaultMessage: 'Generate configuration',
|
||||
}
|
||||
)}
|
||||
</b>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<EuiFlexItem>
|
||||
<EuiPanel hasBorder>
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.manualConfiguration.connectorName',
|
||||
{
|
||||
defaultMessage: 'Connector',
|
||||
}
|
||||
)}
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.startStep.euiFormRow.connectorNameLabel',
|
||||
{ defaultMessage: 'Connector name' }
|
||||
)}
|
||||
>
|
||||
<EuiFieldText
|
||||
data-test-subj="enterpriseSearchStartStepFieldText"
|
||||
fullWidth
|
||||
name="first"
|
||||
value={connectorName}
|
||||
onChange={(e) => {
|
||||
setRawName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText size="xs">
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.manualConfiguration.p.connectorNameDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'You will be redirected to the connector page to configure the rest of your connector',
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="enterpriseSearchFlyoutManualConfigContentCloseButton"
|
||||
iconType="cross"
|
||||
onClick={() => setIsFlyoutVisible(false)}
|
||||
flush="left"
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.flyoutManualConfigContent.closeButtonEmptyLabel',
|
||||
{ defaultMessage: 'Close' }
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="enterpriseSearchFlyoutManualConfigContentSaveButton"
|
||||
onClick={() => {
|
||||
createConnector({
|
||||
isSelfManaged: selfManagePreference === 'selfManaged',
|
||||
shouldGenerateAfterCreate: false,
|
||||
shouldNavigateToConnectorAfterCreate: true,
|
||||
});
|
||||
}}
|
||||
fill
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.flyoutManualConfigContent.saveConfigurationButtonLabel',
|
||||
{ defaultMessage: 'Save configuration' }
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</>
|
||||
)}
|
||||
{flyoutContent === 'client' && (
|
||||
<>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<h2 id={simpleFlyoutTitleId}>{CLI_LABEL}</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.createConnector.manualConfiguration.p.youCanAlsoUseLabel"
|
||||
defaultMessage="You can also use the connectors {cliLink}. The following command creates a new connector attached to the {myIndex}, using configuration from your file."
|
||||
values={{
|
||||
cliLink: (
|
||||
<EuiLink
|
||||
data-test-subj="enterpriseSearchManualConfigurationConnectorsCliLink"
|
||||
href="https://github.com/elastic/connectors/blob/main/docs/CLI.md"
|
||||
target="_blank"
|
||||
external
|
||||
>
|
||||
{CLI_LABEL}
|
||||
</EuiLink>
|
||||
),
|
||||
myIndex: <EuiCode>my-index</EuiCode>,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCodeBlock language="bash" isCopyable>
|
||||
{CREATE_CONNECTOR_PLUGIN.CLI_SNIPPET}
|
||||
</EuiCodeBlock>
|
||||
</EuiFlyoutBody>
|
||||
</>
|
||||
)}
|
||||
</EuiFlyout>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
EuiText,
|
||||
EuiButton,
|
||||
EuiProgress,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { ConnectorConfigurationComponent, ConnectorStatus } from '@kbn/search-connectors';
|
||||
|
||||
import { Status } from '../../../../../../common/types/api';
|
||||
|
||||
import * as Constants from '../../../../shared/constants';
|
||||
import { ConnectorConfigurationApiLogic } from '../../../api/connector/update_connector_configuration_api_logic';
|
||||
import { ConnectorViewLogic } from '../../connector_detail/connector_view_logic';
|
||||
|
||||
interface ConfigurationStepProps {
|
||||
setCurrentStep: Function;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const ConfigurationStep: React.FC<ConfigurationStepProps> = ({ title, setCurrentStep }) => {
|
||||
const { connector } = useValues(ConnectorViewLogic);
|
||||
const { updateConnectorConfiguration } = useActions(ConnectorViewLogic);
|
||||
const { status } = useValues(ConnectorConfigurationApiLogic);
|
||||
const isSyncing = false;
|
||||
|
||||
const isNextStepEnabled =
|
||||
connector?.status === ConnectorStatus.CONNECTED ||
|
||||
connector?.status === ConnectorStatus.CONFIGURED;
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
window.scrollTo({
|
||||
behavior: 'smooth',
|
||||
top: 0,
|
||||
});
|
||||
}, 100);
|
||||
}, []);
|
||||
|
||||
if (!connector) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="m" direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="l" style={{ position: 'relative' }}>
|
||||
<EuiTitle size="m">
|
||||
<h3>{title}</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<ConnectorConfigurationComponent
|
||||
connector={connector}
|
||||
hasPlatinumLicense
|
||||
isLoading={status === Status.LOADING}
|
||||
saveConfig={(config) => {
|
||||
updateConnectorConfiguration({
|
||||
configuration: config,
|
||||
connectorId: connector.id,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
{isSyncing && (
|
||||
<EuiProgress size="xs" position="absolute" style={{ top: 'calc(100% - 2px)' }} />
|
||||
)}
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="l" color="plain">
|
||||
<EuiText>
|
||||
<h3>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.configurationStep.h4.finishUpLabel',
|
||||
{
|
||||
defaultMessage: 'Finish up',
|
||||
}
|
||||
)}
|
||||
</h3>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText color={isNextStepEnabled ? 'default' : 'subdued'} size="s">
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.configurationStep.p.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'You can manually sync your data, schedule a recurring sync or manage your domains.',
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButton
|
||||
data-test-subj="enterpriseSearchStartStepGenerateConfigurationButton"
|
||||
onClick={() => setCurrentStep('finish')}
|
||||
fill
|
||||
>
|
||||
{Constants.NEXT_BUTTON_LABEL}
|
||||
</EuiButton>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,265 @@
|
|||
/*
|
||||
* 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 { css } from '@emotion/react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiIcon,
|
||||
EuiLink,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiSteps,
|
||||
EuiSuperSelect,
|
||||
EuiText,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { AddConnectorApiLogic } from '../../../api/connector/add_connector_api_logic';
|
||||
import { EnterpriseSearchContentPageTemplate } from '../../layout';
|
||||
import { NewConnectorLogic } from '../../new_index/method_connector/new_connector_logic';
|
||||
import { errorToText } from '../../new_index/utils/error_to_text';
|
||||
import { connectorsBreadcrumbs } from '../connectors';
|
||||
|
||||
import { generateStepState } from '../utils/generate_step_state';
|
||||
|
||||
import connectorsBackgroundImage from './assets/connector_logos_comp.png';
|
||||
|
||||
import { ConfigurationStep } from './configuration_step';
|
||||
import { DeploymentStep } from './deployment_step';
|
||||
import { FinishUpStep } from './finish_up_step';
|
||||
import { StartStep } from './start_step';
|
||||
|
||||
export type ConnectorCreationSteps = 'start' | 'deployment' | 'configure' | 'finish';
|
||||
export type SelfManagePreference = 'native' | 'selfManaged';
|
||||
export const CreateConnector: React.FC = () => {
|
||||
const { error } = useValues(AddConnectorApiLogic);
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const [selfManagePreference, setSelfManagePreference] = useState<SelfManagePreference>('native');
|
||||
|
||||
const { selectedConnector, currentStep } = useValues(NewConnectorLogic);
|
||||
const { setCurrentStep } = useActions(NewConnectorLogic);
|
||||
const stepStates = generateStepState(currentStep);
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: separate this to ability and preference
|
||||
if (!selectedConnector?.isNative || !selfManagePreference) {
|
||||
setSelfManagePreference('selfManaged');
|
||||
} else {
|
||||
setSelfManagePreference('native');
|
||||
}
|
||||
}, [selectedConnector]);
|
||||
|
||||
const getSteps = (selfManaged: boolean): EuiContainedStepProps[] => {
|
||||
return [
|
||||
{
|
||||
children: null,
|
||||
status: stepStates.start,
|
||||
title: i18n.translate('xpack.enterpriseSearch.createConnector.startStep.startLabel', {
|
||||
defaultMessage: 'Start',
|
||||
}),
|
||||
},
|
||||
...(selfManaged
|
||||
? [
|
||||
{
|
||||
children: null,
|
||||
status: stepStates.deployment,
|
||||
title: i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.deploymentStep.deploymentLabel',
|
||||
{ defaultMessage: 'Deployment' }
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
children: null,
|
||||
status: stepStates.configure,
|
||||
title: i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.configurationStep.configurationLabel',
|
||||
{ defaultMessage: 'Configuration' }
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
children: null,
|
||||
status: stepStates.finish,
|
||||
title: i18n.translate('xpack.enterpriseSearch.createConnector.finishUpStep.finishUpLabel', {
|
||||
defaultMessage: 'Finish up',
|
||||
}),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const stepContent: Record<'start' | 'deployment' | 'configure' | 'finish', React.ReactNode> = {
|
||||
configure: (
|
||||
<ConfigurationStep
|
||||
title={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.configurationStep.configurationLabel',
|
||||
{ defaultMessage: 'Configuration' }
|
||||
)}
|
||||
setCurrentStep={setCurrentStep}
|
||||
/>
|
||||
),
|
||||
deployment: <DeploymentStep setCurrentStep={setCurrentStep} />,
|
||||
finish: (
|
||||
<FinishUpStep
|
||||
title={i18n.translate('xpack.enterpriseSearch.createConnector.finishUpStep.finishUpLabel', {
|
||||
defaultMessage: 'Finish up',
|
||||
})}
|
||||
/>
|
||||
),
|
||||
start: (
|
||||
<StartStep
|
||||
title={i18n.translate('xpack.enterpriseSearch.createConnector.startStep.startLabel', {
|
||||
defaultMessage: 'Start',
|
||||
})}
|
||||
selfManagePreference={selfManagePreference}
|
||||
setCurrentStep={setCurrentStep}
|
||||
onSelfManagePreferenceChange={(preference) => {
|
||||
setSelfManagePreference(preference);
|
||||
}}
|
||||
error={errorToText(error)}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
return (
|
||||
<EnterpriseSearchContentPageTemplate
|
||||
pageChrome={[
|
||||
...connectorsBreadcrumbs,
|
||||
i18n.translate('xpack.enterpriseSearch.createConnector..breadcrumb', {
|
||||
defaultMessage: 'New connector',
|
||||
}),
|
||||
]}
|
||||
pageViewTelemetry="create_connector"
|
||||
isLoading={false}
|
||||
pageHeader={{
|
||||
description: i18n.translate('xpack.enterpriseSearch.createConnector.description', {
|
||||
defaultMessage: 'Extract, transform, index and sync data from a third-party data source.',
|
||||
}),
|
||||
pageTitle: i18n.translate('xpack.enterpriseSearch.createConnector..title', {
|
||||
defaultMessage: 'Create a connector',
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
{/* Col 1 */}
|
||||
<EuiFlexItem grow={2}>
|
||||
<EuiPanel
|
||||
hasShadow={false}
|
||||
hasBorder
|
||||
color="subdued"
|
||||
paddingSize="l"
|
||||
css={css`
|
||||
${currentStep === 'start'
|
||||
? `background-image: url(${connectorsBackgroundImage});`
|
||||
: ''}
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: bottom center;
|
||||
min-height: 550px;
|
||||
border: 1px solid ${euiTheme.colors.lightShade};
|
||||
`}
|
||||
>
|
||||
<EuiSteps
|
||||
titleSize="xxs"
|
||||
steps={getSteps(selfManagePreference === 'selfManaged')}
|
||||
css={() => css`
|
||||
.euiStep__content {
|
||||
padding-block-end: ${euiTheme.size.xs};
|
||||
}
|
||||
`}
|
||||
/>
|
||||
<EuiSpacer size="xl" />
|
||||
{selectedConnector?.docsUrl && selectedConnector?.docsUrl !== '' && (
|
||||
<>
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
<EuiLink
|
||||
external
|
||||
data-test-subj="enterpriseSearchCreateConnectorConnectorDocsLink"
|
||||
href={selectedConnector?.docsUrl}
|
||||
target="_blank"
|
||||
>
|
||||
{'Elastic '}
|
||||
{selectedConnector?.name}
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.connectorDocsLinkLabel',
|
||||
{ defaultMessage: ' connector reference' }
|
||||
)}
|
||||
</EuiLink>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
)}
|
||||
{currentStep !== 'start' && (
|
||||
<>
|
||||
<EuiFormRow
|
||||
label={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.euiFormRow.connectorLabel',
|
||||
{ defaultMessage: 'Connector' }
|
||||
)}
|
||||
>
|
||||
<EuiSuperSelect
|
||||
readOnly
|
||||
valueOfSelected="item1"
|
||||
options={[
|
||||
{
|
||||
inputDisplay: (
|
||||
<>
|
||||
<EuiIcon
|
||||
size="l"
|
||||
type={selectedConnector?.iconPath ?? ''}
|
||||
css={css`
|
||||
margin-right: ${euiTheme.size.m};
|
||||
`}
|
||||
/>
|
||||
{selectedConnector?.name}
|
||||
</>
|
||||
),
|
||||
value: 'item1',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiBadge color="hollow">
|
||||
{selfManagePreference
|
||||
? i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.badgeType.selfManaged',
|
||||
{
|
||||
defaultMessage: 'Self managed',
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.badgeType.ElasticManaged',
|
||||
{
|
||||
defaultMessage: 'Elastic managed',
|
||||
}
|
||||
)}
|
||||
</EuiBadge>
|
||||
</>
|
||||
)}
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
{/* Col 2 */}
|
||||
<EuiFlexItem grow={7}>{stepContent[currentStep]}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EnterpriseSearchContentPageTemplate>
|
||||
);
|
||||
};
|
|
@ -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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import { EuiFlexItem, EuiPanel, EuiSpacer, EuiText, EuiButton, EuiFlexGroup } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { ConnectorStatus } from '@kbn/search-connectors';
|
||||
|
||||
import * as Constants from '../../../../shared/constants';
|
||||
import { ConnectorViewLogic } from '../../connector_detail/connector_view_logic';
|
||||
import { ConnectorDeployment } from '../../connector_detail/deployment';
|
||||
|
||||
interface DeploymentStepProps {
|
||||
setCurrentStep: Function;
|
||||
}
|
||||
|
||||
export const DeploymentStep: React.FC<DeploymentStepProps> = ({ setCurrentStep }) => {
|
||||
const { connector } = useValues(ConnectorViewLogic);
|
||||
const isNextStepEnabled =
|
||||
connector && !(!connector.status || connector.status === ConnectorStatus.CREATED);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
window.scrollTo({
|
||||
behavior: 'smooth',
|
||||
top: 0,
|
||||
});
|
||||
}, 100);
|
||||
}, []);
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="m" direction="column">
|
||||
<ConnectorDeployment />
|
||||
<EuiFlexItem>
|
||||
<EuiPanel
|
||||
hasShadow={false}
|
||||
hasBorder
|
||||
paddingSize="l"
|
||||
color={isNextStepEnabled ? 'plain' : 'subdued'}
|
||||
>
|
||||
<EuiText color={isNextStepEnabled ? 'default' : 'subdued'}>
|
||||
<h3>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.DeploymentStep.Configuration.title',
|
||||
{
|
||||
defaultMessage: 'Configuration',
|
||||
}
|
||||
)}
|
||||
</h3>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText color={isNextStepEnabled ? 'default' : 'subdued'} size="s">
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.DeploymentStep.Configuration.description',
|
||||
{
|
||||
defaultMessage: 'Now configure your Elastic crawler and sync the data.',
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButton
|
||||
data-test-subj="enterpriseSearchStartStepGenerateConfigurationButton"
|
||||
onClick={() => setCurrentStep('configure')}
|
||||
fill
|
||||
disabled={!isNextStepEnabled}
|
||||
>
|
||||
{Constants.NEXT_BUTTON_LABEL}
|
||||
</EuiButton>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,348 @@
|
|||
/*
|
||||
* 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 { css } from '@emotion/react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiCard,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHorizontalRule,
|
||||
EuiIcon,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
useEuiTheme,
|
||||
EuiProgress,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
|
||||
import { APPLICATIONS_PLUGIN } from '../../../../../../common/constants';
|
||||
|
||||
import { KibanaDeps } from '../../../../../../common/types';
|
||||
|
||||
import { PLAYGROUND_PATH } from '../../../../applications/routes';
|
||||
import { generateEncodedPath } from '../../../../shared/encode_path_params';
|
||||
import { KibanaLogic } from '../../../../shared/kibana';
|
||||
|
||||
import { CONNECTOR_DETAIL_TAB_PATH } from '../../../routes';
|
||||
import { ConnectorDetailTabId } from '../../connector_detail/connector_detail';
|
||||
import { ConnectorViewLogic } from '../../connector_detail/connector_view_logic';
|
||||
import { IndexViewLogic } from '../../search_index/index_view_logic';
|
||||
import { SyncsLogic } from '../../shared/header_actions/syncs_logic';
|
||||
|
||||
import connectorLogo from './assets/connector_logo.svg';
|
||||
|
||||
interface FinishUpStepProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const FinishUpStep: React.FC<FinishUpStepProps> = ({ title }) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const {
|
||||
services: { discover },
|
||||
} = useKibana<KibanaDeps>();
|
||||
const [showNext, setShowNext] = useState(false);
|
||||
|
||||
const { isWaitingForSync, isSyncing: isSyncingProp } = useValues(IndexViewLogic);
|
||||
const { connector } = useValues(ConnectorViewLogic);
|
||||
const { startSync } = useActions(SyncsLogic);
|
||||
|
||||
const isSyncing = isWaitingForSync || isSyncingProp;
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
window.scrollTo({
|
||||
behavior: 'smooth',
|
||||
top: 0,
|
||||
});
|
||||
}, 100);
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="m" direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="l">
|
||||
<EuiTitle size="m">
|
||||
<h3>{title}</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="xl" />
|
||||
{isSyncing && (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<EuiText size="xs">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.syncingDataTextLabel',
|
||||
{
|
||||
defaultMessage: 'Syncing data',
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiProgress
|
||||
size="s"
|
||||
color="success"
|
||||
onClick={() => {
|
||||
setShowNext(true);
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="xl" />
|
||||
</>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
icon={<EuiIcon size="xxl" type="machineLearningApp" />}
|
||||
titleSize="s"
|
||||
title={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.chatWithYourDataLabel',
|
||||
{ defaultMessage: 'Chat with your data' }
|
||||
)}
|
||||
description={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.chatWithYourDataDescriptionl',
|
||||
{
|
||||
defaultMessage:
|
||||
'Combine your data with the power of LLMs for retrieval augmented generation (RAG)',
|
||||
}
|
||||
)}
|
||||
footer={
|
||||
showNext ? (
|
||||
<EuiButton
|
||||
data-test-subj="enterpriseSearchFinishUpStepStartSearchPlaygroundButton"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.euiButton.startSearchPlaygroundLabel',
|
||||
{ defaultMessage: 'Start Search Playground' }
|
||||
)}
|
||||
onClick={() => {
|
||||
if (connector) {
|
||||
KibanaLogic.values.navigateToUrl(
|
||||
`${APPLICATIONS_PLUGIN.URL}${PLAYGROUND_PATH}?default-index=${connector.index_name}`,
|
||||
{ shouldNotCreateHref: true }
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.startSearchPlaygroundButtonLabel',
|
||||
{ defaultMessage: 'Start Search Playground' }
|
||||
)}
|
||||
</EuiButton>
|
||||
) : (
|
||||
<EuiButton
|
||||
data-test-subj="enterpriseSearchFinishUpStepButton"
|
||||
color="warning"
|
||||
iconSide="left"
|
||||
iconType="refresh"
|
||||
isLoading={isSyncing}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.euiButton.firstSyncDataLabel',
|
||||
{ defaultMessage: 'First sync data' }
|
||||
)}
|
||||
onClick={() => {
|
||||
startSync(connector);
|
||||
setShowNext(true);
|
||||
}}
|
||||
>
|
||||
{isSyncing ? 'Syncing data' : 'First sync data'}
|
||||
</EuiButton>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
icon={<EuiIcon size="xxl" type="discoverApp" />}
|
||||
titleSize="s"
|
||||
title={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.exploreYourDataLabel',
|
||||
{ defaultMessage: 'Explore your data' }
|
||||
)}
|
||||
description={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.exploreYourDataDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'See your connector documents or make a data view to explore them',
|
||||
}
|
||||
)}
|
||||
footer={
|
||||
showNext ? (
|
||||
<EuiButton
|
||||
data-test-subj="enterpriseSearchFinishUpStepViewInDiscoverButton"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.enterpriseSearch.finishUpStep.euiButton.viewInDiscoverLabel',
|
||||
{ defaultMessage: 'View in Discover' }
|
||||
)}
|
||||
onClick={() => {
|
||||
discover.locator?.navigate({
|
||||
dataViewSpec: {
|
||||
title: connector?.name,
|
||||
},
|
||||
indexPattern: connector?.index_name,
|
||||
title: connector?.name,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.viewInDiscoverButtonLabel',
|
||||
{ defaultMessage: 'View in Discover' }
|
||||
)}
|
||||
</EuiButton>
|
||||
) : (
|
||||
<EuiButton
|
||||
data-test-subj="enterpriseSearchFinishUpStepButton"
|
||||
color="warning"
|
||||
iconSide="left"
|
||||
iconType="refresh"
|
||||
isLoading={isSyncing}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.euiButton.firstSyncDataLabel',
|
||||
{ defaultMessage: 'First sync data' }
|
||||
)}
|
||||
onClick={() => {
|
||||
startSync(connector);
|
||||
setShowNext(true);
|
||||
}}
|
||||
>
|
||||
{isSyncing ? 'Syncing data' : 'First sync data'}
|
||||
</EuiButton>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
icon={<EuiIcon size="xxl" type={connectorLogo} />}
|
||||
titleSize="s"
|
||||
title={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.manageYourConnectorLabel',
|
||||
{ defaultMessage: 'Manage your connector' }
|
||||
)}
|
||||
description={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.manageYourConnectorDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Now you can manage your connector, schedule a sync and much more',
|
||||
}
|
||||
)}
|
||||
footer={
|
||||
<EuiFlexGroup responsive={false} gutterSize="xs" justifyContent="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="enterpriseSearchFinishUpStepManageConnectorButton"
|
||||
size="m"
|
||||
fill
|
||||
onClick={() => {
|
||||
if (connector) {
|
||||
KibanaLogic.values.navigateToUrl(
|
||||
generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
|
||||
connectorId: connector.id,
|
||||
tabId: ConnectorDetailTabId.CONFIGURATION,
|
||||
})
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.manageConnectorButtonLabel',
|
||||
{ defaultMessage: 'Manage connector' }
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiHorizontalRule margin="xl" />
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.h3.queryYourDataLabel',
|
||||
{
|
||||
defaultMessage: 'Query your data',
|
||||
}
|
||||
)}
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
layout="horizontal"
|
||||
icon={
|
||||
<EuiIcon
|
||||
css={() => css`
|
||||
margin-top: ${euiTheme.size.xs};
|
||||
`}
|
||||
size="m"
|
||||
type="visVega"
|
||||
/>
|
||||
}
|
||||
title={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.queryWithLanguageClientsLabel',
|
||||
{ defaultMessage: 'Query with language clients' }
|
||||
)}
|
||||
titleSize="xs"
|
||||
description={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.queryWithLanguageClientsLDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Use your favorite language client to query your data in your app',
|
||||
}
|
||||
)}
|
||||
onClick={() => {}}
|
||||
display="subdued"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
layout="horizontal"
|
||||
icon={
|
||||
<EuiIcon
|
||||
css={() => css`
|
||||
margin-top: ${euiTheme.size.xs};
|
||||
`}
|
||||
size="m"
|
||||
type="console"
|
||||
/>
|
||||
}
|
||||
title={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.devToolsLabel',
|
||||
{ defaultMessage: 'Dev tools' }
|
||||
)}
|
||||
titleSize="xs"
|
||||
description={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.finishUpStep.euiCard.devToolsDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Tools for interacting with your data, such as console, profiler, Grok debugger and more',
|
||||
}
|
||||
)}
|
||||
onClick={() => {}}
|
||||
display="subdued"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 { CreateConnector } from './create_connector';
|
|
@ -0,0 +1,340 @@
|
|||
/*
|
||||
* 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, { ChangeEvent } from 'react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiFieldText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiPanel,
|
||||
EuiRadio,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
useGeneratedHtmlId,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import * as Constants from '../../../../shared/constants';
|
||||
import { GeneratedConfigFields } from '../../connector_detail/components/generated_config_fields';
|
||||
|
||||
import { ConnectorViewLogic } from '../../connector_detail/connector_view_logic';
|
||||
import { NewConnectorLogic } from '../../new_index/method_connector/new_connector_logic';
|
||||
|
||||
import { ChooseConnectorSelectable } from './components/choose_connector_selectable';
|
||||
import { ConnectorDescriptionPopover } from './components/connector_description_popover';
|
||||
import { ManualConfiguration } from './components/manual_configuration';
|
||||
import { SelfManagePreference } from './create_connector';
|
||||
|
||||
interface StartStepProps {
|
||||
error?: string | React.ReactNode;
|
||||
onSelfManagePreferenceChange(preference: SelfManagePreference): void;
|
||||
selfManagePreference: SelfManagePreference;
|
||||
setCurrentStep: Function;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const StartStep: React.FC<StartStepProps> = ({
|
||||
title,
|
||||
selfManagePreference,
|
||||
setCurrentStep,
|
||||
onSelfManagePreferenceChange,
|
||||
error,
|
||||
}) => {
|
||||
const elasticManagedRadioButtonId = useGeneratedHtmlId({ prefix: 'elasticManagedRadioButton' });
|
||||
const selfManagedRadioButtonId = useGeneratedHtmlId({ prefix: 'selfManagedRadioButton' });
|
||||
|
||||
const {
|
||||
rawName,
|
||||
canConfigureConnector,
|
||||
selectedConnector,
|
||||
generatedConfigData,
|
||||
isGenerateLoading,
|
||||
isCreateLoading,
|
||||
} = useValues(NewConnectorLogic);
|
||||
const { setRawName, createConnector, generateConnectorName } = useActions(NewConnectorLogic);
|
||||
const { connector } = useValues(ConnectorViewLogic);
|
||||
|
||||
const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setRawName(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiForm component="form" id="enterprise-search-create-connector">
|
||||
<EuiFlexGroup gutterSize="m" direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="l">
|
||||
<EuiTitle size="m">
|
||||
<h3>{title}</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.startStep.euiFormRow.connectorLabel',
|
||||
{ defaultMessage: 'Connector' }
|
||||
)}
|
||||
>
|
||||
<ChooseConnectorSelectable selfManaged={selfManagePreference} />
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
isInvalid={!!error}
|
||||
label={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.startStep.euiFormRow.connectorNameLabel',
|
||||
{ defaultMessage: 'Connector name' }
|
||||
)}
|
||||
>
|
||||
<EuiFieldText
|
||||
data-test-subj="enterpriseSearchStartStepFieldText"
|
||||
fullWidth
|
||||
name="first"
|
||||
value={rawName}
|
||||
onChange={handleNameChange}
|
||||
onBlur={() => {
|
||||
if (selectedConnector) {
|
||||
generateConnectorName({
|
||||
connectorName: rawName,
|
||||
connectorType: selectedConnector.serviceType,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.startStep.euiFormRow.descriptionLabel',
|
||||
{ defaultMessage: 'Description' }
|
||||
)}
|
||||
>
|
||||
<EuiFieldText
|
||||
data-test-subj="enterpriseSearchStartStepFieldText"
|
||||
fullWidth
|
||||
name="first"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
{/* Set up */}
|
||||
<EuiFlexItem>
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="l">
|
||||
<EuiTitle size="s">
|
||||
<h4>
|
||||
{i18n.translate('xpack.enterpriseSearch.createConnector.startStep.h4.setUpLabel', {
|
||||
defaultMessage: 'Set up',
|
||||
})}
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.startStep.p.whereDoYouWantLabel',
|
||||
{
|
||||
defaultMessage:
|
||||
'Where do you want to store the connector and how do you want to manage it?',
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiRadio
|
||||
id={elasticManagedRadioButtonId}
|
||||
label={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.startStep.euiRadio.elasticManagedLabel',
|
||||
{ defaultMessage: 'Elastic managed' }
|
||||
)}
|
||||
checked={selfManagePreference === 'native'}
|
||||
disabled={selectedConnector?.isNative === false}
|
||||
onChange={() => onSelfManagePreferenceChange('native')}
|
||||
name="setUp"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ConnectorDescriptionPopover
|
||||
isDisabled={selectedConnector?.isNative === false}
|
||||
isNative
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiRadio
|
||||
id={selfManagedRadioButtonId}
|
||||
label={i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.startStep.euiRadio.selfManagedLabel',
|
||||
{ defaultMessage: 'Self managed' }
|
||||
)}
|
||||
checked={selfManagePreference === 'selfManaged'}
|
||||
onChange={() => onSelfManagePreferenceChange('selfManaged')}
|
||||
name="setUp"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ConnectorDescriptionPopover isDisabled={false} isNative={false} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
{selfManagePreference === 'selfManaged' ? (
|
||||
<EuiFlexItem>
|
||||
<EuiPanel
|
||||
hasShadow={false}
|
||||
hasBorder
|
||||
paddingSize="l"
|
||||
color={selectedConnector?.name ? 'plain' : 'subdued'}
|
||||
>
|
||||
<EuiText color={selectedConnector?.name ? 'default' : 'subdued'}>
|
||||
<h3>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.startStep.h4.deploymentLabel',
|
||||
{
|
||||
defaultMessage: 'Deployment',
|
||||
}
|
||||
)}
|
||||
</h3>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText color={selectedConnector?.name ? 'default' : 'subdued'} size="s">
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.startStep.p.youWillStartTheLabel',
|
||||
{
|
||||
defaultMessage:
|
||||
'You will start the process of creating a new index, API key, and a Web Crawler Connector ID manually. Optionally you can bring your own configuration as well.',
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButton
|
||||
data-test-subj="enterpriseSearchStartStepNextButton"
|
||||
onClick={() => {
|
||||
if (selectedConnector && selectedConnector.name) {
|
||||
createConnector({
|
||||
isSelfManaged: true,
|
||||
});
|
||||
setCurrentStep('deployment');
|
||||
}
|
||||
}}
|
||||
fill
|
||||
disabled={!canConfigureConnector}
|
||||
isLoading={isCreateLoading || isGenerateLoading}
|
||||
>
|
||||
{Constants.NEXT_BUTTON_LABEL}
|
||||
</EuiButton>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
) : (
|
||||
<EuiFlexItem>
|
||||
<EuiPanel
|
||||
color={selectedConnector?.name ? 'plain' : 'subdued'}
|
||||
hasShadow={false}
|
||||
hasBorder
|
||||
paddingSize="l"
|
||||
>
|
||||
<EuiText color={selectedConnector?.name ? 'default' : 'subdued'}>
|
||||
<h3>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.startStep.h4.configureIndexAndAPILabel',
|
||||
{
|
||||
defaultMessage: 'Configure index and API key',
|
||||
}
|
||||
)}
|
||||
</h3>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText color={selectedConnector?.name ? 'default' : 'subdued'} size="s">
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.createConnector.startStep.p.thisProcessWillCreateLabel',
|
||||
{
|
||||
defaultMessage:
|
||||
'This process will create a new index, API key, and a Connector ID. Optionally you can bring your own configuration as well.',
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
{generatedConfigData && connector ? (
|
||||
<>
|
||||
<GeneratedConfigFields
|
||||
apiKey={{
|
||||
api_key: generatedConfigData.apiKey.api_key,
|
||||
encoded: generatedConfigData.apiKey.encoded,
|
||||
id: generatedConfigData.apiKey.id,
|
||||
name: generatedConfigData.apiKey.name,
|
||||
}}
|
||||
connector={connector}
|
||||
isGenerateLoading={false}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButton
|
||||
data-test-subj="enterpriseSearchStartStepGenerateConfigurationButton"
|
||||
fill
|
||||
onClick={() => setCurrentStep('configure')}
|
||||
>
|
||||
{Constants.NEXT_BUTTON_LABEL}
|
||||
</EuiButton>
|
||||
</>
|
||||
) : (
|
||||
<EuiFlexGroup gutterSize="xs">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="entSearchContent-connector-configuration-generateConfigButton"
|
||||
data-telemetry-id="entSearchContent-connector-configuration-generateConfigButton"
|
||||
disabled={!canConfigureConnector}
|
||||
fill
|
||||
iconType="sparkles"
|
||||
isLoading={isGenerateLoading || isCreateLoading}
|
||||
onClick={() => {
|
||||
createConnector({
|
||||
isSelfManaged: false,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.connector_detail.configurationConnector.steps.generateApiKey.button.label',
|
||||
{
|
||||
defaultMessage: 'Generate configuration',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ManualConfiguration
|
||||
isDisabled={isGenerateLoading || isCreateLoading || !canConfigureConnector}
|
||||
selfManagePreference={selfManagePreference}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiForm>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { EuiStepStatus } from '@elastic/eui';
|
||||
|
||||
type Steps = 'start' | 'configure' | 'deployment' | 'finish';
|
||||
|
||||
export const generateStepState = (currentStep: Steps): { [key in Steps]: EuiStepStatus } => {
|
||||
return {
|
||||
configure:
|
||||
currentStep === 'start' || currentStep === 'deployment'
|
||||
? 'incomplete'
|
||||
: currentStep === 'configure'
|
||||
? 'current'
|
||||
: 'complete',
|
||||
deployment:
|
||||
currentStep === 'deployment'
|
||||
? 'current'
|
||||
: currentStep === 'finish' || currentStep === 'configure'
|
||||
? 'complete'
|
||||
: 'incomplete',
|
||||
finish: currentStep === 'finish' ? 'current' : 'incomplete',
|
||||
start: currentStep === 'start' ? 'current' : 'complete',
|
||||
};
|
||||
};
|
|
@ -7,65 +7,214 @@
|
|||
|
||||
import { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import { Connector } from '@kbn/search-connectors';
|
||||
import { ConnectorDefinition } from '@kbn/search-connectors-plugin/public';
|
||||
|
||||
import { Status } from '../../../../../../common/types/api';
|
||||
import { Actions } from '../../../../shared/api_logic/create_api_logic';
|
||||
import { generateEncodedPath } from '../../../../shared/encode_path_params';
|
||||
import { KibanaLogic } from '../../../../shared/kibana';
|
||||
import {
|
||||
AddConnectorApiLogic,
|
||||
AddConnectorApiLogicActions,
|
||||
AddConnectorApiLogicArgs,
|
||||
AddConnectorApiLogicResponse,
|
||||
} from '../../../api/connector/add_connector_api_logic';
|
||||
|
||||
import {
|
||||
IndexExistsApiLogic,
|
||||
IndexExistsApiParams,
|
||||
IndexExistsApiResponse,
|
||||
} from '../../../api/index/index_exists_api_logic';
|
||||
GenerateConfigApiActions,
|
||||
GenerateConfigApiLogic,
|
||||
} from '../../../api/connector/generate_connector_config_api_logic';
|
||||
import {
|
||||
GenerateConnectorNamesApiLogic,
|
||||
GenerateConnectorNamesApiLogicActions,
|
||||
GenerateConnectorNamesApiResponse,
|
||||
} from '../../../api/connector/generate_connector_names_api_logic';
|
||||
import { APIKeyResponse } from '../../../api/generate_api_key/generate_api_key_logic';
|
||||
|
||||
import { isValidIndexName } from '../../../utils/validate_index_name';
|
||||
|
||||
import { UNIVERSAL_LANGUAGE_VALUE } from '../constants';
|
||||
import { LanguageForOptimization } from '../types';
|
||||
import { getLanguageForOptimization } from '../utils';
|
||||
import { CONNECTOR_DETAIL_TAB_PATH } from '../../../routes';
|
||||
import {
|
||||
ConnectorViewActions,
|
||||
ConnectorViewLogic,
|
||||
} from '../../connector_detail/connector_view_logic';
|
||||
import { ConnectorCreationSteps } from '../../connectors/create_connector/create_connector';
|
||||
import { SearchIndexTabId } from '../../search_index/search_index';
|
||||
|
||||
export interface NewConnectorValues {
|
||||
data: IndexExistsApiResponse;
|
||||
fullIndexName: string;
|
||||
fullIndexNameExists: boolean;
|
||||
fullIndexNameIsValid: boolean;
|
||||
language: LanguageForOptimization;
|
||||
languageSelectValue: string;
|
||||
canConfigureConnector: boolean;
|
||||
connectorId: string;
|
||||
connectorName: string;
|
||||
createConnectorApiStatus: Status;
|
||||
currentStep: ConnectorCreationSteps;
|
||||
generateConfigurationStatus: Status;
|
||||
generatedConfigData:
|
||||
| {
|
||||
apiKey: APIKeyResponse['apiKey'];
|
||||
connectorId: Connector['id'];
|
||||
indexName: string;
|
||||
}
|
||||
| undefined;
|
||||
generatedNameData: GenerateConnectorNamesApiResponse | undefined;
|
||||
isCreateLoading: boolean;
|
||||
isGenerateLoading: boolean;
|
||||
rawName: string;
|
||||
selectedConnector: ConnectorDefinition | null;
|
||||
shouldGenerateConfigAfterCreate: boolean;
|
||||
}
|
||||
|
||||
type NewConnectorActions = Pick<
|
||||
Actions<IndexExistsApiParams, IndexExistsApiResponse>,
|
||||
'makeRequest'
|
||||
> & {
|
||||
type NewConnectorActions = {
|
||||
generateConnectorName: GenerateConnectorNamesApiLogicActions['makeRequest'];
|
||||
} & {
|
||||
configurationGenerated: GenerateConfigApiActions['apiSuccess'];
|
||||
generateConfiguration: GenerateConfigApiActions['makeRequest'];
|
||||
} & {
|
||||
connectorCreated: Actions<AddConnectorApiLogicArgs, AddConnectorApiLogicResponse>['apiSuccess'];
|
||||
setLanguageSelectValue(language: string): { language: string };
|
||||
createConnector: ({
|
||||
isSelfManaged,
|
||||
shouldGenerateAfterCreate,
|
||||
shouldNavigateToConnectorAfterCreate,
|
||||
}: {
|
||||
isSelfManaged: boolean;
|
||||
shouldGenerateAfterCreate?: boolean;
|
||||
shouldNavigateToConnectorAfterCreate?: boolean;
|
||||
}) => {
|
||||
isSelfManaged: boolean;
|
||||
shouldGenerateAfterCreate?: boolean;
|
||||
shouldNavigateToConnectorAfterCreate?: boolean;
|
||||
};
|
||||
createConnectorApi: AddConnectorApiLogicActions['makeRequest'];
|
||||
fetchConnector: ConnectorViewActions['fetchConnector'];
|
||||
setCurrentStep(step: ConnectorCreationSteps): { step: ConnectorCreationSteps };
|
||||
setRawName(rawName: string): { rawName: string };
|
||||
setSelectedConnector(connector: ConnectorDefinition | null): {
|
||||
connector: ConnectorDefinition | null;
|
||||
};
|
||||
};
|
||||
|
||||
export const NewConnectorLogic = kea<MakeLogicType<NewConnectorValues, NewConnectorActions>>({
|
||||
actions: {
|
||||
setLanguageSelectValue: (language) => ({ language }),
|
||||
createConnector: ({
|
||||
isSelfManaged,
|
||||
shouldGenerateAfterCreate,
|
||||
shouldNavigateToConnectorAfterCreate,
|
||||
}) => ({
|
||||
isSelfManaged,
|
||||
shouldGenerateAfterCreate,
|
||||
shouldNavigateToConnectorAfterCreate,
|
||||
}),
|
||||
setCurrentStep: (step) => ({ step }),
|
||||
setRawName: (rawName) => ({ rawName }),
|
||||
setSelectedConnector: (connector) => ({ connector }),
|
||||
},
|
||||
connect: {
|
||||
actions: [
|
||||
GenerateConnectorNamesApiLogic,
|
||||
['makeRequest as generateConnectorName', 'apiSuccess as connectorNameGenerated'],
|
||||
AddConnectorApiLogic,
|
||||
['apiSuccess as connectorCreated'],
|
||||
IndexExistsApiLogic,
|
||||
['makeRequest'],
|
||||
['makeRequest as createConnectorApi', 'apiSuccess as connectorCreated'],
|
||||
GenerateConfigApiLogic,
|
||||
['makeRequest as generateConfiguration', 'apiSuccess as configurationGenerated'],
|
||||
ConnectorViewLogic,
|
||||
['fetchConnector'],
|
||||
],
|
||||
values: [
|
||||
GenerateConnectorNamesApiLogic,
|
||||
['data as generatedNameData'],
|
||||
GenerateConfigApiLogic,
|
||||
['data as generatedConfigData', 'status as generateConfigurationStatus'],
|
||||
AddConnectorApiLogic,
|
||||
['status as createConnectorApiStatus'],
|
||||
],
|
||||
values: [IndexExistsApiLogic, ['data']],
|
||||
},
|
||||
path: ['enterprise_search', 'content', 'new_search_index'],
|
||||
listeners: ({ actions, values }) => ({
|
||||
connectorCreated: ({ id, uiFlags }) => {
|
||||
if (uiFlags?.shouldNavigateToConnectorAfterCreate) {
|
||||
KibanaLogic.values.navigateToUrl(
|
||||
generateEncodedPath(CONNECTOR_DETAIL_TAB_PATH, {
|
||||
connectorId: id,
|
||||
tabId: SearchIndexTabId.CONFIGURATION,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
actions.fetchConnector({ connectorId: id });
|
||||
if (!uiFlags || uiFlags.shouldGenerateAfterCreate) {
|
||||
actions.generateConfiguration({ connectorId: id });
|
||||
}
|
||||
}
|
||||
},
|
||||
connectorNameGenerated: ({ connectorName }) => {
|
||||
if (!values.rawName) {
|
||||
actions.setRawName(connectorName);
|
||||
}
|
||||
},
|
||||
createConnector: ({
|
||||
isSelfManaged,
|
||||
shouldGenerateAfterCreate = true,
|
||||
shouldNavigateToConnectorAfterCreate = false,
|
||||
}) => {
|
||||
if (
|
||||
!values.rawName &&
|
||||
values.selectedConnector &&
|
||||
values.connectorName &&
|
||||
values.generatedNameData
|
||||
) {
|
||||
// name is generated, use everything generated
|
||||
actions.createConnectorApi({
|
||||
deleteExistingConnector: false,
|
||||
indexName: values.connectorName,
|
||||
isNative: !values.selectedConnector.isNative ? false : !isSelfManaged,
|
||||
language: null,
|
||||
name: values.generatedNameData.connectorName,
|
||||
serviceType: values.selectedConnector.serviceType,
|
||||
uiFlags: {
|
||||
shouldGenerateAfterCreate,
|
||||
shouldNavigateToConnectorAfterCreate,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
if (values.generatedNameData && values.selectedConnector) {
|
||||
actions.createConnectorApi({
|
||||
deleteExistingConnector: false,
|
||||
indexName: values.generatedNameData.indexName,
|
||||
isNative: !values.selectedConnector.isNative ? false : !isSelfManaged,
|
||||
language: null,
|
||||
name: values.connectorName,
|
||||
serviceType: values.selectedConnector?.serviceType,
|
||||
uiFlags: {
|
||||
shouldGenerateAfterCreate,
|
||||
shouldNavigateToConnectorAfterCreate,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
setSelectedConnector: ({ connector }) => {
|
||||
if (connector) {
|
||||
actions.generateConnectorName({
|
||||
connectorName: values.rawName,
|
||||
connectorType: connector.serviceType,
|
||||
});
|
||||
}
|
||||
},
|
||||
}),
|
||||
path: ['enterprise_search', 'content', 'new_search_connector'],
|
||||
reducers: {
|
||||
languageSelectValue: [
|
||||
UNIVERSAL_LANGUAGE_VALUE,
|
||||
connectorId: [
|
||||
'',
|
||||
{
|
||||
// @ts-expect-error upgrade typescript v5.1.6
|
||||
setLanguageSelectValue: (_, { language }) => language ?? null,
|
||||
connectorCreated: (
|
||||
_: NewConnectorValues['connectorId'],
|
||||
{ id }: { id: NewConnectorValues['connectorId'] }
|
||||
) => id,
|
||||
},
|
||||
],
|
||||
currentStep: [
|
||||
'start',
|
||||
{
|
||||
setCurrentStep: (
|
||||
_: NewConnectorValues['currentStep'],
|
||||
{ step }: { step: NewConnectorValues['currentStep'] }
|
||||
) => step,
|
||||
},
|
||||
],
|
||||
rawName: [
|
||||
|
@ -75,21 +224,34 @@ export const NewConnectorLogic = kea<MakeLogicType<NewConnectorValues, NewConnec
|
|||
setRawName: (_, { rawName }) => rawName,
|
||||
},
|
||||
],
|
||||
selectedConnector: [
|
||||
null,
|
||||
{
|
||||
setSelectedConnector: (
|
||||
_: NewConnectorValues['selectedConnector'],
|
||||
{ connector }: { connector: NewConnectorValues['selectedConnector'] }
|
||||
) => connector,
|
||||
},
|
||||
],
|
||||
},
|
||||
selectors: ({ selectors }) => ({
|
||||
fullIndexName: [() => [selectors.rawName], (name: string) => name],
|
||||
fullIndexNameExists: [
|
||||
() => [selectors.data, selectors.fullIndexName],
|
||||
(data: IndexExistsApiResponse | undefined, fullIndexName: string) =>
|
||||
data?.exists === true && data.indexName === fullIndexName,
|
||||
canConfigureConnector: [
|
||||
() => [selectors.connectorName, selectors.selectedConnector],
|
||||
(connectorName: string, selectedConnector: NewConnectorValues['selectedConnector']) =>
|
||||
(connectorName && selectedConnector?.name) ?? false,
|
||||
],
|
||||
fullIndexNameIsValid: [
|
||||
() => [selectors.fullIndexName],
|
||||
(fullIndexName) => isValidIndexName(fullIndexName),
|
||||
connectorName: [
|
||||
() => [selectors.rawName, selectors.generatedNameData],
|
||||
(name: string, generatedName: NewConnectorValues['generatedNameData']) =>
|
||||
name ? name : generatedName?.connectorName ?? '',
|
||||
],
|
||||
language: [
|
||||
() => [selectors.languageSelectValue],
|
||||
(languageSelectValue) => getLanguageForOptimization(languageSelectValue),
|
||||
isCreateLoading: [
|
||||
() => [selectors.createConnectorApiStatus],
|
||||
(status) => status === Status.LOADING,
|
||||
],
|
||||
isGenerateLoading: [
|
||||
() => [selectors.generateConfigurationStatus],
|
||||
(status) => status === Status.LOADING,
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -54,44 +54,17 @@ export const NewConnectorTemplate: React.FC<Props> = ({
|
|||
type,
|
||||
isBeta,
|
||||
}) => {
|
||||
const { fullIndexName, fullIndexNameExists, fullIndexNameIsValid, rawName } =
|
||||
useValues(NewConnectorLogic);
|
||||
const { connectorName, rawName } = useValues(NewConnectorLogic);
|
||||
const { setRawName } = useActions(NewConnectorLogic);
|
||||
|
||||
const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setRawName(e.target.value);
|
||||
if (onNameChange) {
|
||||
onNameChange(fullIndexName);
|
||||
onNameChange(connectorName);
|
||||
}
|
||||
};
|
||||
|
||||
const formInvalid = !!error || fullIndexNameExists || !fullIndexNameIsValid;
|
||||
|
||||
const formError = () => {
|
||||
if (fullIndexNameExists) {
|
||||
return i18n.translate(
|
||||
'xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.alreadyExists.error',
|
||||
{
|
||||
defaultMessage: 'A connector with the name {connectorName} already exists',
|
||||
values: {
|
||||
connectorName: fullIndexName,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
if (!fullIndexNameIsValid) {
|
||||
return i18n.translate(
|
||||
'xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.isInvalid.error',
|
||||
{
|
||||
defaultMessage: '{connectorName} is an invalid connector name',
|
||||
values: {
|
||||
connectorName: fullIndexName,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
return error;
|
||||
};
|
||||
const formInvalid = !!error;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -100,7 +73,7 @@ export const NewConnectorTemplate: React.FC<Props> = ({
|
|||
id="enterprise-search-create-connector"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
onSubmit(fullIndexName);
|
||||
onSubmit(connectorName);
|
||||
}}
|
||||
>
|
||||
<EuiFlexGroup direction="column">
|
||||
|
@ -131,10 +104,10 @@ export const NewConnectorTemplate: React.FC<Props> = ({
|
|||
}
|
||||
)}
|
||||
isInvalid={formInvalid}
|
||||
error={formError()}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFieldText
|
||||
data-test-subj="enterpriseSearchNewConnectorTemplateFieldText"
|
||||
data-telemetry-id={`entSearchContent-${type}-newConnector-editName`}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.nameInputPlaceholder',
|
||||
|
@ -167,7 +140,11 @@ export const NewConnectorTemplate: React.FC<Props> = ({
|
|||
<EuiFlexGroup direction="column" gutterSize="xs">
|
||||
{type === INGESTION_METHOD_IDS.CONNECTOR && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink target="_blank" href={docLinks.connectors}>
|
||||
<EuiLink
|
||||
data-test-subj="enterpriseSearchNewConnectorTemplateLearnMoreAboutConnectorsLink"
|
||||
target="_blank"
|
||||
href={docLinks.connectors}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.learnMoreConnectors.linkText',
|
||||
{
|
||||
|
@ -182,6 +159,7 @@ export const NewConnectorTemplate: React.FC<Props> = ({
|
|||
<EuiFlexGroup direction="row" alignItems="center" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
data-test-subj="enterpriseSearchNewConnectorTemplateButton"
|
||||
data-telemetry-id={`entSearchContent-${type}-newConnector-goBack`}
|
||||
isDisabled={buttonLoading}
|
||||
onClick={() => history.back()}
|
||||
|
|
|
@ -21,6 +21,7 @@ export const NEW_ES_INDEX_PATH = `${NEW_INDEX_PATH}/elasticsearch`;
|
|||
export const NEW_DIRECT_UPLOAD_PATH = `${NEW_INDEX_PATH}/upload`;
|
||||
export const NEW_INDEX_SELECT_CONNECTOR_PATH = `${CONNECTORS_PATH}/select_connector`;
|
||||
export const NEW_CONNECTOR_PATH = `${CONNECTORS_PATH}/new_connector`;
|
||||
export const NEW_CONNECTOR_FLOW_PATH = `${CONNECTORS_PATH}/new_connector_flow`;
|
||||
export const NEW_CRAWLER_PATH = `${CRAWLERS_PATH}/new_crawler`;
|
||||
export const NEW_INDEX_SELECT_CONNECTOR_NATIVE_PATH = `${CONNECTORS_PATH}/select_connector?filter=native`;
|
||||
export const NEW_INDEX_SELECT_CONNECTOR_CLIENTS_PATH = `${CONNECTORS_PATH}/select_connector?filter=connector_clients`;
|
||||
|
|
|
@ -49,6 +49,10 @@ export const BACK_BUTTON_LABEL = i18n.translate('xpack.enterpriseSearch.actions.
|
|||
defaultMessage: 'Back',
|
||||
});
|
||||
|
||||
export const NEXT_BUTTON_LABEL = i18n.translate('xpack.enterpriseSearch.actions.nextButtonLabel', {
|
||||
defaultMessage: 'Next',
|
||||
});
|
||||
|
||||
export const CLOSE_BUTTON_LABEL = i18n.translate(
|
||||
'xpack.enterpriseSearch.actions.closeButtonLabel',
|
||||
{ defaultMessage: 'Close' }
|
||||
|
|
|
@ -16,24 +16,51 @@ import { indexOrAliasExists } from '../indices/exists_index';
|
|||
|
||||
export const generateConnectorName = async (
|
||||
client: IScopedClusterClient,
|
||||
connectorType: string
|
||||
connectorType: string,
|
||||
userConnectorName?: string
|
||||
): Promise<{ apiKeyName: string; connectorName: string; indexName: string }> => {
|
||||
const prefix = toAlphanumeric(connectorType);
|
||||
if (!prefix || prefix.length === 0) {
|
||||
throw new Error('Connector type is required');
|
||||
throw new Error('Connector type or connectorName is required');
|
||||
}
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const connectorName = `${prefix}-${uuidv4().split('-')[1]}`;
|
||||
const indexName = `connector-${connectorName}`;
|
||||
|
||||
const result = await indexOrAliasExists(client, indexName);
|
||||
if (!result) {
|
||||
if (userConnectorName) {
|
||||
let indexName = `connector-${userConnectorName}`;
|
||||
const resultSameName = await indexOrAliasExists(client, indexName);
|
||||
// index with same name doesn't exist
|
||||
if (!resultSameName) {
|
||||
return {
|
||||
apiKeyName: indexName,
|
||||
connectorName,
|
||||
apiKeyName: userConnectorName,
|
||||
connectorName: userConnectorName,
|
||||
indexName,
|
||||
};
|
||||
}
|
||||
// if the index name already exists, we will generate until it doesn't for 20 times
|
||||
for (let i = 0; i < 20; i++) {
|
||||
indexName = `connector-${userConnectorName}-${uuidv4().split('-')[1].slice(0, 4)}`;
|
||||
|
||||
const result = await indexOrAliasExists(client, indexName);
|
||||
if (!result) {
|
||||
return {
|
||||
apiKeyName: indexName,
|
||||
connectorName: userConnectorName,
|
||||
indexName,
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const connectorName = `${prefix}-${uuidv4().split('-')[1].slice(0, 4)}`;
|
||||
const indexName = `connector-${connectorName}`;
|
||||
|
||||
const result = await indexOrAliasExists(client, indexName);
|
||||
if (!result) {
|
||||
return {
|
||||
apiKeyName: indexName,
|
||||
connectorName,
|
||||
indexName,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error(ErrorCode.GENERATE_INDEX_NAME_ERROR);
|
||||
};
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { ElasticsearchErrorDetails } from '@kbn/es-errors';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -841,15 +840,20 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) {
|
|||
path: '/internal/enterprise_search/connectors/generate_connector_name',
|
||||
validate: {
|
||||
body: schema.object({
|
||||
connectorName: schema.maybe(schema.string()),
|
||||
connectorType: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
elasticsearchErrorHandler(log, async (context, request, response) => {
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
const { connectorType } = request.body;
|
||||
const { connectorType, connectorName } = request.body;
|
||||
try {
|
||||
const generatedNames = await generateConnectorName(client, connectorType ?? 'custom');
|
||||
const generatedNames = await generateConnectorName(
|
||||
client,
|
||||
connectorType ?? 'custom',
|
||||
connectorName
|
||||
);
|
||||
return response.ok({
|
||||
body: generatedNames,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
|
|
|
@ -16858,10 +16858,8 @@
|
|||
"xpack.enterpriseSearch.content.new_index.genericTitle": "Nouvel index de recherche",
|
||||
"xpack.enterpriseSearch.content.new_index.successToast.title": "L’index a bien été créé",
|
||||
"xpack.enterpriseSearch.content.new_web_crawler.breadcrumbs": "Nouveau robot d'indexation",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.alreadyExists.error": "Un connecteur nommé {connectorName} existe déjà",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.createIndex.buttonText": "Créer un connecteur",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.formTitle": "Créer un connecteur",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.isInvalid.error": "{connectorName} est un nom de connecteur non valide",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.learnMoreConnectors.linkText": "En savoir plus sur les connecteurs",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.nameInputHelpText.lineTwo": "Les noms doivent être en minuscules et ne peuvent pas contenir d'espaces ni de caractères spéciaux.",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.nameInputLabel": "Nom du connecteur",
|
||||
|
|
|
@ -16604,10 +16604,8 @@
|
|||
"xpack.enterpriseSearch.content.new_index.genericTitle": "新しい検索インデックス",
|
||||
"xpack.enterpriseSearch.content.new_index.successToast.title": "インデックスが正常に作成されました",
|
||||
"xpack.enterpriseSearch.content.new_web_crawler.breadcrumbs": "新しいWebクローラー",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.alreadyExists.error": "名前\"{connectorName}\"のコネクターはすでに存在しています",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.createIndex.buttonText": "コネクターを作成",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.formTitle": "コネクターを作成する",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.isInvalid.error": "{connectorName}は無効なコネクター名です",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.learnMoreConnectors.linkText": "コネクターの詳細",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.nameInputHelpText.lineTwo": "名前は小文字で入力してください。スペースや特殊文字は使用できません。",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.nameInputLabel": "コネクター名",
|
||||
|
|
|
@ -16633,10 +16633,8 @@
|
|||
"xpack.enterpriseSearch.content.new_index.genericTitle": "新搜索索引",
|
||||
"xpack.enterpriseSearch.content.new_index.successToast.title": "已成功创建索引",
|
||||
"xpack.enterpriseSearch.content.new_web_crawler.breadcrumbs": "新网络爬虫",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.alreadyExists.error": "名为 {connectorName} 的连接器已存在",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.createIndex.buttonText": "创建连接器",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.formTitle": "创建连接器",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.isInvalid.error": "{connectorName} 为无效的连接器名称",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.learnMoreConnectors.linkText": "详细了解连接器",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.nameInputHelpText.lineTwo": "名称应为小写,并且不能包含空格或特殊字符。",
|
||||
"xpack.enterpriseSearch.content.newConnector.newConnectorTemplate.nameInputLabel": "连接器名称",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue