Skip to content

Commit 6502f28

Browse files
authored
[Search] Switch over to V2 index management details (#259866)
## Summary This PR completes the consolidation of the index details pages, and removes all V1 files in favour of V2, as well as the UI setting which controlled the display of V2. Any tests impacted by this change have been updated, and any tests needed for new components have been added. This change will only be visible in a Stateful deployment in a Classic space. A PR to fully deprecate search indices plugin, and use the index management plugin for the index details pages in Stateful/Search solution and Serverless will follow. ### Videos https://github.com/user-attachments/assets/8aaf68d1-2219-48d2-a560-521aa91fdfbf ### Testing Confirm the following: **Index List Page** - [ ] Clicking the `Create index` button brings up the new index modal - [ ] A randomly generated index name is pre-populated in the index name field - [ ] Clicking the `Create my index` button successfully creates an index **Index Details Page** - [ ] You can see the V2 header changes - [ ] The `Back to indices` button is no longer in the header - [ ] There is a `EuiButtonEmpty` labeled `Connection details`, which opens the connection details flyout when clicked - [ ] There is a `EuiButtonEmpty` labeled `Discover index` which opens the index on the Discover page - [ ] The `Manage index` button on the far right of the head - [ ] All header buttons are on the same row as the index title - [ ] The `Storage` and `Status` stats card are rendered - [ ] The `Status` card has an accurate count of the number of documents in the index - [ ] The `Add data to this index` section is rendered - [ ] If viewing an index with documents, the `Data preview` section is rendered - [ ] A maximum of 10 sample documents is shown - [ ] The section description shows an accurate count of the number of sample documents shows ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] ~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/src/platform/packages/shared/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 - [ ] ~If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)~ - [ ] ~This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations.~ - [ ] ~[Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed~ - [ ] ~The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)~ - [ ] ~Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels.~
1 parent 915ce7d commit 6502f28

18 files changed

Lines changed: 786 additions & 991 deletions

File tree

x-pack/platform/plugins/private/translations/translations/de-DE.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18473,8 +18473,6 @@
1847318473
"xpack.idxMgmt.createIndex.modal.indexMode.label": "Indexmodus",
1847418474
"xpack.idxMgmt.createIndex.modal.indexName.label": "Indexname",
1847518475
"xpack.idxMgmt.createIndex.modal.invalidName.error": "Der Indexname ist nicht gültig",
18476-
"xpack.idxMgmt.createIndex.modal.saveButton": "Erstellen",
18477-
"xpack.idxMgmt.createIndex.modal.title": "Index erstellen",
1847818476
"xpack.idxMgmt.createIndex.successfullyCreatedIndexMessage": "Index erfolgreich erstellt: {indexName}",
1847918477
"xpack.idxMgmt.createRoute.duplicateTemplateIdErrorMessage": "Es gibt bereits eine Vorlage mit dem Namen \"{name}“.",
1848018478
"xpack.idxMgmt.createTemplate.cloneTemplatePageTitle": "Klonen Sie die Vorlage „{name}“",

x-pack/platform/plugins/private/translations/translations/fr-FR.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18735,8 +18735,6 @@
1873518735
"xpack.idxMgmt.createIndex.modal.indexMode.label": "Mode d'index",
1873618736
"xpack.idxMgmt.createIndex.modal.indexName.label": "Nom de l'index",
1873718737
"xpack.idxMgmt.createIndex.modal.invalidName.error": "Le nom de l'index n'est pas valide",
18738-
"xpack.idxMgmt.createIndex.modal.saveButton": "Créer",
18739-
"xpack.idxMgmt.createIndex.modal.title": "Créer un index",
1874018738
"xpack.idxMgmt.createIndex.successfullyCreatedIndexMessage": "Création réussie de l'index : {indexName}",
1874118739
"xpack.idxMgmt.createRoute.duplicateTemplateIdErrorMessage": "Un modèle s'appelle déjà \"{name}\".",
1874218740
"xpack.idxMgmt.createTemplate.cloneTemplatePageTitle": "Cloner le modèle \"{name}\"",

x-pack/platform/plugins/private/translations/translations/ja-JP.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18759,8 +18759,6 @@
1875918759
"xpack.idxMgmt.createIndex.modal.indexMode.label": "インデックスモード",
1876018760
"xpack.idxMgmt.createIndex.modal.indexName.label": "インデックス名",
1876118761
"xpack.idxMgmt.createIndex.modal.invalidName.error": "インデックス名が有効ではありません",
18762-
"xpack.idxMgmt.createIndex.modal.saveButton": "作成",
18763-
"xpack.idxMgmt.createIndex.modal.title": "インデックスの作成",
1876418762
"xpack.idxMgmt.createIndex.successfullyCreatedIndexMessage": "インデックスの作成が正常に完了しました:{indexName}",
1876518763
"xpack.idxMgmt.createRoute.duplicateTemplateIdErrorMessage": "''{name}''という名前のテンプレートがすでに存在します。",
1876618764
"xpack.idxMgmt.createTemplate.cloneTemplatePageTitle": "テンプレート''{name}''の複製",

x-pack/platform/plugins/private/translations/translations/zh-CN.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18749,8 +18749,6 @@
1874918749
"xpack.idxMgmt.createIndex.modal.indexMode.label": "索引模式",
1875018750
"xpack.idxMgmt.createIndex.modal.indexName.label": "索引名称",
1875118751
"xpack.idxMgmt.createIndex.modal.invalidName.error": "索引名称无效",
18752-
"xpack.idxMgmt.createIndex.modal.saveButton": "创建",
18753-
"xpack.idxMgmt.createIndex.modal.title": "创建索引",
1875418752
"xpack.idxMgmt.createIndex.successfullyCreatedIndexMessage": "已成功创建索引:{indexName}",
1875518753
"xpack.idxMgmt.createRoute.duplicateTemplateIdErrorMessage": "已有名称为“{name}”的模板。",
1875618754
"xpack.idxMgmt.createTemplate.cloneTemplatePageTitle": "克隆模板“{name}”",

x-pack/platform/plugins/shared/index_management/__jest__/client_integration/helpers/actions/index_table_actions.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,8 @@ export const createCreateIndexActions = () => {
117117
};
118118

119119
const clickCreateIndexSaveButton = async () => {
120-
// `CreateIndexModal` renders the submit button in the modal footer and wires it via `form=...`
121-
// (instead of nesting it inside the <form>). In JSDOM, clicking a submit button triggers
122-
// `requestSubmit`, which throws "Not implemented". We only need the React `onClick` handler,
123-
// so switch the DOM button type to avoid the native submit behavior.
124-
const saveButton = screen.getByTestId('createIndexSaveButton') as HTMLButtonElement;
125-
saveButton.type = 'button';
126-
fireEvent.click(saveButton);
120+
const form = screen.getByTestId('createIndexModalForm');
121+
fireEvent.submit(form);
127122
};
128123

129124
const setIndexName = (name: string) => {

x-pack/platform/plugins/shared/index_management/common/constants/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,4 @@ export const PLUGIN = {
6868
export const MAX_DOCUMENTS_FOR_CONVERT_TO_LOOKUP_INDEX = 2000000000; // 2 billion documents
6969
export const MAX_SHARDS_FOR_CONVERT_TO_LOOKUP_INDEX = 1; // Single shard
7070

71-
export const PLATFORM_INDEX_MGMT_V2 = 'platform:indexManagementV2';
72-
7371
export const DEFAULT_DOCUMENT_PAGE_SIZE = 10;

x-pack/platform/plugins/shared/index_management/public/application/hooks/use_is_platform_index_management_v2_enabled.ts

Lines changed: 0 additions & 14 deletions
This file was deleted.

x-pack/platform/plugins/shared/index_management/public/application/sections/home/index_list/create_index/create_index_button.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ import { EuiButton } from '@elastic/eui';
1212

1313
import useObservable from 'react-use/lib/useObservable';
1414
import { CreateIndexModal } from './create_index_modal';
15-
import { CreateIndexModalV2 } from './create_index_modal_v2';
1615
import { useAppContext } from '../../../../app_context';
17-
import { useIsPlatformIndexManagementV2Enabled } from '../../../../hooks/use_is_platform_index_management_v2_enabled';
1816

1917
export interface CreateIndexButtonProps {
2018
loadIndices: () => void;
@@ -26,12 +24,9 @@ export const CreateIndexButton = ({ loadIndices, share, dataTestSubj }: CreateIn
2624
const {
2725
core: { chrome },
2826
} = useAppContext();
29-
const isPlatformIndexManagementV2Enabled = useIsPlatformIndexManagementV2Enabled();
3027
const [createIndexModalOpen, setCreateIndexModalOpen] = useState<boolean>(false);
3128
const createIndexUrl = share?.url.locators.get('SEARCH_CREATE_INDEX')?.useUrl({});
3229

33-
const IndexModal = isPlatformIndexManagementV2Enabled ? CreateIndexModalV2 : CreateIndexModal;
34-
3530
const activeSolutionId = useObservable(chrome.getActiveSolutionNavId$());
3631

3732
const actionProp =
@@ -55,7 +50,10 @@ export const CreateIndexButton = ({ loadIndices, share, dataTestSubj }: CreateIn
5550
/>
5651
</EuiButton>
5752
{createIndexModalOpen && (
58-
<IndexModal closeModal={() => setCreateIndexModalOpen(false)} loadIndices={loadIndices} />
53+
<CreateIndexModal
54+
closeModal={() => setCreateIndexModalOpen(false)}
55+
loadIndices={loadIndices}
56+
/>
5957
)}
6058
</>
6159
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
10+
import userEvent from '@testing-library/user-event';
11+
import { EuiThemeProvider } from '@elastic/eui';
12+
import { I18nProvider } from '@kbn/i18n-react';
13+
import { CreateIndexModal } from './create_index_modal';
14+
15+
const mockCreateIndex = jest.fn();
16+
jest.mock('../../../../services', () => ({
17+
createIndex: (...args: unknown[]) => mockCreateIndex(...args),
18+
}));
19+
20+
const mockShowSuccessToast = jest.fn();
21+
jest.mock('../../../../services/notification', () => ({
22+
notificationService: {
23+
showSuccessToast: (...args: unknown[]) => mockShowSuccessToast(...args),
24+
},
25+
}));
26+
27+
jest.mock('./utils', () => ({
28+
generateRandomIndexName: () => 'search-abcd',
29+
isValidIndexName: (name: string) => {
30+
if (!name || name !== name.toLowerCase() || name.length === 0) return false;
31+
return true;
32+
},
33+
}));
34+
35+
const renderModal = (props: Partial<React.ComponentProps<typeof CreateIndexModal>> = {}) => {
36+
const defaultProps = {
37+
closeModal: jest.fn(),
38+
loadIndices: jest.fn(),
39+
};
40+
41+
return render(
42+
<I18nProvider>
43+
<EuiThemeProvider>
44+
<CreateIndexModal {...defaultProps} {...props} />
45+
</EuiThemeProvider>
46+
</I18nProvider>
47+
);
48+
};
49+
50+
describe('CreateIndexModal', () => {
51+
beforeEach(() => {
52+
jest.clearAllMocks();
53+
});
54+
55+
it('renders the modal with title and description', () => {
56+
renderModal();
57+
expect(screen.getByText('Create your index')).toBeInTheDocument();
58+
expect(
59+
screen.getByText('An index stores and defines the schema of your data.')
60+
).toBeInTheDocument();
61+
});
62+
63+
it('pre-fills the index name with a generated random name', () => {
64+
renderModal();
65+
const input = screen.getByTestId('createIndexNameFieldText');
66+
expect(input).toHaveValue('search-abcd');
67+
});
68+
69+
it('shows index name and index mode form fields', () => {
70+
renderModal();
71+
expect(screen.getByTestId('createIndexNameFieldText')).toBeInTheDocument();
72+
expect(screen.getByTestId('indexModeField')).toBeInTheDocument();
73+
});
74+
75+
it('shows a validation error for invalid index names', async () => {
76+
renderModal();
77+
const input = screen.getByTestId('createIndexNameFieldText');
78+
await userEvent.clear(input);
79+
await userEvent.type(input, 'INVALID');
80+
81+
expect(screen.getByText('Index name is not valid')).toBeInTheDocument();
82+
});
83+
84+
it('clears validation error when a valid name is entered', async () => {
85+
renderModal();
86+
const input = screen.getByTestId('createIndexNameFieldText');
87+
88+
await userEvent.clear(input);
89+
await userEvent.type(input, 'INVALID');
90+
expect(screen.getByText('Index name is not valid')).toBeInTheDocument();
91+
92+
await userEvent.clear(input);
93+
await userEvent.type(input, 'valid-name');
94+
expect(screen.queryByText('Index name is not valid')).not.toBeInTheDocument();
95+
});
96+
97+
it('calls closeModal when cancel button is clicked', () => {
98+
const closeModal = jest.fn();
99+
renderModal({ closeModal });
100+
fireEvent.click(screen.getByTestId('createIndexCancelButton'));
101+
expect(closeModal).toHaveBeenCalled();
102+
});
103+
104+
it('toggles the API code block when Show/Hide API button is clicked', () => {
105+
renderModal();
106+
expect(screen.getByText('Show API')).toBeInTheDocument();
107+
108+
fireEvent.click(screen.getByTestId('createIndexShowApiButton'));
109+
expect(screen.getByText('Hide API')).toBeInTheDocument();
110+
expect(screen.getByText(/PUT search-abcd/)).toBeInTheDocument();
111+
112+
fireEvent.click(screen.getByTestId('createIndexShowApiButton'));
113+
expect(screen.getByText('Show API')).toBeInTheDocument();
114+
});
115+
116+
it('reflects the selected index mode in the API code block', async () => {
117+
renderModal();
118+
fireEvent.click(screen.getByTestId('createIndexShowApiButton'));
119+
120+
expect(screen.getByText(/\"mode\":\"standard\"/)).toBeInTheDocument();
121+
});
122+
123+
it('creates the index on submit with valid name', async () => {
124+
const closeModal = jest.fn();
125+
const loadIndices = jest.fn();
126+
mockCreateIndex.mockResolvedValue({ error: undefined });
127+
128+
renderModal({ closeModal, loadIndices });
129+
fireEvent.click(screen.getByTestId('createIndexSaveButton'));
130+
131+
await waitFor(() => {
132+
expect(mockCreateIndex).toHaveBeenCalledWith('search-abcd', 'standard');
133+
});
134+
135+
await waitFor(() => {
136+
expect(mockShowSuccessToast).toHaveBeenCalled();
137+
expect(closeModal).toHaveBeenCalled();
138+
expect(loadIndices).toHaveBeenCalled();
139+
});
140+
});
141+
142+
it('creates the index when pressing Enter in the index name input', async () => {
143+
const closeModal = jest.fn();
144+
const loadIndices = jest.fn();
145+
mockCreateIndex.mockResolvedValue({ error: undefined });
146+
147+
renderModal({ closeModal, loadIndices });
148+
fireEvent.submit(screen.getByTestId('createIndexModalForm'));
149+
150+
await waitFor(() => {
151+
expect(mockCreateIndex).toHaveBeenCalledWith('search-abcd', 'standard');
152+
});
153+
154+
await waitFor(() => {
155+
expect(mockShowSuccessToast).toHaveBeenCalled();
156+
expect(closeModal).toHaveBeenCalled();
157+
expect(loadIndices).toHaveBeenCalled();
158+
});
159+
});
160+
161+
it('displays an error callout when index creation fails', async () => {
162+
mockCreateIndex.mockResolvedValue({ error: { message: 'Index already exists' } });
163+
164+
renderModal();
165+
fireEvent.click(screen.getByTestId('createIndexSaveButton'));
166+
167+
await waitFor(() => {
168+
expect(screen.getByText('Error creating index')).toBeInTheDocument();
169+
expect(screen.getByText(/Index already exists/)).toBeInTheDocument();
170+
});
171+
});
172+
173+
it('does not submit when the index name is invalid', async () => {
174+
renderModal();
175+
const input = screen.getByTestId('createIndexNameFieldText');
176+
177+
await userEvent.clear(input);
178+
179+
fireEvent.click(screen.getByTestId('createIndexSaveButton'));
180+
181+
expect(mockCreateIndex).not.toHaveBeenCalled();
182+
});
183+
});

0 commit comments

Comments
 (0)