[8.x] [ES3][Search] Create Index Page (#199402) (#199703)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[ES3][Search] Create Index Page
(#199402)](https://github.com/elastic/kibana/pull/199402)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Rodney
Norris","email":"rodney.norris@elastic.co"},"sourceCommit":{"committedDate":"2024-11-11T19:24:32Z","message":"[ES3][Search]
Create Index Page (#199402)\n\n## Summary\r\n\r\nThis PR introduces a
Create Index page for the serverless search\r\nsolution. This page is
almost identical to the new Global Empty State,\r\nbut is navigated to
via the Create Index button in Index Management. The\r\nindex details
redirect logic is also slightly different on the Create\r\nIndex page,
it will only redirect when the \"code\" view is open and a new\r\nindex
is created. instead of redirecting from both UI and Code view
like\r\nthe Global Empty State page does.\r\n\r\nWith the addition of
this page we are also removing the \"Home\" link from\r\nthe serverless
search side nav to reduce confusion when the global empty\r\nstart
redirects to index management when indices exist.\r\n\r\nThere is also
some minor clean-up to ensure both the global empty state\r\nand the new
create index pages have proper document titles
and\r\nbreadcrumbs.\r\n\r\n### Screenshots\r\nUpdates to Global Empty
State:\r\n\r\n![image](https://github.com/user-attachments/assets/bb60734e-543d-4481-b121-d52633d462a8)\r\nCreate
Index Page:\r\n<img width=\"1320\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/0d095eb6-fda3-4783-83ab-20449b5b31f1\">\r\n\r\n###
Checklist\r\n\r\n- [x] 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- [x] [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- [x] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n- [x] Any UI touched in this PR is
usable by keyboard only (learn more\r\nabout [keyboard
accessibility](https://webaim.org/techniques/keyboard/))\r\n- [x] 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\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by:
Elastic Machine
<elasticmachine@users.noreply.github.com>","sha":"e03e59b6d482a05435d86a612d92028f264df893","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Search","backport:prev-minor","v8.17.0"],"number":199402,"url":"https://github.com/elastic/kibana/pull/199402","mergeCommit":{"message":"[ES3][Search]
Create Index Page (#199402)\n\n## Summary\r\n\r\nThis PR introduces a
Create Index page for the serverless search\r\nsolution. This page is
almost identical to the new Global Empty State,\r\nbut is navigated to
via the Create Index button in Index Management. The\r\nindex details
redirect logic is also slightly different on the Create\r\nIndex page,
it will only redirect when the \"code\" view is open and a new\r\nindex
is created. instead of redirecting from both UI and Code view
like\r\nthe Global Empty State page does.\r\n\r\nWith the addition of
this page we are also removing the \"Home\" link from\r\nthe serverless
search side nav to reduce confusion when the global empty\r\nstart
redirects to index management when indices exist.\r\n\r\nThere is also
some minor clean-up to ensure both the global empty state\r\nand the new
create index pages have proper document titles
and\r\nbreadcrumbs.\r\n\r\n### Screenshots\r\nUpdates to Global Empty
State:\r\n\r\n![image](https://github.com/user-attachments/assets/bb60734e-543d-4481-b121-d52633d462a8)\r\nCreate
Index Page:\r\n<img width=\"1320\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/0d095eb6-fda3-4783-83ab-20449b5b31f1\">\r\n\r\n###
Checklist\r\n\r\n- [x] 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- [x] [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- [x] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n- [x] Any UI touched in this PR is
usable by keyboard only (learn more\r\nabout [keyboard
accessibility](https://webaim.org/techniques/keyboard/))\r\n- [x] 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\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by:
Elastic Machine
<elasticmachine@users.noreply.github.com>","sha":"e03e59b6d482a05435d86a612d92028f264df893"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/199402","number":199402,"mergeCommit":{"message":"[ES3][Search]
Create Index Page (#199402)\n\n## Summary\r\n\r\nThis PR introduces a
Create Index page for the serverless search\r\nsolution. This page is
almost identical to the new Global Empty State,\r\nbut is navigated to
via the Create Index button in Index Management. The\r\nindex details
redirect logic is also slightly different on the Create\r\nIndex page,
it will only redirect when the \"code\" view is open and a new\r\nindex
is created. instead of redirecting from both UI and Code view
like\r\nthe Global Empty State page does.\r\n\r\nWith the addition of
this page we are also removing the \"Home\" link from\r\nthe serverless
search side nav to reduce confusion when the global empty\r\nstart
redirects to index management when indices exist.\r\n\r\nThere is also
some minor clean-up to ensure both the global empty state\r\nand the new
create index pages have proper document titles
and\r\nbreadcrumbs.\r\n\r\n### Screenshots\r\nUpdates to Global Empty
State:\r\n\r\n![image](https://github.com/user-attachments/assets/bb60734e-543d-4481-b121-d52633d462a8)\r\nCreate
Index Page:\r\n<img width=\"1320\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/0d095eb6-fda3-4783-83ab-20449b5b31f1\">\r\n\r\n###
Checklist\r\n\r\n- [x] 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- [x] [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- [x] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n- [x] Any UI touched in this PR is
usable by keyboard only (learn more\r\nabout [keyboard
accessibility](https://webaim.org/techniques/keyboard/))\r\n- [x] 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\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by:
Elastic Machine
<elasticmachine@users.noreply.github.com>","sha":"e03e59b6d482a05435d86a612d92028f264df893"}},{"branch":"8.x","label":"v8.17.0","labelRegex":"^v8.17.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Rodney Norris 2024-11-12 14:31:09 -06:00 committed by GitHub
parent f157662827
commit 8c95788e7c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
51 changed files with 1461 additions and 625 deletions

View file

@ -25,3 +25,4 @@ export const SEARCH_ELASTICSEARCH = 'enterpriseSearchElasticsearch';
export const SEARCH_VECTOR_SEARCH = 'enterpriseSearchVectorSearch';
export const SEARCH_SEMANTIC_SEARCH = 'enterpriseSearchSemanticSearch';
export const SEARCH_AI_SEARCH = 'enterpriseSearchAISearch';
export const SEARCH_INDICES_CREATE_INDEX = 'createIndex';

View file

@ -22,6 +22,7 @@ import {
SEARCH_HOMEPAGE,
SEARCH_INDICES_START,
SEARCH_INDICES,
SEARCH_INDICES_CREATE_INDEX,
SEARCH_ELASTICSEARCH,
SEARCH_VECTOR_SEARCH,
SEARCH_SEMANTIC_SEARCH,
@ -55,6 +56,8 @@ export type AppsearchLinkId = 'engines';
export type RelevanceLinkId = 'inferenceEndpoints';
export type SearchIndicesLinkId = typeof SEARCH_INDICES_CREATE_INDEX;
export type DeepLinkId =
| EnterpriseSearchApp
| EnterpriseSearchContentApp
@ -77,4 +80,5 @@ export type DeepLinkId =
| SearchElasticsearch
| SearchVectorSearch
| SearchSemanticSearch
| SearchAISearch;
| SearchAISearch
| `${SearchIndices}:${SearchIndicesLinkId}`;

View file

@ -8,6 +8,7 @@
import React from 'react';
import { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import { CreateIndexButton } from '../../sections/home/index_list/create_index/create_index_button';
import { ExtensionsService } from '../../../services/extensions_service';
@ -16,11 +17,13 @@ export const NoMatch = ({
filter,
resetFilter,
extensionsService,
share,
}: {
loadIndices: () => void;
filter: string;
resetFilter: () => void;
extensionsService: ExtensionsService;
share?: SharePluginStart;
}) => {
if (filter) {
return (
@ -62,7 +65,7 @@ export const NoMatch = ({
if (extensionsService.emptyListContent) {
return extensionsService.emptyListContent.renderContent({
createIndexButton: <CreateIndexButton loadIndices={loadIndices} />,
createIndexButton: <CreateIndexButton loadIndices={loadIndices} share={share} />,
});
}
@ -85,7 +88,7 @@ export const NoMatch = ({
/>
</p>
}
actions={<CreateIndexButton loadIndices={loadIndices} />}
actions={<CreateIndexButton loadIndices={loadIndices} share={share} />}
/>
);
};

View file

@ -7,22 +7,32 @@
import React, { useState } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import { EuiButton } from '@elastic/eui';
import { CreateIndexModal } from './create_index_modal';
export const CreateIndexButton = ({ loadIndices }: { loadIndices: () => void }) => {
export interface CreateIndexButtonProps {
loadIndices: () => void;
share?: SharePluginStart;
}
export const CreateIndexButton = ({ loadIndices, share }: CreateIndexButtonProps) => {
const [createIndexModalOpen, setCreateIndexModalOpen] = useState<boolean>(false);
const createIndexUrl = share?.url.locators.get('SEARCH_CREATE_INDEX')?.useUrl({});
const actionProp = createIndexUrl
? { href: createIndexUrl }
: { onClick: () => setCreateIndexModalOpen(true) };
return (
<>
<EuiButton
fill
iconType="plusInCircleFilled"
onClick={() => setCreateIndexModalOpen(true)}
key="createIndexButton"
data-test-subj="createIndexButton"
data-telemetry-id="idxMgmt-indexList-createIndexButton"
{...actionProp}
>
<FormattedMessage
id="xpack.idxMgmt.indexTable.createIndexButton"

View file

@ -545,9 +545,10 @@ export class IndexTable extends Component {
return (
<AppContextConsumer>
{({ services, config, core }) => {
{({ services, config, core, plugins }) => {
const { extensionsService } = services;
const { application, http } = core;
const { share } = plugins;
const columnConfigs = getColumnConfigs({
showIndexStats: config.enableIndexStats,
showSizeAndDocCount: config.enableSizeAndDocCount,
@ -669,7 +670,7 @@ export class IndexTable extends Component {
</>
)}
<EuiFlexItem grow={false}>
<CreateIndexButton loadIndices={loadIndices} />
<CreateIndexButton loadIndices={loadIndices} share={share} />
</EuiFlexItem>
</EuiFlexGroup>
@ -714,6 +715,7 @@ export class IndexTable extends Component {
<EuiTableRowCell align="center" colSpan={columnsCount}>
<NoMatch
loadIndices={loadIndices}
share={share}
filter={filter}
resetFilter={() => filterChanged('')}
extensionsService={extensionsService}

View file

@ -12,9 +12,9 @@ export enum AnalyticsEvents {
startCreateIndexPageModifyIndexName = 'start_modify_index_name',
startCreateIndexClick = 'start_create_index',
startCreateIndexLanguageSelect = 'start_code_lang_select',
startCreateIndexRunInConsole = 'start_cta_run_in_console',
startCreateIndexCodeCopyInstall = 'start_code_copy_install',
startCreateIndexCodeCopy = 'start_code_copy',
startCreateIndexRunInConsole = 'start_cta_run_in_console',
startCreateIndexCreatedRedirect = 'start_index_created_api',
startFileUploadClick = 'start_file_upload',
indexDetailsInstallCodeCopy = 'index_details_code_copy_install',
@ -23,4 +23,15 @@ export enum AnalyticsEvents {
indexDetailsNavDataTab = 'index_details_nav_data_tab',
indexDetailsNavSettingsTab = 'index_details_nav_settings_tab',
indexDetailsNavMappingsTab = 'index_details_nav_mappings_tab',
createIndexPageOpened = 'create_index_page_opened',
createIndexShowCodeClick = 'create_index_show_code',
createIndexShowUIClick = 'create_index_show_create_index_ui',
createIndexPageModifyIndexName = 'create_index_modify_index_name',
createIndexCreateIndexClick = 'create_index_click_create',
createIndexLanguageSelect = 'create_index_code_lang_select',
createIndexRunInConsole = 'create_index_run_in_console',
createIndexCodeCopyInstall = 'create_index_copy_install',
createIndexCodeCopy = 'create_index_code_copy',
createIndexFileUploadClick = 'create_index_file_upload',
createIndexIndexCreatedRedirect = 'create_index_created_api',
}

View file

@ -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, { useCallback, useState } from 'react';
import type { IndicesStatusResponse, UserStartPrivilegesResponse } from '../../../common';
import { AnalyticsEvents } from '../../analytics/constants';
import { AvailableLanguages } from '../../code_examples';
import { useKibana } from '../../hooks/use_kibana';
import { useUsageTracker } from '../../hooks/use_usage_tracker';
import { CreateIndexFormState } from '../../types';
import { generateRandomIndexName } from '../../utils/indices';
import { getDefaultCodingLanguage } from '../../utils/language';
import { CreateIndexPanel } from '../shared/create_index_panel';
import { CreateIndexCodeView } from './create_index_code_view';
import { CreateIndexUIView } from './create_index_ui_view';
function initCreateIndexState() {
const defaultIndexName = generateRandomIndexName();
return {
indexName: defaultIndexName,
defaultIndexName,
codingLanguage: getDefaultCodingLanguage(),
};
}
export interface CreateIndexProps {
indicesData?: IndicesStatusResponse;
userPrivileges?: UserStartPrivilegesResponse;
}
enum CreateIndexViewMode {
UI = 'ui',
Code = 'code',
}
export const CreateIndex = ({ indicesData, userPrivileges }: CreateIndexProps) => {
const { application } = useKibana().services;
const [createIndexView, setCreateIndexView] = useState<CreateIndexViewMode>(
userPrivileges?.privileges.canCreateIndex === false
? CreateIndexViewMode.Code
: CreateIndexViewMode.UI
);
const [formState, setFormState] = useState<CreateIndexFormState>(initCreateIndexState);
const usageTracker = useUsageTracker();
const onChangeView = useCallback(
(id: string) => {
switch (id) {
case CreateIndexViewMode.UI:
usageTracker.click(AnalyticsEvents.createIndexShowUIClick);
setCreateIndexView(CreateIndexViewMode.UI);
return;
case CreateIndexViewMode.Code:
usageTracker.click(AnalyticsEvents.createIndexShowCodeClick);
setCreateIndexView(CreateIndexViewMode.Code);
return;
}
},
[usageTracker]
);
const onChangeCodingLanguage = useCallback(
(language: AvailableLanguages) => {
setFormState({
...formState,
codingLanguage: language,
});
usageTracker.count([
AnalyticsEvents.createIndexLanguageSelect,
`${AnalyticsEvents.createIndexLanguageSelect}_${language}`,
]);
},
[usageTracker, formState, setFormState]
);
const onClose = useCallback(() => {
application.navigateToApp('management', { deepLinkId: 'index_management' });
}, [application]);
return (
<CreateIndexPanel
createIndexView={createIndexView}
onChangeView={onChangeView}
onClose={onClose}
>
{createIndexView === CreateIndexViewMode.UI && (
<CreateIndexUIView
formState={formState}
setFormState={setFormState}
userPrivileges={userPrivileges}
/>
)}
{createIndexView === CreateIndexViewMode.Code && (
<CreateIndexCodeView
indicesData={indicesData}
selectedLanguage={formState.codingLanguage}
indexName={formState.indexName}
changeCodingLanguage={onChangeCodingLanguage}
canCreateApiKey={userPrivileges?.privileges.canCreateApiKeys}
analyticsEvents={{
runInConsole: AnalyticsEvents.createIndexRunInConsole,
installCommands: AnalyticsEvents.createIndexCodeCopyInstall,
createIndex: AnalyticsEvents.createIndexCodeCopy,
}}
/>
)}
</CreateIndexPanel>
);
};

View file

@ -0,0 +1,26 @@
/*
* 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 type { IndicesStatusResponse } from '../../../common';
import {
CreateIndexCodeView as SharedCreateIndexCodeView,
CreateIndexCodeViewProps as SharedCreateIndexCodeViewProps,
} from '../shared/create_index_code_view';
import { useIndicesRedirect } from './hooks/use_indices_redirect';
export interface CreateIndexCodeViewProps extends SharedCreateIndexCodeViewProps {
indicesData?: IndicesStatusResponse;
}
export const CreateIndexCodeView = ({ indicesData, ...props }: CreateIndexCodeViewProps) => {
useIndicesRedirect(indicesData);
return <SharedCreateIndexCodeView {...props} />;
};

View file

@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiLoadingLogo, EuiPageTemplate } from '@elastic/eui';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
import { useKibana } from '../../hooks/use_kibana';
import { useIndicesStatusQuery } from '../../hooks/api/use_indices_status';
import { useUserPrivilegesQuery } from '../../hooks/api/use_user_permissions';
import { LoadIndicesStatusError } from '../shared/load_indices_status_error';
import { CreateIndex } from './create_index';
import { usePageChrome } from '../../hooks/use_page_chrome';
import { IndexManagementBreadcrumbs } from '../shared/breadcrumbs';
const CreateIndexLabel = i18n.translate('xpack.searchIndices.createIndex.docTitle', {
defaultMessage: 'Create Index',
});
export const CreateIndexPage = () => {
const { console: consolePlugin } = useKibana().services;
const {
data: indicesData,
isInitialLoading,
isError: hasIndicesStatusFetchError,
error: indicesFetchError,
} = useIndicesStatusQuery();
const { data: userPrivileges } = useUserPrivilegesQuery();
const embeddableConsole = useMemo(
() => (consolePlugin?.EmbeddableConsole ? <consolePlugin.EmbeddableConsole /> : null),
[consolePlugin]
);
usePageChrome(CreateIndexLabel, [...IndexManagementBreadcrumbs, { text: CreateIndexLabel }]);
return (
<EuiPageTemplate
offset={0}
restrictWidth={false}
data-test-subj="elasticsearchCreateIndexPage"
grow={false}
>
<KibanaPageTemplate.Section alignment="center" restrictWidth={false} grow>
{isInitialLoading && <EuiLoadingLogo />}
{hasIndicesStatusFetchError && <LoadIndicesStatusError error={indicesFetchError} />}
{!isInitialLoading && !hasIndicesStatusFetchError && (
<CreateIndex indicesData={indicesData} userPrivileges={userPrivileges} />
)}
</KibanaPageTemplate.Section>
{embeddableConsole}
</EuiPageTemplate>
);
};

View file

@ -0,0 +1,76 @@
/*
* 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, useState } from 'react';
import type { UserStartPrivilegesResponse } from '../../../common';
import { AnalyticsEvents } from '../../analytics/constants';
import { CreateIndexFormState } from '../../types';
import { CreateIndexForm } from '../shared/create_index_form';
import { useUsageTracker } from '../../hooks/use_usage_tracker';
import { isValidIndexName } from '../../utils/indices';
import { useCreateIndex } from '../shared/hooks/use_create_index';
import { useKibana } from '../../hooks/use_kibana';
export interface CreateIndexUIViewProps {
formState: CreateIndexFormState;
setFormState: (value: CreateIndexFormState) => void;
userPrivileges?: UserStartPrivilegesResponse;
}
export const CreateIndexUIView = ({
formState,
setFormState,
userPrivileges,
}: CreateIndexUIViewProps) => {
const [indexNameHasError, setIndexNameHasError] = useState<boolean>(false);
const { application } = useKibana().services;
const usageTracker = useUsageTracker();
const { createIndex, isLoading } = useCreateIndex();
const onIndexNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newIndexName = e.target.value;
setFormState({ ...formState, indexName: e.target.value });
const invalidIndexName = !isValidIndexName(newIndexName);
if (indexNameHasError !== invalidIndexName) {
setIndexNameHasError(invalidIndexName);
}
};
const onCreateIndex = useCallback(
(e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!isValidIndexName(formState.indexName)) {
return;
}
usageTracker.click(AnalyticsEvents.createIndexCreateIndexClick);
if (formState.defaultIndexName !== formState.indexName) {
usageTracker.click(AnalyticsEvents.createIndexPageModifyIndexName);
}
createIndex({ indexName: formState.indexName });
},
[usageTracker, createIndex, formState.indexName, formState.defaultIndexName]
);
const onFileUpload = useCallback(() => {
usageTracker.click(AnalyticsEvents.createIndexFileUploadClick);
application.navigateToApp('ml', { path: 'filedatavisualizer' });
}, [usageTracker, application]);
return (
<CreateIndexForm
indexName={formState.indexName}
indexNameHasError={indexNameHasError}
isLoading={isLoading}
onCreateIndex={onCreateIndex}
onFileUpload={onFileUpload}
onIndexNameChange={onIndexNameChange}
userPrivileges={userPrivileges}
showAPIKeyCreateLabel={false}
/>
);
};

View file

@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useEffect, useState } from 'react';
import type { IndicesStatusResponse } from '../../../../common';
import { useKibana } from '../../../hooks/use_kibana';
import { getFirstNewIndexName } from '../../../utils/indices';
import { navigateToIndexDetails } from '../../utils';
import { useUsageTracker } from '../../../contexts/usage_tracker_context';
import { AnalyticsEvents } from '../../../analytics/constants';
export const useIndicesRedirect = (indicesStatus?: IndicesStatusResponse) => {
const { application, http } = useKibana().services;
const [initialStatus, setInitialStatus] = useState<IndicesStatusResponse | undefined>(undefined);
const [hasDoneRedirect, setHasDoneRedirect] = useState(() => false);
const usageTracker = useUsageTracker();
return useEffect(() => {
if (hasDoneRedirect) {
return;
}
if (!indicesStatus) {
return;
}
if (initialStatus === undefined) {
setInitialStatus(indicesStatus);
return;
}
const newIndexName = getFirstNewIndexName(initialStatus.indexNames, indicesStatus.indexNames);
if (newIndexName) {
navigateToIndexDetails(application, http, newIndexName);
setHasDoneRedirect(true);
usageTracker.click(AnalyticsEvents.createIndexIndexCreatedRedirect);
return;
}
}, [
application,
http,
indicesStatus,
initialStatus,
setHasDoneRedirect,
usageTracker,
hasDoneRedirect,
]);
};

View file

@ -37,20 +37,14 @@ import { SearchIndexDetailsPageMenuItemPopover } from './details_page_menu_item'
import { useIndexDocumentSearch } from '../../hooks/api/use_document_search';
import { useUsageTracker } from '../../contexts/usage_tracker_context';
import { AnalyticsEvents } from '../../analytics/constants';
import { usePageChrome } from '../../hooks/use_page_chrome';
import { IndexManagementBreadcrumbs } from '../shared/breadcrumbs';
export const SearchIndexDetailsPage = () => {
const indexName = decodeURIComponent(useParams<{ indexName: string }>().indexName);
const tabId = decodeURIComponent(useParams<{ tabId: string }>().tabId);
const {
console: consolePlugin,
docLinks,
application,
history,
share,
chrome,
serverless,
} = useKibana().services;
const { console: consolePlugin, docLinks, application, history, share } = useKibana().services;
const {
data: index,
refetch,
@ -82,23 +76,12 @@ export const SearchIndexDetailsPage = () => {
setHasDocuments(!(!isInitialLoading && indexDocuments?.results?.data.length === 0));
}, [indexDocuments, isInitialLoading, setHasDocuments, setDocumentsLoading]);
useEffect(() => {
chrome.docTitle.change(indexName);
if (serverless) {
serverless.setBreadcrumbs([
{
text: i18n.translate('xpack.searchIndices.indexBreadcrumbLabel', {
defaultMessage: 'Index Management',
}),
href: '/app/management/data/index_management/indices',
},
{
text: indexName,
},
]);
}
}, [chrome, indexName, serverless]);
usePageChrome(indexName, [
...IndexManagementBreadcrumbs,
{
text: indexName,
},
]);
const usageTracker = useUsageTracker();

View file

@ -7,13 +7,17 @@
import React from 'react';
import { Route, Router, Routes } from '@kbn/shared-ux-router';
import { Redirect } from 'react-router-dom';
import { useKibana } from '../../hooks/use_kibana';
import { useKibana } from '../hooks/use_kibana';
import {
SearchIndexDetailsTabs,
SEARCH_INDICES_DETAILS_PATH,
SEARCH_INDICES_DETAILS_TABS_PATH,
} from '../../routes';
import { SearchIndexDetailsPage } from './details_page';
CREATE_INDEX_PATH,
} from '../routes';
import { SearchIndexDetailsPage } from './indices/details_page';
import { CreateIndexPage } from './create_index/create_index_page';
export const SearchIndicesRouter: React.FC = () => {
const { application, history } = useKibana().services;
return (
@ -29,6 +33,7 @@ export const SearchIndicesRouter: React.FC = () => {
/>
</Routes>
</Route>
<Route exact path={CREATE_INDEX_PATH} component={CreateIndexPage} />
<Route
render={() => {
application.navigateToApp('elasticsearchStart');

View file

@ -17,19 +17,19 @@ interface APIKeyCalloutProps {
export const APIKeyCallout = ({ apiKey }: APIKeyCalloutProps) => {
const title = apiKey
? i18n.translate('xpack.searchIndices.startPage.codeView.apiKeyTitle', {
? i18n.translate('xpack.searchIndices.shared.codeView.apiKeyTitle', {
defaultMessage: 'Copy your API key',
})
: i18n.translate('xpack.searchIndices.startPage.codeView.explicitGenerate.apiKeyTitle', {
: i18n.translate('xpack.searchIndices.shared.codeView.explicitGenerate.apiKeyTitle', {
defaultMessage: 'Create an API key',
});
const description = apiKey
? i18n.translate('xpack.searchIndices.startPage.codeView.apiKeyDescription', {
? i18n.translate('xpack.searchIndices.shared.codeView.apiKeyDescription', {
defaultMessage:
'Make sure you keep it somewhere safe. You wont be able to retrieve it later.',
})
: i18n.translate('xpack.searchIndices.startPage.codeView.explicitGenerate.apiKeyDescription', {
: i18n.translate('xpack.searchIndices.shared.codeView.explicitGenerate.apiKeyDescription', {
defaultMessage: 'Create an API key to connect to Elasticsearch.',
});

View file

@ -0,0 +1,24 @@
/*
* 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 { ChromeBreadcrumb } from '@kbn/core-chrome-browser';
import { i18n } from '@kbn/i18n';
export const IndexManagementBreadcrumbs: ChromeBreadcrumb[] = [
{
text: i18n.translate('xpack.searchIndices.breadcrumbs.indexManagement.label', {
defaultMessage: 'Index Management',
}),
href: '/app/management/data/index_management',
},
{
text: i18n.translate('xpack.searchIndices.breadcrumbs.indexManagement.indices.label', {
defaultMessage: 'Indices',
}),
href: '/app/management/data/index_management/indices',
},
];

View file

@ -4,61 +4,55 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useCallback, useMemo } from 'react';
import React, { useMemo } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { TryInConsoleButton } from '@kbn/try-in-console';
import { useSearchApiKey } from '@kbn/search-api-keys-components';
import { AnalyticsEvents } from '../../analytics/constants';
import { Languages, AvailableLanguages, LanguageOptions } from '../../code_examples';
import { useUsageTracker } from '../../hooks/use_usage_tracker';
import { useKibana } from '../../hooks/use_kibana';
import { useElasticsearchUrl } from '../../hooks/use_elasticsearch_url';
import { CodeSample } from '../shared/code_sample';
import { LanguageSelector } from '../shared/language_selector';
import { CreateIndexFormState } from './types';
import { useStartPageCodingExamples } from './hooks/use_coding_examples';
import { APIKeyCallout } from './api_key_callout';
import { CodeSample } from './code_sample';
import { useCreateIndexCodingExamples } from './hooks/use_create_index_coding_examples';
import { LanguageSelector } from './language_selector';
export interface CreateIndexCodeViewProps {
createIndexForm: CreateIndexFormState;
selectedLanguage: AvailableLanguages;
indexName: string;
changeCodingLanguage: (language: AvailableLanguages) => void;
canCreateApiKey?: boolean;
analyticsEvents: {
runInConsole: string;
installCommands: string;
createIndex: string;
};
}
export const CreateIndexCodeView = ({
createIndexForm,
changeCodingLanguage,
analyticsEvents,
canCreateApiKey,
changeCodingLanguage,
indexName,
selectedLanguage,
}: CreateIndexCodeViewProps) => {
const { application, share, console: consolePlugin } = useKibana().services;
const usageTracker = useUsageTracker();
const selectedCodeExamples = useStartPageCodingExamples();
const selectedCodeExamples = useCreateIndexCodingExamples();
const { codingLanguage: selectedLanguage } = createIndexForm;
const onSelectLanguage = useCallback(
(value: AvailableLanguages) => {
changeCodingLanguage(value);
usageTracker.count([
AnalyticsEvents.startCreateIndexLanguageSelect,
`${AnalyticsEvents.startCreateIndexLanguageSelect}_${value}`,
]);
},
[usageTracker, changeCodingLanguage]
);
const elasticsearchUrl = useElasticsearchUrl();
const { apiKey, apiKeyIsVisible } = useSearchApiKey();
const codeParams = useMemo(() => {
return {
indexName: createIndexForm.indexName || undefined,
indexName: indexName || undefined,
elasticsearchURL: elasticsearchUrl,
apiKey: apiKeyIsVisible && apiKey ? apiKey : undefined,
};
}, [createIndexForm.indexName, elasticsearchUrl, apiKeyIsVisible, apiKey]);
}, [indexName, elasticsearchUrl, apiKeyIsVisible, apiKey]);
const selectedCodeExample = useMemo(() => {
return selectedCodeExamples[selectedLanguage];
}, [selectedLanguage, selectedCodeExamples]);
@ -75,7 +69,7 @@ export const CreateIndexCodeView = ({
<LanguageSelector
options={LanguageOptions}
selectedLanguage={selectedLanguage}
onSelectLanguage={onSelectLanguage}
onSelectLanguage={changeCodingLanguage}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
@ -87,8 +81,8 @@ export const CreateIndexCodeView = ({
telemetryId={`${selectedLanguage}_create_index`}
onClick={() => {
usageTracker.click([
AnalyticsEvents.startCreateIndexRunInConsole,
`${AnalyticsEvents.startCreateIndexRunInConsole}_${selectedLanguage}`,
analyticsEvents.runInConsole,
`${analyticsEvents.runInConsole}_${selectedLanguage}`,
]);
}}
/>
@ -102,8 +96,8 @@ export const CreateIndexCodeView = ({
code={selectedCodeExample.installCommand}
onCodeCopyClick={() => {
usageTracker.click([
AnalyticsEvents.startCreateIndexCodeCopyInstall,
`${AnalyticsEvents.startCreateIndexCodeCopyInstall}_${selectedLanguage}`,
analyticsEvents.installCommands,
`${analyticsEvents.installCommands}_${selectedLanguage}`,
]);
}}
/>
@ -116,9 +110,9 @@ export const CreateIndexCodeView = ({
code={selectedCodeExample.createIndex(codeParams)}
onCodeCopyClick={() => {
usageTracker.click([
AnalyticsEvents.startCreateIndexCodeCopy,
`${AnalyticsEvents.startCreateIndexCodeCopy}_${selectedLanguage}`,
`${AnalyticsEvents.startCreateIndexCodeCopy}_${selectedLanguage}_${selectedCodeExamples.exampleType}`,
analyticsEvents.createIndex,
`${analyticsEvents.createIndex}_${selectedLanguage}`,
`${analyticsEvents.createIndex}_${selectedLanguage}_${selectedCodeExamples.exampleType}`,
]);
}}
/>

View file

@ -0,0 +1,165 @@
/*
* 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 {
EuiButton,
EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
EuiForm,
EuiFormRow,
EuiHorizontalRule,
EuiIcon,
EuiLink,
EuiPanel,
EuiSpacer,
EuiText,
EuiToolTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import type { UserStartPrivilegesResponse } from '../../../common';
export interface CreateIndexFormProps {
indexName: string;
indexNameHasError: boolean;
isLoading: boolean;
onCreateIndex: (e: React.FormEvent<HTMLFormElement>) => void;
onFileUpload: () => void;
onIndexNameChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
showAPIKeyCreateLabel: boolean;
userPrivileges?: UserStartPrivilegesResponse;
}
export const CreateIndexForm = ({
indexName,
indexNameHasError,
isLoading,
onCreateIndex,
onFileUpload,
onIndexNameChange,
showAPIKeyCreateLabel,
userPrivileges,
}: CreateIndexFormProps) => {
return (
<>
<EuiForm
data-test-subj="createIndexUIView"
fullWidth
component="form"
onSubmit={onCreateIndex}
>
<EuiFormRow
label={i18n.translate('xpack.searchIndices.shared.createIndex.name.label', {
defaultMessage: 'Name your index',
})}
helpText={i18n.translate('xpack.searchIndices.shared.createIndex.name.helpText', {
defaultMessage:
'Index names must be lowercase and can only contain hyphens and numbers',
})}
fullWidth
isInvalid={indexNameHasError}
>
<EuiFieldText
autoFocus
fullWidth
data-test-subj="indexNameField"
name="indexName"
value={indexName}
isInvalid={indexNameHasError}
disabled={userPrivileges?.privileges?.canCreateIndex === false}
onChange={onIndexNameChange}
placeholder={i18n.translate('xpack.searchIndices.shared.createIndex.name.placeholder', {
defaultMessage: 'Enter a name for your index',
})}
/>
</EuiFormRow>
<EuiSpacer />
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={false}>
<EuiToolTip
content={
userPrivileges?.privileges?.canCreateIndex === false ? (
<p>
{i18n.translate('xpack.searchIndices.shared.createIndex.permissionTooltip', {
defaultMessage: 'You do not have permission to create an index.',
})}
</p>
) : undefined
}
>
<EuiButton
fill
color="primary"
iconSide="left"
iconType="sparkles"
data-test-subj="createIndexBtn"
disabled={
userPrivileges?.privileges?.canCreateIndex === false ||
indexNameHasError ||
isLoading
}
isLoading={isLoading}
type="submit"
>
{i18n.translate('xpack.searchIndices.shared.createIndex.action.text', {
defaultMessage: 'Create my index',
})}
</EuiButton>
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem>
{showAPIKeyCreateLabel && (
<EuiFlexGroup gutterSize="s">
<EuiIcon size="m" type="key" color="subdued" />
<EuiText size="s" data-test-subj="apiKeyLabel">
<p>
{i18n.translate(
'xpack.searchIndices.shared.createIndex.apiKeyCreation.description',
{
defaultMessage: "We'll create an API key for this index",
}
)}
</p>
</EuiText>
</EuiFlexGroup>
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiForm>
<EuiHorizontalRule margin="none" />
<EuiPanel color="transparent" paddingSize="s">
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiIcon type="documents" />
</EuiFlexItem>
<EuiFlexItem>
<EuiText color="subdued" size="s">
<p>
<FormattedMessage
id="xpack.searchIndices.shared.createIndex.fileUpload.text"
defaultMessage="Already have some data? {link}"
values={{
link: (
<EuiLink data-test-subj="uploadFileLink" onClick={onFileUpload}>
{i18n.translate('xpack.searchIndices.shared.createIndex.fileUpload.link', {
defaultMessage: 'Upload a file',
})}
</EuiLink>
),
}}
/>
</p>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</>
);
};

View file

@ -0,0 +1,271 @@
/*
* 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, { useMemo } from 'react';
import {
EuiButtonEmpty,
EuiButtonGroup,
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiPanel,
EuiSpacer,
EuiText,
EuiTextAlign,
EuiTitle,
useEuiTheme,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { docLinks } from '../../../common/doc_links';
import { useKibana } from '../../hooks/use_kibana';
import { CreateIndexViewMode } from '../../types';
const MAX_WIDTH = '650px';
export interface CreateIndexPanelProps {
children: React.ReactNode | React.ReactNode[];
createIndexView: CreateIndexViewMode;
onChangeView: (id: string) => void;
onClose: () => void;
showCallouts?: boolean;
showSkip?: boolean;
title?: React.ReactNode;
}
export const CreateIndexPanel = ({
children,
createIndexView,
onChangeView,
onClose,
showCallouts,
showSkip,
title,
}: CreateIndexPanelProps) => {
const { cloud, http } = useKibana().services;
const { euiTheme } = useEuiTheme();
const o11yTrialLink = useMemo(() => {
if (cloud && cloud.isServerlessEnabled) {
const baseUrl = cloud?.projectsUrl ?? 'https://cloud.elastic.co/projects/';
return `${baseUrl}create/observability/start`;
}
return http.basePath.prepend('/app/observability/onboarding');
}, [cloud, http]);
return (
<>
<EuiPanel
color="subdued"
hasShadow={false}
hasBorder
style={{
maxWidth: MAX_WIDTH,
margin: '0 auto',
padding: euiTheme.size.l,
paddingTop: euiTheme.size.m,
}}
>
<EuiFlexGroup justifyContent="flexEnd">
<EuiButtonIcon
data-test-subj="closeCreateIndex"
iconType="cross"
onClick={onClose}
color="text"
aria-label={i18n.translate('xpack.searchIndices.shared.createIndex.closeAriaLabel', {
defaultMessage: 'Close create index',
})}
/>
</EuiFlexGroup>
<EuiPanel
color="transparent"
paddingSize="none"
style={{
paddingLeft: euiTheme.size.m,
paddingRight: euiTheme.size.m,
}}
>
<EuiFlexGroup alignItems="center" gutterSize="m">
<EuiFlexItem grow={false}>
<EuiIcon type="logoElasticsearch" size="xl" />
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle size="xs">
<h1>
{i18n.translate('xpack.searchIndices.shared.createIndex.pageTitle', {
defaultMessage: 'Elasticsearch',
})}
</h1>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
<EuiTitle size="l">
<h2>
{i18n.translate('xpack.searchIndices.shared.createIndex.pageDescription', {
defaultMessage: 'Get started with Elasticsearch',
})}
</h2>
</EuiTitle>
</EuiPanel>
<EuiSpacer size="l" />
<EuiPanel>
<EuiFlexGroup direction="column" gutterSize="m">
<EuiFlexGroup alignItems="center">
<EuiFlexItem>
<EuiTitle size="xs">
<h4>
{title ??
i18n.translate('xpack.searchIndices.shared.createIndex.defaultTitle', {
defaultMessage: 'Create an index',
})}
</h4>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonGroup
legend={i18n.translate(
'xpack.searchIndices.shared.createIndex.viewSelect.legend',
{
defaultMessage: 'Create index view selection',
}
)}
options={[
{
id: CreateIndexViewMode.UI,
label: i18n.translate(
'xpack.searchIndices.shared.createIndex.viewSelect.ui',
{
defaultMessage: 'UI',
}
),
'data-test-subj': 'createIndexUIViewBtn',
},
{
id: CreateIndexViewMode.Code,
label: i18n.translate(
'xpack.searchIndices.shared.createIndex.viewSelect.code',
{
defaultMessage: 'Code',
}
),
'data-test-subj': 'createIndexCodeViewBtn',
},
]}
buttonSize="compressed"
idSelected={createIndexView}
onChange={onChangeView}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiText color="subdued">
<p>
{i18n.translate('xpack.searchIndices.shared.createIndex.description', {
defaultMessage:
'An index stores your data and defines the schema, or field mappings, for your searches',
})}
</p>
</EuiText>
{children}
</EuiFlexGroup>
</EuiPanel>
{showCallouts && (
<>
<EuiSpacer />
<EuiPanel color="transparent">
<EuiTextAlign textAlign="center">
<EuiTitle size="xs">
<h5>
{i18n.translate(
'xpack.searchIndices.shared.createIndex.observabilityCallout.title',
{
defaultMessage: 'Looking to store your logs or metrics data?',
}
)}
</h5>
</EuiTitle>
</EuiTextAlign>
<EuiSpacer size="m" />
<EuiFlexGroup alignItems="center" justifyContent="center">
<EuiFlexItem grow={false}>
<EuiButtonEmpty
color="text"
iconSide="right"
iconType="popout"
data-test-subj="analyzeLogsBtn"
href={docLinks.analyzeLogs}
target="_blank"
>
{i18n.translate(
'xpack.searchIndices.shared.createIndex.observabilityCallout.logs.button',
{
defaultMessage: 'Collect and analyze logs',
}
)}
</EuiButtonEmpty>
<EuiText color="subdued" size="s" textAlign="center">
<small>
{i18n.translate(
'xpack.searchIndices.shared.createIndex.observabilityCallout.logs.subTitle',
{
defaultMessage: 'Explore Logstash and Beats',
}
)}
</small>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText>or</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
color="text"
iconSide="right"
iconType="popout"
data-test-subj="startO11yTrialBtn"
href={o11yTrialLink}
target="_blank"
>
{i18n.translate(
'xpack.searchIndices.shared.createIndex.observabilityCallout.o11yTrial.button',
{
defaultMessage: 'Start an Observability trial',
}
)}
</EuiButtonEmpty>
<EuiText color="subdued" size="s" textAlign="center">
<small>
{i18n.translate(
'xpack.searchIndices.shared.createIndex.observabilityCallout.o11yTrial.subTitle',
{
defaultMessage: 'Powerful performance monitoring',
}
)}
</small>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</>
)}
</EuiPanel>
{showSkip === true && (
<>
<EuiSpacer />
<EuiFlexGroup justifyContent="center">
<EuiButtonEmpty onClick={onClose} data-test-subj="createIndexSkipBtn">
{i18n.translate('xpack.searchIndices.shared.createIndex.skipLabel', {
defaultMessage: 'Skip',
})}
</EuiButtonEmpty>
</EuiFlexGroup>
</>
)}
</>
);
};

View file

@ -11,7 +11,7 @@ import { useCreateIndex as useCreateIndexApi } from '../../../hooks/api/use_crea
import { useKibana } from '../../../hooks/use_kibana';
import { navigateToIndexDetails } from './utils';
import { navigateToIndexDetails } from '../../utils';
export const useCreateIndex = () => {
const { application, http } = useKibana().services;

View file

@ -8,7 +8,7 @@
import { CreateIndexCodeExamples } from '../../../types';
import { DenseVectorSeverlessCodeExamples } from '../../../code_examples/create_index';
export const useStartPageCodingExamples = (): CreateIndexCodeExamples => {
export const useCreateIndexCodingExamples = (): CreateIndexCodeExamples => {
// TODO: in the future this will be dynamic based on the onboarding token
// or project sub-type
return DenseVectorSeverlessCodeExamples;

View file

@ -15,14 +15,14 @@ export interface StartPageErrorProps {
error: unknown;
}
export const StartPageError = ({ error }: StartPageErrorProps) => {
export const LoadIndicesStatusError = ({ error }: StartPageErrorProps) => {
return (
<EuiEmptyPrompt
iconType="error"
color="danger"
title={
<h2>
{i18n.translate('xpack.searchIndices.startPage.statusFetchError.title', {
{i18n.translate('xpack.searchIndices.shared.statusFetchError.title', {
defaultMessage: 'Error loading indices',
})}
</h2>
@ -31,7 +31,7 @@ export const StartPageError = ({ error }: StartPageErrorProps) => {
<EuiCodeBlock css={{ textAlign: 'left' }}>
{getErrorMessage(
error,
i18n.translate('xpack.searchIndices.startPage.statusFetchError.unknownError', {
i18n.translate('xpack.searchIndices.shared.statusFetchError.unknownError', {
defaultMessage: 'Unknown error fetching indices.',
})
)}

View file

@ -6,52 +6,29 @@
*/
import React, { useCallback, useState } from 'react';
import {
EuiButton,
EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
EuiForm,
EuiFormRow,
EuiHorizontalRule,
EuiIcon,
EuiLink,
EuiPanel,
EuiSpacer,
EuiText,
EuiToolTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import type { UserStartPrivilegesResponse } from '../../../common';
import { AnalyticsEvents } from '../../analytics/constants';
import { useUsageTracker } from '../../hooks/use_usage_tracker';
import { CreateIndexFormState } from '../../types';
import { isValidIndexName } from '../../utils/indices';
import { useCreateIndex } from './hooks/use_create_index';
import { useCreateIndex } from '../shared/hooks/use_create_index';
import { CreateIndexForm } from '../shared/create_index_form';
import { CreateIndexFormState } from './types';
import { useKibana } from '../../hooks/use_kibana';
const CREATE_INDEX_CONTENT = i18n.translate(
'xpack.searchIndices.startPage.createIndex.action.text',
{
defaultMessage: 'Create my index',
}
);
export interface CreateIndexFormProps {
export interface CreateIndexUIViewProps {
formState: CreateIndexFormState;
setFormState: React.Dispatch<React.SetStateAction<CreateIndexFormState>>;
userPrivileges?: UserStartPrivilegesResponse;
}
export const CreateIndexForm = ({
export const CreateIndexUIView = ({
userPrivileges,
formState,
setFormState,
}: CreateIndexFormProps) => {
}: CreateIndexUIViewProps) => {
const { application } = useKibana().services;
const [indexNameHasError, setIndexNameHasError] = useState<boolean>(false);
const usageTracker = useUsageTracker();
@ -86,129 +63,15 @@ export const CreateIndexForm = ({
}, [usageTracker, application]);
return (
<>
<EuiForm
data-test-subj="createIndexUIView"
fullWidth
component="form"
onSubmit={onCreateIndex}
>
<EuiFormRow
label={i18n.translate('xpack.searchIndices.startPage.createIndex.name.label', {
defaultMessage: 'Name your index',
})}
helpText={i18n.translate('xpack.searchIndices.startPage.createIndex.name.helpText', {
defaultMessage:
'Index names must be lowercase and can only contain hyphens and numbers',
})}
fullWidth
isInvalid={indexNameHasError}
>
<EuiFieldText
autoFocus
fullWidth
data-test-subj="indexNameField"
name="indexName"
value={formState.indexName}
isInvalid={indexNameHasError}
disabled={userPrivileges?.privileges?.canCreateIndex === false}
onChange={onIndexNameChange}
placeholder={i18n.translate(
'xpack.searchIndices.startPage.createIndex.name.placeholder',
{
defaultMessage: 'Enter a name for your index',
}
)}
/>
</EuiFormRow>
<EuiSpacer />
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={false}>
{userPrivileges?.privileges?.canCreateIndex === false ? (
<EuiToolTip
content={
<p>
{i18n.translate('xpack.searchIndices.startPage.createIndex.permissionTooltip', {
defaultMessage: 'You do not have permission to create an index.',
})}
</p>
}
>
<EuiButton
fill
color="primary"
iconSide="left"
iconType="sparkles"
data-test-subj="createIndexBtn"
disabled={true}
>
{CREATE_INDEX_CONTENT}
</EuiButton>
</EuiToolTip>
) : (
<EuiButton
fill
color="primary"
iconSide="left"
iconType="sparkles"
data-test-subj="createIndexBtn"
disabled={indexNameHasError || isLoading}
isLoading={isLoading}
type="submit"
>
{CREATE_INDEX_CONTENT}
</EuiButton>
)}
</EuiFlexItem>
<EuiFlexItem>
{userPrivileges?.privileges?.canCreateApiKeys && (
<EuiFlexGroup gutterSize="s">
<EuiIcon size="m" type="key" color="subdued" />
<EuiText size="s" data-test-subj="apiKeyLabel">
<p>
{i18n.translate(
'xpack.searchIndices.startPage.createIndex.apiKeyCreation.description',
{
defaultMessage: "We'll create an API key for this index",
}
)}
</p>
</EuiText>
</EuiFlexGroup>
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiForm>
<EuiHorizontalRule margin="none" />
<EuiPanel color="transparent" paddingSize="s">
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiIcon type="documents" />
</EuiFlexItem>
<EuiFlexItem>
<EuiText color="subdued" size="s">
<p>
<FormattedMessage
id="xpack.searchIndices.startPage.createIndex.fileUpload.text"
defaultMessage="Already have some data? {link}"
values={{
link: (
<EuiLink data-test-subj="uploadFileLink" onClick={onFileUpload}>
{i18n.translate(
'xpack.searchIndices.startPage.createIndex.fileUpload.link',
{
defaultMessage: 'Upload a file',
}
)}
</EuiLink>
),
}}
/>
</p>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</>
<CreateIndexForm
indexName={formState.indexName}
indexNameHasError={indexNameHasError}
isLoading={isLoading}
onCreateIndex={onCreateIndex}
onFileUpload={onFileUpload}
onIndexNameChange={onIndexNameChange}
showAPIKeyCreateLabel={userPrivileges?.privileges.canCreateApiKeys ?? false}
userPrivileges={userPrivileges}
/>
);
};

View file

@ -5,23 +5,10 @@
* 2.0.
*/
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
EuiButtonEmpty,
EuiButtonGroup,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiPanel,
EuiSpacer,
EuiText,
EuiTextAlign,
EuiTitle,
} from '@elastic/eui';
import React, { useCallback, useEffect, useState } from 'react';
import { i18n } from '@kbn/i18n';
import type { IndicesStatusResponse, UserStartPrivilegesResponse } from '../../../common';
import { docLinks } from '../../../common/doc_links';
import { AnalyticsEvents } from '../../analytics/constants';
import { AvailableLanguages } from '../../code_examples';
@ -29,9 +16,11 @@ import { useUsageTracker } from '../../hooks/use_usage_tracker';
import { generateRandomIndexName } from '../../utils/indices';
import { getDefaultCodingLanguage } from '../../utils/language';
import { CreateIndexForm } from './create_index';
import { CreateIndexCodeView } from './create_index_code';
import { CreateIndexFormState } from './types';
import { CreateIndexUIView } from './create_index';
import { CreateIndexCodeView } from '../shared/create_index_code_view';
import { CreateIndexFormState, CreateIndexViewMode } from '../../types';
import { CreateIndexPanel } from '../shared/create_index_panel';
import { useKibana } from '../../hooks/use_kibana';
function initCreateIndexState(): CreateIndexFormState {
@ -43,21 +32,17 @@ function initCreateIndexState(): CreateIndexFormState {
};
}
const MAX_WIDTH = '650px';
enum CreateIndexView {
UI = 'ui',
Code = 'code',
}
export interface ElasticsearchStartProps {
indicesData?: IndicesStatusResponse;
userPrivileges?: UserStartPrivilegesResponse;
}
export const ElasticsearchStart = ({ userPrivileges }: ElasticsearchStartProps) => {
const { cloud, http } = useKibana().services;
const [createIndexView, setCreateIndexView] = useState<CreateIndexView>(
userPrivileges?.privileges.canCreateIndex === false ? CreateIndexView.Code : CreateIndexView.UI
const { application } = useKibana().services;
const [createIndexView, setCreateIndexViewMode] = useState<CreateIndexViewMode>(
userPrivileges?.privileges.canCreateIndex === false
? CreateIndexViewMode.Code
: CreateIndexViewMode.UI
);
const [formState, setFormState] = useState<CreateIndexFormState>(initCreateIndexState);
const usageTracker = useUsageTracker();
@ -68,28 +53,20 @@ export const ElasticsearchStart = ({ userPrivileges }: ElasticsearchStartProps)
useEffect(() => {
if (userPrivileges === undefined) return;
if (userPrivileges.privileges.canCreateIndex === false) {
setCreateIndexView(CreateIndexView.Code);
setCreateIndexViewMode(CreateIndexViewMode.Code);
}
}, [userPrivileges]);
const o11yTrialLink = useMemo(() => {
if (cloud && cloud.isServerlessEnabled) {
const baseUrl = cloud?.projectsUrl ?? 'https://cloud.elastic.co/projects/';
return `${baseUrl}create/observability/start`;
}
return http.basePath.prepend('/app/observability/onboarding');
}, [cloud, http]);
const onChangeView = useCallback(
(id: string) => {
switch (id) {
case CreateIndexView.UI:
case CreateIndexViewMode.UI:
usageTracker.click(AnalyticsEvents.startPageShowCreateIndexUIClick);
setCreateIndexView(CreateIndexView.UI);
setCreateIndexViewMode(CreateIndexViewMode.UI);
return;
case CreateIndexView.Code:
case CreateIndexViewMode.Code:
usageTracker.click(AnalyticsEvents.startPageShowCodeClick);
setCreateIndexView(CreateIndexView.Code);
setCreateIndexViewMode(CreateIndexViewMode.Code);
return;
}
},
@ -101,178 +78,48 @@ export const ElasticsearchStart = ({ userPrivileges }: ElasticsearchStartProps)
...formState,
codingLanguage: language,
});
usageTracker.count([
AnalyticsEvents.startCreateIndexLanguageSelect,
`${AnalyticsEvents.startCreateIndexLanguageSelect}_${language}`,
]);
},
[formState, setFormState]
[usageTracker, formState, setFormState]
);
const onClose = useCallback(() => {
application.navigateToApp('management', { deepLinkId: 'index_management' });
}, [application]);
return (
<EuiPanel
color="subdued"
hasShadow={false}
hasBorder
paddingSize="l"
style={{ maxWidth: MAX_WIDTH, margin: '0 auto' }}
<CreateIndexPanel
title={i18n.translate('xpack.searchIndices.startPage.createIndex.title', {
defaultMessage: 'Create your first index',
})}
createIndexView={createIndexView}
onChangeView={onChangeView}
onClose={onClose}
showSkip
showCallouts
>
<EuiPanel color="transparent" paddingSize="m">
<EuiFlexGroup alignItems="center" gutterSize="m">
<EuiFlexItem grow={false}>
<EuiIcon type="logoElasticsearch" size="xl" />
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle size="xs">
<h1>
{i18n.translate('xpack.searchIndices.startPage.pageTitle', {
defaultMessage: 'Elasticsearch',
})}
</h1>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
<EuiTitle size="l">
<h2>
{i18n.translate('xpack.searchIndices.startPage.pageDescription', {
defaultMessage: 'Vectorize, search, and visualize your data',
})}
</h2>
</EuiTitle>
</EuiPanel>
<EuiSpacer />
<EuiPanel>
<EuiFlexGroup direction="column" gutterSize="m">
<EuiFlexGroup alignItems="center">
<EuiFlexItem>
<EuiTitle size="xs">
<h4>
{i18n.translate('xpack.searchIndices.startPage.createIndex.title', {
defaultMessage: 'Create your first index',
})}
</h4>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonGroup
legend={i18n.translate(
'xpack.searchIndices.startPage.createIndex.viewSelec.legend',
{ defaultMessage: 'Create index view selection' }
)}
options={[
{
id: CreateIndexView.UI,
label: i18n.translate(
'xpack.searchIndices.startPage.createIndex.viewSelect.ui',
{ defaultMessage: 'UI' }
),
'data-test-subj': 'createIndexUIViewBtn',
},
{
id: CreateIndexView.Code,
label: i18n.translate(
'xpack.searchIndices.startPage.createIndex.viewSelect.code',
{ defaultMessage: 'Code' }
),
'data-test-subj': 'createIndexCodeViewBtn',
},
]}
buttonSize="compressed"
idSelected={createIndexView}
onChange={onChangeView}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiText color="subdued">
<p>
{i18n.translate('xpack.searchIndices.startPage.createIndex.description', {
defaultMessage:
'An index stores your data and defines the schema, or field mappings, for your searches',
})}
</p>
</EuiText>
{createIndexView === CreateIndexView.UI && (
<CreateIndexForm
userPrivileges={userPrivileges}
formState={formState}
setFormState={setFormState}
/>
)}
{createIndexView === CreateIndexView.Code && (
<CreateIndexCodeView
createIndexForm={formState}
changeCodingLanguage={onChangeCodingLanguage}
canCreateApiKey={userPrivileges?.privileges.canCreateApiKeys}
/>
)}
</EuiFlexGroup>
</EuiPanel>
<EuiSpacer />
<EuiPanel color="transparent">
<EuiTextAlign textAlign="center">
<EuiTitle size="xs">
<h5>
{i18n.translate('xpack.searchIndices.startPage.observabilityCallout.title', {
defaultMessage: 'Looking to store your logs or metrics data?',
})}
</h5>
</EuiTitle>
</EuiTextAlign>
<EuiSpacer size="m" />
<EuiFlexGroup alignItems="center" justifyContent="center">
<EuiFlexItem grow={false}>
<EuiButtonEmpty
color="text"
iconSide="right"
iconType="popout"
data-test-subj="analyzeLogsBtn"
href={docLinks.analyzeLogs}
target="_blank"
>
{i18n.translate('xpack.searchIndices.startPage.observabilityCallout.logs.button', {
defaultMessage: 'Collect and analyze logs',
})}
</EuiButtonEmpty>
<EuiText color="subdued" size="s" textAlign="center">
<small>
{i18n.translate(
'xpack.searchIndices.startPage.observabilityCallout.logs.subTitle',
{
defaultMessage: 'Explore Logstash and Beats',
}
)}
</small>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText>or</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
color="text"
iconSide="right"
iconType="popout"
data-test-subj="startO11yTrialBtn"
href={o11yTrialLink}
target="_blank"
>
{i18n.translate(
'xpack.searchIndices.startPage.observabilityCallout.o11yTrial.button',
{
defaultMessage: 'Start an Observability trial',
}
)}
</EuiButtonEmpty>
<EuiText color="subdued" size="s" textAlign="center">
<small>
{i18n.translate(
'xpack.searchIndices.startPage.observabilityCallout.o11yTrial.subTitle',
{
defaultMessage: 'Powerful performance monitoring',
}
)}
</small>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiPanel>
{createIndexView === CreateIndexViewMode.UI && (
<CreateIndexUIView
userPrivileges={userPrivileges}
formState={formState}
setFormState={setFormState}
/>
)}
{createIndexView === CreateIndexViewMode.Code && (
<CreateIndexCodeView
selectedLanguage={formState.codingLanguage}
indexName={formState.indexName}
changeCodingLanguage={onChangeCodingLanguage}
canCreateApiKey={userPrivileges?.privileges.canCreateApiKeys}
analyticsEvents={{
runInConsole: AnalyticsEvents.startCreateIndexRunInConsole,
installCommands: AnalyticsEvents.startCreateIndexCodeCopyInstall,
createIndex: AnalyticsEvents.startCreateIndexCodeCopy,
}}
/>
)}
</CreateIndexPanel>
);
};

View file

@ -11,7 +11,7 @@ import type { IndicesStatusResponse } from '../../../../common';
import { useKibana } from '../../../hooks/use_kibana';
import { navigateToIndexDetails } from './utils';
import { navigateToIndexDetails } from '../../utils';
import { useUsageTracker } from '../../../contexts/usage_tracker_context';
import { AnalyticsEvents } from '../../../analytics/constants';

View file

@ -6,6 +6,7 @@
*/
import React, { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiLoadingLogo, EuiPageTemplate } from '@elastic/eui';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
@ -16,7 +17,13 @@ import { useUserPrivilegesQuery } from '../../hooks/api/use_user_permissions';
import { useIndicesRedirect } from './hooks/use_indices_redirect';
import { ElasticsearchStart } from './elasticsearch_start';
import { StartPageError } from './status_error';
import { LoadIndicesStatusError } from '../shared/load_indices_status_error';
import { IndexManagementBreadcrumbs } from '../shared/breadcrumbs';
import { usePageChrome } from '../../hooks/use_page_chrome';
const PageTitle = i18n.translate('xpack.searchIndices.startPage.docTitle', {
defaultMessage: 'Create your first index',
});
export const ElasticsearchStartPage = () => {
const { console: consolePlugin } = useKibana().services;
@ -27,6 +34,7 @@ export const ElasticsearchStartPage = () => {
error: indicesFetchError,
} = useIndicesStatusQuery();
const { data: userPrivileges } = useUserPrivilegesQuery();
usePageChrome(PageTitle, [...IndexManagementBreadcrumbs, { text: PageTitle }]);
const embeddableConsole = useMemo(
() => (consolePlugin?.EmbeddableConsole ? <consolePlugin.EmbeddableConsole /> : null),
@ -43,7 +51,7 @@ export const ElasticsearchStartPage = () => {
>
<KibanaPageTemplate.Section alignment="center" restrictWidth={false} grow>
{isInitialLoading && <EuiLoadingLogo />}
{hasIndicesStatusFetchError && <StartPageError error={indicesFetchError} />}
{hasIndicesStatusFetchError && <LoadIndicesStatusError error={indicesFetchError} />}
{!isInitialLoading && !hasIndicesStatusFetchError && (
<ElasticsearchStart indicesData={indicesData} userPrivileges={userPrivileges} />
)}

View file

@ -1,14 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { AvailableLanguages } from '../../code_examples';
export interface CreateIndexFormState {
indexName: string;
defaultIndexName: string;
codingLanguage: AvailableLanguages;
}

View file

@ -5,12 +5,15 @@
* 2.0.
*/
import type { ApplicationStart, HttpSetup } from '@kbn/core/public';
import { generatePath } from 'react-router-dom';
const INDEX_DETAILS_PATH = '/app/elasticsearch/indices/index_details';
import type { ApplicationStart, HttpSetup } from '@kbn/core/public';
import { INDICES_APP_BASE, SEARCH_INDICES_DETAILS_PATH } from '../routes';
const INDEX_DETAILS_FULL_PATH = `${INDICES_APP_BASE}${SEARCH_INDICES_DETAILS_PATH}`;
function getIndexDetailsPath(http: HttpSetup, indexName: string) {
return http.basePath.prepend(`${INDEX_DETAILS_PATH}/${encodeURIComponent(indexName)}`);
return http.basePath.prepend(generatePath(INDEX_DETAILS_FULL_PATH, { indexName }));
}
export const navigateToIndexDetails = (

View file

@ -0,0 +1,37 @@
/*
* 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 { useEffect } from 'react';
import type { ChromeBreadcrumb } from '@kbn/core-chrome-browser';
import { useKibana } from './use_kibana';
export const usePageChrome = (docTitle: string, breadcrumbs: ChromeBreadcrumb[]) => {
const { chrome, http, serverless } = useKibana().services;
useEffect(() => {
chrome.docTitle.change(docTitle);
const newBreadcrumbs = breadcrumbs.map((breadcrumb) => {
if (breadcrumb.href && http.basePath.get().length > 0) {
breadcrumb.href = http.basePath.prepend(breadcrumb.href);
}
return breadcrumb;
});
if (serverless) {
serverless.setBreadcrumbs(newBreadcrumbs);
} else {
chrome.setBreadcrumbs(newBreadcrumbs);
}
return () => {
// clear manually set breadcrumbs
if (serverless) {
serverless.setBreadcrumbs([]);
} else {
chrome.setBreadcrumbs([]);
}
};
}, [breadcrumbs, chrome, docTitle, http.basePath, serverless]);
};

View file

@ -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 type { LocatorDefinition } from '@kbn/share-plugin/common';
import type { SharePluginSetup } from '@kbn/share-plugin/public';
import type { SerializableRecord } from '@kbn/utility-types';
import { INDICES_APP_ID } from '../common';
import { CREATE_INDEX_PATH } from './routes';
export function registerLocators(share: SharePluginSetup) {
share.url.locators.create<SerializableRecord>(new CreateIndexLocatorDefinition());
}
class CreateIndexLocatorDefinition implements LocatorDefinition<SerializableRecord> {
public readonly getLocation = async () => {
return {
app: INDICES_APP_ID,
path: CREATE_INDEX_PATH,
state: {},
};
};
public readonly id = 'SEARCH_CREATE_INDEX';
}

View file

@ -6,10 +6,12 @@
*/
import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import { SEARCH_INDICES_CREATE_INDEX } from '@kbn/deeplinks-search/constants';
import { i18n } from '@kbn/i18n';
import { docLinks } from '../common/doc_links';
import type {
AppPluginSetupDependencies,
SearchIndicesAppPluginStartDependencies,
SearchIndicesPluginSetup,
SearchIndicesPluginStart,
@ -17,7 +19,13 @@ import type {
} from './types';
import { initQueryClient } from './services/query_client';
import { INDICES_APP_ID, START_APP_ID } from '../common';
import { INDICES_APP_BASE, START_APP_BASE, SearchIndexDetailsTabValues } from './routes';
import {
CREATE_INDEX_PATH,
INDICES_APP_BASE,
START_APP_BASE,
SearchIndexDetailsTabValues,
} from './routes';
import { registerLocators } from './locators';
export class SearchIndicesPlugin
implements Plugin<SearchIndicesPluginSetup, SearchIndicesPluginStart>
@ -25,7 +33,8 @@ export class SearchIndicesPlugin
private pluginEnabled: boolean = false;
public setup(
core: CoreSetup<SearchIndicesAppPluginStartDependencies, SearchIndicesPluginStart>
core: CoreSetup<SearchIndicesAppPluginStartDependencies, SearchIndicesPluginStart>,
plugins: AppPluginSetupDependencies
): SearchIndicesPluginSetup {
this.pluginEnabled = true;
@ -51,12 +60,21 @@ export class SearchIndicesPlugin
core.application.register({
id: INDICES_APP_ID,
appRoute: INDICES_APP_BASE,
deepLinks: [
{
id: SEARCH_INDICES_CREATE_INDEX,
path: CREATE_INDEX_PATH,
title: i18n.translate('xpack.searchIndices.elasticsearchIndices.createIndexTitle', {
defaultMessage: 'Create index',
}),
},
],
title: i18n.translate('xpack.searchIndices.elasticsearchIndices.startAppTitle', {
defaultMessage: 'Elasticsearch Indices',
}),
async mount({ element, history }) {
const { renderApp } = await import('./application');
const { SearchIndicesRouter } = await import('./components/indices/indices_router');
const { SearchIndicesRouter } = await import('./components/indices_router');
const [coreStart, depsStart] = await core.getStartServices();
const startDeps: SearchIndicesServicesContextDeps = {
...depsStart,
@ -66,6 +84,8 @@ export class SearchIndicesPlugin
},
});
registerLocators(plugins.share);
return {
enabled: true,
startAppId: START_APP_ID,

View file

@ -13,6 +13,7 @@ export enum SearchIndexDetailsTabs {
MAPPINGS = 'mappings',
SETTINGS = 'settings',
}
export const CREATE_INDEX_PATH = `${ROOT_PATH}create`;
export const SearchIndexDetailsTabValues: string[] = Object.values(SearchIndexDetailsTabs);
export const START_APP_BASE = '/app/elasticsearch/start';

View file

@ -5,19 +5,25 @@
* 2.0.
*/
import type { CloudStart } from '@kbn/cloud-plugin/public';
import type { ConsolePluginStart } from '@kbn/console-plugin/public';
import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public';
import type { ConsolePluginSetup, ConsolePluginStart } from '@kbn/console-plugin/public';
import type { AppMountParameters, CoreStart } from '@kbn/core/public';
import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
import type {
UsageCollectionSetup,
UsageCollectionStart,
} from '@kbn/usage-collection-plugin/public';
import type {
MappingProperty,
MappingPropertyBase,
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { IndexManagementPluginStart } from '@kbn/index-management-shared-types';
import type {
IndexManagementPluginSetup,
IndexManagementPluginStart,
} from '@kbn/index-management-shared-types';
import type { AppDeepLinkId } from '@kbn/core-chrome-browser';
import { ServerlessPluginStart } from '@kbn/serverless/public';
import type { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public';
import type { AvailableLanguages } from './code_examples';
export interface SearchIndicesPluginSetup {
enabled: boolean;
@ -31,14 +37,20 @@ export interface SearchIndicesPluginStart {
startRoute: string;
}
export interface AppPluginStartDependencies {
navigation: NavigationPublicPluginStart;
export interface AppPluginSetupDependencies {
console?: ConsolePluginSetup;
cloud?: CloudSetup;
indexManagement: IndexManagementPluginSetup;
share: SharePluginSetup;
serverless?: ServerlessPluginSetup;
usageCollection?: UsageCollectionSetup;
}
export interface SearchIndicesAppPluginStartDependencies {
console?: ConsolePluginStart;
cloud?: CloudStart;
share: SharePluginStart;
serverless?: ServerlessPluginStart;
usageCollection?: UsageCollectionStart;
indexManagement: IndexManagementPluginStart;
}
@ -50,8 +62,6 @@ export interface SearchIndicesServicesContextDeps {
export type SearchIndicesServicesContext = CoreStart &
SearchIndicesAppPluginStartDependencies & {
history: AppMountParameters['history'];
indexManagement: IndexManagementPluginStart;
serverless: ServerlessPluginStart;
};
export interface AppUsageTracker {
@ -123,3 +133,14 @@ export interface IngestDataCodeExamples {
python: IngestDataCodeDefinition;
javascript: IngestDataCodeDefinition;
}
export interface CreateIndexFormState {
indexName: string;
defaultIndexName: string;
codingLanguage: AvailableLanguages;
}
export enum CreateIndexViewMode {
UI = 'ui',
Code = 'code',
}

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { generateRandomIndexName, isValidIndexName } from './indices';
import { generateRandomIndexName, isValidIndexName, getFirstNewIndexName } from './indices';
describe('indices utils', function () {
describe('generateRandomIndexName', function () {
@ -46,4 +46,31 @@ describe('indices utils', function () {
expect(isValidIndexName(indexName)).toBe(true);
});
});
describe('getFirstNewIndexName', function () {
it('returns undefined when lists are the same', () => {
expect(getFirstNewIndexName([], [])).toEqual(undefined);
expect(getFirstNewIndexName(['index'], ['index'])).toEqual(undefined);
expect(getFirstNewIndexName(['index', 'test'], ['index', 'test'])).toEqual(undefined);
});
it('returns new item when it exists', () => {
expect(getFirstNewIndexName([], ['index'])).toEqual('index');
expect(getFirstNewIndexName(['index'], ['index', 'test'])).toEqual('test');
expect(getFirstNewIndexName(['index', 'test'], ['index', 'test', 'unit-test'])).toEqual(
'unit-test'
);
expect(getFirstNewIndexName(['index', 'test'], ['unit-test', 'index', 'test'])).toEqual(
'unit-test'
);
});
it('returns first new item when it multiple new indices exists', () => {
expect(getFirstNewIndexName([], ['index', 'test'])).toEqual('index');
expect(getFirstNewIndexName(['index'], ['test', 'index', 'unit-test'])).toEqual('test');
});
it('can handle old indices being removed', () => {
expect(getFirstNewIndexName(['index'], ['test'])).toEqual('test');
expect(getFirstNewIndexName(['test', 'index', 'unit-test'], ['index', 'new'])).toEqual('new');
});
});
});

View file

@ -35,3 +35,12 @@ export function generateRandomIndexName(
return result;
}
export function getFirstNewIndexName(startingIndexNames: string[], currentIndexNames: string[]) {
for (const index of currentIndexNames) {
if (startingIndexNames.indexOf(index) === -1) {
return index;
}
}
return undefined;
}

View file

@ -12,7 +12,6 @@
],
"kbn_references": [
"@kbn/core",
"@kbn/navigation-plugin",
"@kbn/config-schema",
"@kbn/core-elasticsearch-server",
"@kbn/logging",
@ -39,7 +38,8 @@
"@kbn/search-shared-ui",
"@kbn/deeplinks-search",
"@kbn/core-chrome-browser",
"@kbn/serverless"
"@kbn/serverless",
"@kbn/utility-types"
],
"exclude": [
"target/**/*",

View file

@ -16,7 +16,9 @@ export const CreateIndexButton: React.FC = () => {
services: { application, share },
} = useKibana();
const createIndexLocator = useMemo(
() => share.url.locators.get('CREATE_INDEX_LOCATOR_ID'),
() =>
share.url.locators.get('CREATE_INDEX_LOCATOR_ID') ??
share.url.locators.get('SEARCH_CREATE_INDEX'),
[share.url.locators]
);
const handleNavigateToIndex = useCallback(async () => {

View file

@ -21,45 +21,9 @@ export const navigationTree = (): NavigationTreeDefinition => ({
breadcrumbStatus: 'hidden',
children: [
{
id: 'home',
title: i18n.translate('xpack.serverlessSearch.nav.home', {
defaultMessage: 'Home',
}),
link: 'elasticsearchStart',
spaceBefore: 'm',
},
{
id: 'dev_tools',
title: i18n.translate('xpack.serverlessSearch.nav.devTools', {
defaultMessage: 'Dev Tools',
}),
link: 'dev_tools:console',
getIsActive: ({ pathNameSerialized, prepend }) => {
return pathNameSerialized.startsWith(prepend('/app/dev_tools'));
},
},
{
id: 'kibana',
title: i18n.translate('xpack.serverlessSearch.nav.kibana', {
defaultMessage: 'Kibana',
}),
spaceBefore: 'm',
children: [
{
link: 'discover',
},
{
link: 'dashboards',
getIsActive: ({ pathNameSerialized, prepend }) => {
return pathNameSerialized.startsWith(prepend('/app/dashboards'));
},
},
],
},
{
id: 'content',
title: i18n.translate('xpack.serverlessSearch.nav.content', {
defaultMessage: 'Content',
id: 'data',
title: i18n.translate('xpack.serverlessSearch.nav.data', {
defaultMessage: 'Data',
}),
spaceBefore: 'm',
children: [
@ -75,9 +39,8 @@ export const navigationTree = (): NavigationTreeDefinition => ({
pathNameSerialized.startsWith(
prepend('/app/management/data/index_management/')
) ||
pathNameSerialized.startsWith(
prepend('/app/elasticsearch/indices/index_details/')
)
pathNameSerialized.startsWith(prepend('/app/elasticsearch/indices')) ||
pathNameSerialized.startsWith(prepend('/app/elasticsearch/start'))
);
},
},
@ -94,6 +57,16 @@ export const navigationTree = (): NavigationTreeDefinition => ({
}),
spaceBefore: 'm',
children: [
{
id: 'dev_tools',
title: i18n.translate('xpack.serverlessSearch.nav.devTools', {
defaultMessage: 'Dev Tools',
}),
link: 'dev_tools',
getIsActive: ({ pathNameSerialized, prepend }) => {
return pathNameSerialized.startsWith(prepend('/app/dev_tools'));
},
},
{
id: 'searchPlayground',
title: i18n.translate('xpack.serverlessSearch.nav.build.searchPlayground', {
@ -148,18 +121,19 @@ export const navigationTree = (): NavigationTreeDefinition => ({
spaceBefore: 'm',
children: [{ link: 'maps' }],
},
{
id: 'gettingStarted',
title: i18n.translate('xpack.serverlessSearch.nav.gettingStarted', {
defaultMessage: 'Getting Started',
}),
link: 'serverlessElasticsearch',
spaceBefore: 'm',
},
],
},
],
footer: [
{
id: 'gettingStarted',
type: 'navItem',
title: i18n.translate('xpack.serverlessSearch.nav.gettingStarted', {
defaultMessage: 'Getting Started',
}),
link: 'serverlessElasticsearch',
icon: 'launch',
},
{
type: 'navGroup',
id: 'project_settings_project_nav',
@ -169,12 +143,22 @@ export const navigationTree = (): NavigationTreeDefinition => ({
icon: 'gear',
breadcrumbStatus: 'hidden',
children: [
{
link: 'ml:modelManagement',
title: i18n.translate('xpack.serverlessSearch.nav.trainedModels', {
defaultMessage: 'Trained models',
}),
},
{
link: 'management',
title: i18n.translate('xpack.serverlessSearch.nav.mngt', {
defaultMessage: 'Management',
}),
},
{
id: 'cloudLinkUserAndRoles',
cloudLink: 'userAndRoles',
},
{
id: 'cloudLinkDeployment',
cloudLink: 'deployment',
@ -182,10 +166,6 @@ export const navigationTree = (): NavigationTreeDefinition => ({
defaultMessage: 'Performance',
}),
},
{
id: 'cloudLinkUserAndRoles',
cloudLink: 'userAndRoles',
},
{
id: 'cloudLinkBilling',
cloudLink: 'billingAndSub',

View file

@ -36109,32 +36109,31 @@
"xpack.searchIndices.server.createIndex.errorMessage": "Échec de la création de l'index en raison d'une exception. {errorMessage}",
"xpack.searchIndices.server.deleteDocument.errorMessage": "Impossible de supprimer le document",
"xpack.searchIndices.settingsTabLabel": "Paramètres",
"xpack.searchIndices.startPage.codeView.apiKeyDescription": "Assurez-vous de le conserver dans un endroit sûr. Vous ne pourrez pas le récupérer plus tard.",
"xpack.searchIndices.startPage.codeView.apiKeyTitle": "Copier votre clé d'API",
"xpack.searchIndices.startPage.codeView.explicitGenerate.apiKeyDescription": "Créez une clé d'API pour vous connecter à Elasticsearch.",
"xpack.searchIndices.startPage.codeView.explicitGenerate.apiKeyTitle": "Créer une clé d'API",
"xpack.searchIndices.startPage.createIndex.action.text": "Créer mon index",
"xpack.searchIndices.startPage.createIndex.apiKeyCreation.description": "Nous allons créer une clé d'API pour cet index",
"xpack.searchIndices.startPage.createIndex.description": "Un index stocke vos données et définit le schéma, ou les mappings de champs, pour vos recherches",
"xpack.searchIndices.startPage.createIndex.fileUpload.link": "Charger un fichier",
"xpack.searchIndices.startPage.createIndex.fileUpload.text": "Vous disposez déjà de données ? {link}",
"xpack.searchIndices.startPage.createIndex.name.helpText": "Les noms d'index doivent être en minuscules et ne peuvent contenir que des tirets et des chiffres",
"xpack.searchIndices.startPage.createIndex.name.label": "Nommer votre index",
"xpack.searchIndices.startPage.createIndex.name.placeholder": "Définir un nom pour votre index",
"xpack.searchIndices.startPage.createIndex.permissionTooltip": "Vous ne disposez pas d'autorisation pour créer un index.",
"xpack.searchIndices.shared.codeView.apiKeyDescription": "Assurez-vous de le conserver dans un endroit sûr. Vous ne pourrez pas le récupérer plus tard.",
"xpack.searchIndices.shared.codeView.apiKeyTitle": "Copier votre clé d'API",
"xpack.searchIndices.shared.codeView.explicitGenerate.apiKeyDescription": "Créez une clé d'API pour vous connecter à Elasticsearch.",
"xpack.searchIndices.shared.codeView.explicitGenerate.apiKeyTitle": "Créer une clé d'API",
"xpack.searchIndices.shared.createIndex.action.text": "Créer mon index",
"xpack.searchIndices.shared.createIndex.apiKeyCreation.description": "Nous allons créer une clé d'API pour cet index",
"xpack.searchIndices.shared.createIndex.description": "Un index stocke vos données et définit le schéma, ou les mappings de champs, pour vos recherches",
"xpack.searchIndices.shared.createIndex.fileUpload.link": "Charger un fichier",
"xpack.searchIndices.shared.createIndex.fileUpload.text": "Vous disposez déjà de données ? {link}",
"xpack.searchIndices.shared.createIndex.name.helpText": "Les noms d'index doivent être en minuscules et ne peuvent contenir que des tirets et des chiffres",
"xpack.searchIndices.shared.createIndex.name.label": "Nommer votre index",
"xpack.searchIndices.shared.createIndex.name.placeholder": "Définir un nom pour votre index",
"xpack.searchIndices.shared.createIndex.observabilityCallout.logs.button": "Collectez et analysez les logs",
"xpack.searchIndices.shared.createIndex.observabilityCallout.logs.subTitle": "Explorer Logstash et Beats",
"xpack.searchIndices.shared.createIndex.observabilityCallout.o11yTrial.button": "Démarrer un essai d'Observability",
"xpack.searchIndices.shared.createIndex.observabilityCallout.o11yTrial.subTitle": "Puissant monitoring des performances",
"xpack.searchIndices.shared.createIndex.observabilityCallout.title": "Vous cherchez à stocker vos logs ou vos données d'indicateurs ?",
"xpack.searchIndices.shared.createIndex.pageTitle": "Elasticsearch",
"xpack.searchIndices.shared.createIndex.permissionTooltip": "Vous ne disposez pas d'autorisation pour créer un index.",
"xpack.searchIndices.shared.createIndex.viewSelect.code": "Code",
"xpack.searchIndices.shared.createIndex.viewSelect.legend": "Créer une sélection de vue d'index",
"xpack.searchIndices.shared.createIndex.viewSelect.ui": "Interface utilisateur",
"xpack.searchIndices.shared.statusFetchError.title": "Erreur lors du chargement des index",
"xpack.searchIndices.shared.statusFetchError.unknownError": "Erreur inconnue lors de la récupération des index.",
"xpack.searchIndices.startPage.createIndex.title": "Créer votre premier index",
"xpack.searchIndices.startPage.createIndex.viewSelec.legend": "Créer une sélection de vue d'index",
"xpack.searchIndices.startPage.createIndex.viewSelect.code": "Code",
"xpack.searchIndices.startPage.createIndex.viewSelect.ui": "Interface utilisateur",
"xpack.searchIndices.startPage.observabilityCallout.logs.button": "Collectez et analysez les logs",
"xpack.searchIndices.startPage.observabilityCallout.logs.subTitle": "Explorer Logstash et Beats",
"xpack.searchIndices.startPage.observabilityCallout.o11yTrial.button": "Démarrer un essai d'Observability",
"xpack.searchIndices.startPage.observabilityCallout.o11yTrial.subTitle": "Puissant monitoring des performances",
"xpack.searchIndices.startPage.observabilityCallout.title": "Vous cherchez à stocker vos logs ou vos données d'indicateurs ?",
"xpack.searchIndices.startPage.pageDescription": "Vectorisez, recherchez et visualisez vos données",
"xpack.searchIndices.startPage.pageTitle": "Elasticsearch",
"xpack.searchIndices.startPage.statusFetchError.title": "Erreur lors du chargement des index",
"xpack.searchIndices.startPage.statusFetchError.unknownError": "Erreur inconnue lors de la récupération des index.",
"xpack.searchInferenceEndpoints.actions.copyID": "Copier l'identifiant du point de terminaison d'inférence {inferenceId}",
"xpack.searchInferenceEndpoints.actions.copyIDSuccess": "Identifiant du point de terminaison d'inférence {inferenceId} copié",
"xpack.searchInferenceEndpoints.actions.deleteEndpoint": "Supprimer le point de terminaison d'inférence {selectedEndpointName}",
@ -43293,12 +43292,9 @@
"xpack.serverlessSearch.learnMore": "En savoir plus",
"xpack.serverlessSearch.nav.build": "Développer",
"xpack.serverlessSearch.nav.build.searchPlayground": "Playground",
"xpack.serverlessSearch.nav.content": "Contenu",
"xpack.serverlessSearch.nav.content.indices": "Gestion des index",
"xpack.serverlessSearch.nav.devTools": "Outils de développement",
"xpack.serverlessSearch.nav.gettingStarted": "Commencer",
"xpack.serverlessSearch.nav.home": "Accueil",
"xpack.serverlessSearch.nav.kibana": "Kibana",
"xpack.serverlessSearch.nav.mngt": "Gestion",
"xpack.serverlessSearch.nav.performance": "Performances",
"xpack.serverlessSearch.nav.projectSettings": "Paramètres de projet",

View file

@ -36077,32 +36077,31 @@
"xpack.searchIndices.server.createIndex.errorMessage": "例外が発生したため、インデックスを作成できませんでした。{errorMessage}",
"xpack.searchIndices.server.deleteDocument.errorMessage": "ドキュメントを削除できませんでした",
"xpack.searchIndices.settingsTabLabel": "設定",
"xpack.searchIndices.startPage.codeView.apiKeyDescription": "必ず安全に保管してください。後から取得することはできません。",
"xpack.searchIndices.startPage.codeView.apiKeyTitle": "APIキーをコピー",
"xpack.searchIndices.startPage.codeView.explicitGenerate.apiKeyDescription": "Elasticsearchに接続するためのAPIキーを作成します。",
"xpack.searchIndices.startPage.codeView.explicitGenerate.apiKeyTitle": "APIキーを作成する",
"xpack.searchIndices.startPage.createIndex.action.text": "インデックスを作成",
"xpack.searchIndices.startPage.createIndex.apiKeyCreation.description": "このインデックスのAPIキーを作成します",
"xpack.searchIndices.startPage.createIndex.description": "インデックスはデータを格納し、検索のためのスキーマ、つまりフィールドマッピングを定義します。",
"xpack.searchIndices.startPage.createIndex.fileUpload.link": "ファイルをアップロード",
"xpack.searchIndices.startPage.createIndex.fileUpload.text": "すでに一部のデータがありますか?{link}",
"xpack.searchIndices.startPage.createIndex.name.helpText": "インデックス名は小文字で、ハイフンと数字のみを使用する必要があります。",
"xpack.searchIndices.startPage.createIndex.name.label": "インデックスの名前を指定",
"xpack.searchIndices.startPage.createIndex.name.placeholder": "インデックスの名前を入力",
"xpack.searchIndices.startPage.createIndex.permissionTooltip": "APIキーを作成する権限がありません。",
"xpack.searchIndices.shared.codeView.apiKeyDescription": "必ず安全に保管してください。後から取得することはできません。",
"xpack.searchIndices.shared.codeView.apiKeyTitle": "APIキーをコピー",
"xpack.searchIndices.shared.codeView.explicitGenerate.apiKeyDescription": "Elasticsearchに接続するためのAPIキーを作成します。",
"xpack.searchIndices.shared.codeView.explicitGenerate.apiKeyTitle": "APIキーを作成する",
"xpack.searchIndices.shared.createIndex.action.text": "インデックスを作成",
"xpack.searchIndices.shared.createIndex.apiKeyCreation.description": "このインデックスのAPIキーを作成します",
"xpack.searchIndices.shared.createIndex.description": "インデックスはデータを格納し、検索のためのスキーマ、つまりフィールドマッピングを定義します。",
"xpack.searchIndices.shared.createIndex.fileUpload.link": "ファイルをアップロード",
"xpack.searchIndices.shared.createIndex.fileUpload.text": "すでに一部のデータがありますか?{link}",
"xpack.searchIndices.shared.createIndex.name.helpText": "インデックス名は小文字で、ハイフンと数字のみを使用する必要があります。",
"xpack.searchIndices.shared.createIndex.name.label": "インデックスの名前を指定",
"xpack.searchIndices.shared.createIndex.name.placeholder": "インデックスの名前を入力",
"xpack.searchIndices.shared.createIndex.observabilityCallout.logs.button": "ログを収集して分析",
"xpack.searchIndices.shared.createIndex.observabilityCallout.logs.subTitle": "LogstashとBeatsを探索",
"xpack.searchIndices.shared.createIndex.observabilityCallout.o11yTrial.button": "オブザーバビリティの試用を開始",
"xpack.searchIndices.shared.createIndex.observabilityCallout.o11yTrial.subTitle": "強力なパフォーマンス監視",
"xpack.searchIndices.shared.createIndex.observabilityCallout.title": "ログとメトリックデータを格納する方法をお探しですか?",
"xpack.searchIndices.shared.createIndex.pageTitle": "Elasticsearch",
"xpack.searchIndices.shared.createIndex.permissionTooltip": "APIキーを作成する権限がありません。",
"xpack.searchIndices.shared.createIndex.viewSelect.code": "コード",
"xpack.searchIndices.shared.createIndex.viewSelect.legend": "インデックスビュー選択を作成",
"xpack.searchIndices.shared.createIndex.viewSelect.ui": "UI",
"xpack.searchIndices.shared.statusFetchError.title": "インデックスの読み込み中にエラーが発生",
"xpack.searchIndices.shared.statusFetchError.unknownError": "インデックスの取得中の不明なエラー",
"xpack.searchIndices.startPage.createIndex.title": "最初のインデックスを作成",
"xpack.searchIndices.startPage.createIndex.viewSelec.legend": "インデックスビュー選択を作成",
"xpack.searchIndices.startPage.createIndex.viewSelect.code": "コード",
"xpack.searchIndices.startPage.createIndex.viewSelect.ui": "UI",
"xpack.searchIndices.startPage.observabilityCallout.logs.button": "ログを収集して分析",
"xpack.searchIndices.startPage.observabilityCallout.logs.subTitle": "LogstashとBeatsを探索",
"xpack.searchIndices.startPage.observabilityCallout.o11yTrial.button": "オブザーバビリティの試用を開始",
"xpack.searchIndices.startPage.observabilityCallout.o11yTrial.subTitle": "強力なパフォーマンス監視",
"xpack.searchIndices.startPage.observabilityCallout.title": "ログとメトリックデータを格納する方法をお探しですか?",
"xpack.searchIndices.startPage.pageDescription": "データをベクトル化、検索、可視化",
"xpack.searchIndices.startPage.pageTitle": "Elasticsearch",
"xpack.searchIndices.startPage.statusFetchError.title": "インデックスの読み込み中にエラーが発生",
"xpack.searchIndices.startPage.statusFetchError.unknownError": "インデックスの取得中の不明なエラー",
"xpack.searchInferenceEndpoints.actions.copyID": "推論エンドポイント ID {inferenceId}をコピー",
"xpack.searchInferenceEndpoints.actions.copyIDSuccess": "推論エンドポイント ID {inferenceId}がコピーされました",
"xpack.searchInferenceEndpoints.actions.deleteEndpoint": "推論エンドポイント{selectedEndpointName}を削除",
@ -43258,12 +43257,9 @@
"xpack.serverlessSearch.learnMore": "詳細",
"xpack.serverlessSearch.nav.build": "ビルド",
"xpack.serverlessSearch.nav.build.searchPlayground": "Playground",
"xpack.serverlessSearch.nav.content": "コンテンツ",
"xpack.serverlessSearch.nav.content.indices": "インデックス管理",
"xpack.serverlessSearch.nav.devTools": "開発ツール",
"xpack.serverlessSearch.nav.gettingStarted": "はじめに",
"xpack.serverlessSearch.nav.home": "ホーム",
"xpack.serverlessSearch.nav.kibana": "Kibana",
"xpack.serverlessSearch.nav.mngt": "管理",
"xpack.serverlessSearch.nav.performance": "パフォーマンス",
"xpack.serverlessSearch.nav.projectSettings": "プロジェクト設定",

View file

@ -36145,32 +36145,30 @@
"xpack.searchIndices.server.createIndex.errorMessage": "由于出现异常,无法创建索引。{errorMessage}",
"xpack.searchIndices.server.deleteDocument.errorMessage": "无法删除文档",
"xpack.searchIndices.settingsTabLabel": "设置",
"xpack.searchIndices.startPage.codeView.apiKeyDescription": "请确保将其存放在某个安全位置。稍后您将无法对其进行检索。",
"xpack.searchIndices.startPage.codeView.apiKeyTitle": "复制您的 API 密钥",
"xpack.searchIndices.startPage.codeView.explicitGenerate.apiKeyDescription": "创建 API 密钥以连接到 Elasticsearch。",
"xpack.searchIndices.startPage.codeView.explicitGenerate.apiKeyTitle": "创建 API 密钥",
"xpack.searchIndices.startPage.createIndex.action.text": "创建我的索引",
"xpack.searchIndices.startPage.createIndex.apiKeyCreation.description": "我们将为此索引创建 API 密钥",
"xpack.searchIndices.startPage.createIndex.description": "索引存储您的数据并为您的搜索定义架构或字段映射",
"xpack.searchIndices.startPage.createIndex.fileUpload.link": "上传文件",
"xpack.searchIndices.startPage.createIndex.fileUpload.text": "已具有某些数据?{link}",
"xpack.searchIndices.startPage.createIndex.name.helpText": "索引名称必须为小写,并且只能包含连字符和数字",
"xpack.searchIndices.startPage.createIndex.name.label": "命名您的索引",
"xpack.searchIndices.startPage.createIndex.name.placeholder": "输入索引的名称",
"xpack.searchIndices.startPage.createIndex.permissionTooltip": "您无权创建索引。",
"xpack.searchIndices.shared.codeView.apiKeyDescription": "请确保将其存放在某个安全位置。稍后您将无法对其进行检索。",
"xpack.searchIndices.shared.codeView.apiKeyTitle": "复制您的 API 密钥",
"xpack.searchIndices.shared.codeView.explicitGenerate.apiKeyDescription": "创建 API 密钥以连接到 Elasticsearch。",
"xpack.searchIndices.shared.codeView.explicitGenerate.apiKeyTitle": "创建 API 密钥",
"xpack.searchIndices.shared.createIndex.action.text": "创建我的索引",
"xpack.searchIndices.shared.createIndex.apiKeyCreation.description": "我们将为此索引创建 API 密钥",
"xpack.searchIndices.shared.createIndex.description": "索引存储您的数据并为您的搜索定义架构或字段映射",
"xpack.searchIndices.shared.createIndex.fileUpload.link": "上传文件",
"xpack.searchIndices.shared.createIndex.fileUpload.text": "已具有某些数据?{link}",
"xpack.searchIndices.shared.createIndex.name.helpText": "索引名称必须为小写,并且只能包含连字符和数字",
"xpack.searchIndices.shared.createIndex.name.label": "命名您的索引",
"xpack.searchIndices.shared.createIndex.name.placeholder": "输入索引的名称",
"xpack.searchIndices.shared.createIndex.observabilityCallout.logs.button": "收集和分析日志",
"xpack.searchIndices.shared.createIndex.observabilityCallout.logs.subTitle": "浏览 Logstash 和 Beats",
"xpack.searchIndices.shared.createIndex.observabilityCallout.o11yTrial.button": "开始 Observability 试用",
"xpack.searchIndices.shared.createIndex.observabilityCallout.o11yTrial.subTitle": "强大的性能监测",
"xpack.searchIndices.shared.createIndex.observabilityCallout.title": "计划存储您的日志或指标数据?",
"xpack.searchIndices.shared.createIndex.pageTitle": "Elasticsearch",
"xpack.searchIndices.shared.createIndex.permissionTooltip": "您无权创建索引。",
"xpack.searchIndices.shared.createIndex.viewSelect.code": "Code",
"xpack.searchIndices.shared.createIndex.viewSelect.ui": "UI",
"xpack.searchIndices.shared.statusFetchError.title": "加载索引时出错",
"xpack.searchIndices.shared.statusFetchError.unknownError": "提取索引时出现未知错误。",
"xpack.searchIndices.startPage.createIndex.title": "创建您的首个索引",
"xpack.searchIndices.startPage.createIndex.viewSelec.legend": "创建索引视图选择",
"xpack.searchIndices.startPage.createIndex.viewSelect.code": "Code",
"xpack.searchIndices.startPage.createIndex.viewSelect.ui": "UI",
"xpack.searchIndices.startPage.observabilityCallout.logs.button": "收集和分析日志",
"xpack.searchIndices.startPage.observabilityCallout.logs.subTitle": "浏览 Logstash 和 Beats",
"xpack.searchIndices.startPage.observabilityCallout.o11yTrial.button": "开始 Observability 试用",
"xpack.searchIndices.startPage.observabilityCallout.o11yTrial.subTitle": "强大的性能监测",
"xpack.searchIndices.startPage.observabilityCallout.title": "计划存储您的日志或指标数据?",
"xpack.searchIndices.startPage.pageDescription": "向量化、搜索和可视化您的数据",
"xpack.searchIndices.startPage.pageTitle": "Elasticsearch",
"xpack.searchIndices.startPage.statusFetchError.title": "加载索引时出错",
"xpack.searchIndices.startPage.statusFetchError.unknownError": "提取索引时出现未知错误。",
"xpack.searchInferenceEndpoints.actions.copyID": "复制推理终端 ID {inferenceId}",
"xpack.searchInferenceEndpoints.actions.copyIDSuccess": "已复制推理终端 ID {inferenceId}",
"xpack.searchInferenceEndpoints.actions.deleteEndpoint": "删除推理终端 {selectedEndpointName}",
@ -43329,12 +43327,9 @@
"xpack.serverlessSearch.learnMore": "了解详情",
"xpack.serverlessSearch.nav.build": "构建",
"xpack.serverlessSearch.nav.build.searchPlayground": "Playground",
"xpack.serverlessSearch.nav.content": "内容",
"xpack.serverlessSearch.nav.content.indices": "索引管理",
"xpack.serverlessSearch.nav.devTools": "开发工具",
"xpack.serverlessSearch.nav.gettingStarted": "入门",
"xpack.serverlessSearch.nav.home": "主页",
"xpack.serverlessSearch.nav.kibana": "Kibana",
"xpack.serverlessSearch.nav.mngt": "管理",
"xpack.serverlessSearch.nav.performance": "性能",
"xpack.serverlessSearch.nav.projectSettings": "项目设置",

View file

@ -162,13 +162,13 @@ export function IndexManagementPageProvider({ getService }: FtrProviderContext)
},
async clickCreateIndexButton() {
await testSubjects.click('createIndexButton');
await testSubjects.existOrFail('createIndexSaveButton');
},
async setCreateIndexName(value: string) {
await testSubjects.existOrFail('createIndexNameFieldText');
await testSubjects.setValue('createIndexNameFieldText', value);
},
async clickCreateIndexSaveButton() {
await testSubjects.existOrFail('createIndexSaveButton');
await testSubjects.click('createIndexSaveButton');
// Wait for modal to close
await testSubjects.missingOrFail('createIndexSaveButton', {

View file

@ -24,6 +24,7 @@ import { SvlSearchHomePageProvider } from './svl_search_homepage';
import { SvlSearchIndexDetailPageProvider } from './svl_search_index_detail_page';
import { SvlSearchElasticsearchStartPageProvider } from './svl_search_elasticsearch_start_page';
import { SvlApiKeysProvider } from './svl_api_keys';
import { SvlSearchCreateIndexPageProvider } from './svl_search_create_index_page';
export const pageObjects = {
...xpackFunctionalPageObjects,
@ -45,4 +46,5 @@ export const pageObjects = {
svlSearchIndexDetailPage: SvlSearchIndexDetailPageProvider,
svlSearchElasticsearchStartPage: SvlSearchElasticsearchStartPageProvider,
svlApiKeys: SvlApiKeysProvider,
svlSearchCreateIndexPage: SvlSearchCreateIndexPageProvider,
};

View file

@ -0,0 +1,106 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../ftr_provider_context';
export function SvlSearchCreateIndexPageProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const browser = getService('browser');
const retry = getService('retry');
return {
async expectToBeOnCreateIndexPage() {
expect(await browser.getCurrentUrl()).contain('/app/elasticsearch/indices/create');
await testSubjects.existOrFail('elasticsearchCreateIndexPage', { timeout: 2000 });
},
async expectToBeOnIndexDetailsPage() {
await retry.tryForTime(60 * 1000, async () => {
expect(await browser.getCurrentUrl()).contain('/app/elasticsearch/indices/index_details');
});
},
async expectToBeOnIndexListPage() {
await retry.tryForTime(60 * 1000, async () => {
expect(await browser.getCurrentUrl()).contain(
'/app/management/data/index_management/indices'
);
});
},
async expectToBeOnMLFileUploadPage() {
await retry.tryForTime(60 * 1000, async () => {
expect(await browser.getCurrentUrl()).contain('/app/ml/filedatavisualizer');
});
},
async expectIndexNameToExist() {
await testSubjects.existOrFail('indexNameField');
},
async setIndexNameValue(value: string) {
await testSubjects.existOrFail('indexNameField');
await testSubjects.setValue('indexNameField', value);
},
async expectCloseCreateIndexButtonExists() {
await testSubjects.existOrFail('closeCreateIndex');
},
async clickCloseCreateIndexButton() {
await testSubjects.existOrFail('closeCreateIndex');
await testSubjects.click('closeCreateIndex');
},
async expectCreateIndexButtonToExist() {
await testSubjects.existOrFail('createIndexBtn');
},
async expectCreateIndexButtonToBeEnabled() {
await testSubjects.existOrFail('createIndexBtn');
expect(await testSubjects.isEnabled('createIndexBtn')).equal(true);
},
async expectCreateIndexButtonToBeDisabled() {
await testSubjects.existOrFail('createIndexBtn');
expect(await testSubjects.isEnabled('createIndexBtn')).equal(false);
},
async clickCreateIndexButton() {
await testSubjects.existOrFail('createIndexBtn');
expect(await testSubjects.isEnabled('createIndexBtn')).equal(true);
await testSubjects.click('createIndexBtn');
},
async expectCreateIndexCodeView() {
await testSubjects.existOrFail('createIndexCodeView');
},
async expectCreateIndexUIView() {
await testSubjects.existOrFail('createIndexUIView');
},
async clickUIViewButton() {
await testSubjects.existOrFail('createIndexUIViewBtn');
await testSubjects.click('createIndexUIViewBtn');
},
async clickCodeViewButton() {
await testSubjects.existOrFail('createIndexCodeViewBtn');
await testSubjects.click('createIndexCodeViewBtn');
},
async clickFileUploadLink() {
await testSubjects.existOrFail('uploadFileLink');
await testSubjects.click('uploadFileLink');
},
async expectAPIKeyVisibleInCodeBlock(apiKey: string) {
await testSubjects.existOrFail('createIndex-code-block');
await retry.try(async () => {
expect(await testSubjects.getVisibleText('createIndex-code-block')).to.contain(apiKey);
});
},
async expectAPIKeyPreGenerated() {
await testSubjects.existOrFail('apiKeyHasBeenGenerated');
},
async expectAPIKeyNotPreGenerated() {
await testSubjects.existOrFail('apiKeyHasNotBeenGenerated');
},
async expectAPIKeyFormNotAvailable() {
await testSubjects.missingOrFail('apiKeyHasNotBeenGenerated');
await testSubjects.missingOrFail('apiKeyHasBeenGenerated');
},
};
}

View file

@ -42,6 +42,20 @@ export function SvlSearchElasticsearchStartPageProvider({ getService }: FtrProvi
await testSubjects.existOrFail('indexNameField');
await testSubjects.setValue('indexNameField', value);
},
async expectCloseCreateIndexButtonExists() {
await testSubjects.existOrFail('closeCreateIndex');
},
async clickCloseCreateIndexButton() {
await testSubjects.existOrFail('closeCreateIndex');
await testSubjects.click('closeCreateIndex');
},
async expectSkipButtonExists() {
await testSubjects.existOrFail('createIndexSkipBtn');
},
async clickSkipButton() {
await testSubjects.existOrFail('createIndexSkipBtn');
await testSubjects.click('createIndexSkipBtn');
},
async expectCreateIndexButtonToExist() {
await testSubjects.existOrFail('createIndexBtn');
},

View file

@ -14,6 +14,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const security = getService('security');
const testIndexName = `index-ftr-test-${Math.random()}`;
describe('Index Details ', function () {
this.tags(['skipSvlSearch']);
before(async () => {
await security.testUser.setRoles(['index_management_user']);
await pageObjects.svlCommonPage.loginAsAdmin();
@ -34,7 +35,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await pageObjects.indexManagement.expectIndexToExist(testIndexName);
});
describe('can view index details', function () {
this.tags(['skipSvlSearch']);
it('index with no documents', async () => {
await pageObjects.indexManagement.indexDetailsPage.openIndexDetailsPage(0);
await pageObjects.indexManagement.indexDetailsPage.expectIndexDetailsPageIsLoaded();

View file

@ -17,6 +17,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const es = getService('es');
describe('Indices', function () {
this.tags(['skipSvlSearch']);
before(async () => {
await security.testUser.setRoles(['index_management_user']);
await pageObjects.svlCommonPage.loginAsAdmin();
@ -53,7 +54,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
after(async () => {
await esDeleteAllIndices(testIndexName);
});
this.tags('skipSvlSearch');
it('navigates to overview', async () => {
await pageObjects.indexManagement.changeManageIndexTab('showOverviewIndexMenuButton');
await pageObjects.indexManagement.indexDetailsPage.expectIndexDetailsPageIsLoaded();

View file

@ -156,6 +156,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await pageObjects.svlSearchElasticsearchStartPage.expectAnalyzeLogsLink();
await pageObjects.svlSearchElasticsearchStartPage.expectO11yTrialLink();
});
it('should have close button', async () => {
await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnStartPage();
await pageObjects.svlSearchElasticsearchStartPage.expectCloseCreateIndexButtonExists();
await pageObjects.svlSearchElasticsearchStartPage.clickCloseCreateIndexButton();
await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnIndexListPage();
});
it('should have skip button', async () => {
await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnStartPage();
await pageObjects.svlSearchElasticsearchStartPage.expectSkipButtonExists();
await pageObjects.svlSearchElasticsearchStartPage.clickSkipButton();
await pageObjects.svlSearchElasticsearchStartPage.expectToBeOnIndexListPage();
});
});
describe('viewer', function () {
before(async () => {

View file

@ -15,6 +15,7 @@ export default function ({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./elasticsearch_start.ts'));
loadTestFile(require.resolve('./search_index_detail.ts'));
loadTestFile(require.resolve('./getting_started'));
loadTestFile(require.resolve('./index_management'));
loadTestFile(require.resolve('./connectors/connectors_overview'));
loadTestFile(require.resolve('./default_dataview'));
loadTestFile(require.resolve('./pipelines'));

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
import { testHasEmbeddedConsole } from './embedded_console';
@ -16,9 +17,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
'common',
'header',
'indexManagement',
'svlSearchCreateIndexPage',
]);
const browser = getService('browser');
const security = getService('security');
const es = getService('es');
const esDeleteAllIndices = getService('esDeleteAllIndices');
const testIndexName = `test-index-ftr-${Math.random()}`;
const testAPIIndexName = `test-api-index-ftr-${Math.random()}`;
describe('index management', function () {
before(async () => {
await security.testUser.setRoles(['index_management_user']);
@ -29,9 +36,82 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await pageObjects.indexManagement.changeTabs('indicesTab');
await pageObjects.header.waitUntilLoadingHasFinished();
});
after(async () => {
await esDeleteAllIndices([testIndexName, testAPIIndexName]);
});
it('renders the indices tab', async () => {
const url = await browser.getCurrentUrl();
expect(url).to.contain(`/indices`);
});
it('has embedded dev console', async () => {
await testHasEmbeddedConsole(pageObjects);
});
describe('create index', function () {
beforeEach(async () => {
await pageObjects.common.navigateToApp('indexManagement');
// Navigate to the indices tab
await pageObjects.indexManagement.changeTabs('indicesTab');
await pageObjects.header.waitUntilLoadingHasFinished();
});
it('can create an index', async () => {
await pageObjects.indexManagement.clickCreateIndexButton();
await pageObjects.svlSearchCreateIndexPage.expectToBeOnCreateIndexPage();
await pageObjects.svlSearchCreateIndexPage.expectCreateIndexUIView();
await pageObjects.svlSearchCreateIndexPage.expectCreateIndexButtonToBeEnabled();
await pageObjects.svlSearchCreateIndexPage.setIndexNameValue(testIndexName);
await pageObjects.svlSearchCreateIndexPage.clickCreateIndexButton();
await pageObjects.svlSearchCreateIndexPage.expectToBeOnIndexDetailsPage();
await pageObjects.common.navigateToApp('indexManagement');
await pageObjects.indexManagement.changeTabs('indicesTab');
await pageObjects.indexManagement.expectIndexToExist(testIndexName);
});
it('should redirect to index details when index is created via API and on the code view', async () => {
await pageObjects.indexManagement.clickCreateIndexButton();
await pageObjects.svlSearchCreateIndexPage.expectToBeOnCreateIndexPage();
await pageObjects.svlSearchCreateIndexPage.expectCreateIndexUIView();
await pageObjects.svlSearchCreateIndexPage.clickCodeViewButton();
await pageObjects.svlSearchCreateIndexPage.expectCreateIndexCodeView();
await es.indices.create({ index: testAPIIndexName });
await pageObjects.svlSearchCreateIndexPage.expectToBeOnIndexDetailsPage();
});
it('should have file upload link', async () => {
await pageObjects.indexManagement.clickCreateIndexButton();
await pageObjects.svlSearchCreateIndexPage.expectToBeOnCreateIndexPage();
await pageObjects.svlSearchCreateIndexPage.clickFileUploadLink();
await pageObjects.svlSearchCreateIndexPage.expectToBeOnMLFileUploadPage();
});
it('should support closing create index page', async () => {
await pageObjects.indexManagement.clickCreateIndexButton();
await pageObjects.svlSearchCreateIndexPage.expectCloseCreateIndexButtonExists();
await pageObjects.svlSearchCreateIndexPage.clickCloseCreateIndexButton();
await pageObjects.svlSearchCreateIndexPage.expectToBeOnIndexListPage();
});
it('should have the embedded console', async () => {
await pageObjects.indexManagement.clickCreateIndexButton();
await testHasEmbeddedConsole(pageObjects);
});
});
describe('manage index', function () {
beforeEach(async () => {
await pageObjects.common.navigateToApp('indexManagement');
// Navigate to the indices tab
await pageObjects.indexManagement.changeTabs('indicesTab');
await pageObjects.header.waitUntilLoadingHasFinished();
await pageObjects.indexManagement.manageIndex(testIndexName);
await pageObjects.indexManagement.manageIndexContextMenuExists();
});
it('can delete index', async () => {
await pageObjects.indexManagement.confirmDeleteModalIsVisible();
await pageObjects.indexManagement.expectIndexIsDeleted(testIndexName);
});
});
});
}

View file

@ -34,10 +34,11 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
// check side nav links
await solutionNavigation.sidenav.expectSectionExists('search_project_nav');
await solutionNavigation.sidenav.expectLinkActive({
deepLinkId: 'elasticsearchStart',
deepLinkId: 'management:index_management',
});
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Indices' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({
deepLinkId: 'elasticsearchStart',
text: 'Create your first index',
});
await testSubjects.existOrFail(`elasticsearchStartPage`);
@ -58,6 +59,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
});
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Data' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Index Management' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Indices' });
// > Connectors
await solutionNavigation.sidenav.clickLink({
@ -196,9 +198,9 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
// navigate back to serverless search overview
await svlCommonNavigation.clickLogo();
await svlCommonNavigation.sidenav.expectLinkActive({
deepLinkId: 'elasticsearchStart',
deepLinkId: 'management:index_management',
});
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: `Home` });
await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ text: `Indices` });
await testSubjects.existOrFail(`elasticsearchStartPage`);
await expectNoPageReload();
@ -256,7 +258,6 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
it('renders expected side navigation items', async () => {
await solutionNavigation.sidenav.openSection('project_settings_project_nav');
// Verify all expected top-level links exist
await solutionNavigation.sidenav.expectLinkExists({ text: 'Home' });
await solutionNavigation.sidenav.expectLinkExists({ text: 'Data' });
await solutionNavigation.sidenav.expectLinkExists({ text: 'Index Management' });
await solutionNavigation.sidenav.expectLinkExists({ text: 'Connectors' });
@ -281,7 +282,6 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
await solutionNavigation.sidenav.openSection('project_settings_project_nav');
await solutionNavigation.sidenav.expectOnlyDefinedLinks([
'search_project_nav',
'home',
'data',
'management:index_management',
'serverlessConnectors',

View file

@ -69,7 +69,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectPlaygroundHeaderComponentsToExist();
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectPlaygroundHeaderComponentsToDisabled();
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectPlaygroundStartChatPageComponentsToExist();
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectPlaygroundStartChatPageIndexCalloutExists();
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectPlaygroundStartChatPageIndexButtonExists();
});
describe('with gen ai connectors', () => {
@ -106,7 +106,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
});
describe('without any indices', () => {
it('hide no index callout when index added', async () => {
it('hide no create index button when index added', async () => {
await createIndex();
await pageObjects.searchPlayground.PlaygroundStartChatPage.expectOpenFlyoutAndSelectIndex();
});