Skip to content

Commit

Permalink
[ES3][Search] Create Index Page (elastic#199402)
Browse files Browse the repository at this point in the history
## Summary

This PR introduces a Create Index page for the serverless search
solution. This page is almost identical to the new Global Empty State,
but is navigated to via the Create Index button in Index Management. The
index details redirect logic is also slightly different on the Create
Index page, it will only redirect when the "code" view is open and a new
index is created. instead of redirecting from both UI and Code view like
the Global Empty State page does.

With the addition of this page we are also removing the "Home" link from
the serverless search side nav to reduce confusion when the global empty
start redirects to index management when indices exist.

There is also some minor clean-up to ensure both the global empty state
and the new create index pages have proper document titles and
breadcrumbs.

### Screenshots
Updates to Global Empty State:

![image](https://github.com/user-attachments/assets/bb60734e-543d-4481-b121-d52633d462a8)
Create Index Page:
<img width="1320" alt="image"
src="https://github.com/user-attachments/assets/0d095eb6-fda3-4783-83ab-20449b5b31f1">

### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [x] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))

---------

Co-authored-by: kibanamachine <[email protected]>
Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
3 people authored and tkajtoch committed Nov 12, 2024
1 parent a05070f commit d7e9bf2
Show file tree
Hide file tree
Showing 51 changed files with 1,420 additions and 584 deletions.
1 change: 1 addition & 0 deletions packages/deeplinks/search/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
6 changes: 5 additions & 1 deletion packages/deeplinks/search/deep_links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
SEARCH_HOMEPAGE,
SEARCH_INDICES_START,
SEARCH_INDICES,
SEARCH_INDICES_CREATE_INDEX,
SEARCH_ELASTICSEARCH,
SEARCH_VECTOR_SEARCH,
SEARCH_SEMANTIC_SEARCH,
Expand Down Expand Up @@ -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
Expand All @@ -77,4 +80,5 @@ export type DeepLinkId =
| SearchElasticsearch
| SearchVectorSearch
| SearchSemanticSearch
| SearchAISearch;
| SearchAISearch
| `${SearchIndices}:${SearchIndicesLinkId}`;
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -16,11 +17,13 @@ export const NoMatch = ({
filter,
resetFilter,
extensionsService,
share,
}: {
loadIndices: () => void;
filter: string;
resetFilter: () => void;
extensionsService: ExtensionsService;
share?: SharePluginStart;
}) => {
if (filter) {
return (
Expand Down Expand Up @@ -62,7 +65,7 @@ export const NoMatch = ({

if (extensionsService.emptyListContent) {
return extensionsService.emptyListContent.renderContent({
createIndexButton: <CreateIndexButton loadIndices={loadIndices} />,
createIndexButton: <CreateIndexButton loadIndices={loadIndices} share={share} />,
});
}

Expand All @@ -85,7 +88,7 @@ export const NoMatch = ({
/>
</p>
}
actions={<CreateIndexButton loadIndices={loadIndices} />}
actions={<CreateIndexButton loadIndices={loadIndices} share={share} />}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -669,7 +670,7 @@ export class IndexTable extends Component {
</>
)}
<EuiFlexItem grow={false}>
<CreateIndexButton loadIndices={loadIndices} />
<CreateIndexButton loadIndices={loadIndices} share={share} />
</EuiFlexItem>
</EuiFlexGroup>

Expand Down Expand Up @@ -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}
Expand Down
13 changes: 12 additions & 1 deletion x-pack/plugins/search_indices/public/analytics/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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',
}
Original file line number Diff line number Diff line change
@@ -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>
);
};
Original file line number Diff line number Diff line change
@@ -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} />;
};
Original file line number Diff line number Diff line change
@@ -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>
);
};
Loading

0 comments on commit d7e9bf2

Please sign in to comment.