Migrate Index Management to new solutions nav (#101548)
* Migrate index template and component template wizard pages to new nav. * Convert index templates and component templates pages to new nav. * Convert indices and data streams pages to new nav. * Add PageLoading component to es_ui_shared. * Refactor index table component tests. * Add missing error reporting to get all templates API route handler.
This commit is contained in:
parent
e53da4e3eb
commit
5df858aae1
|
@ -13,7 +13,7 @@ import { Error } from '../types';
|
|||
|
||||
interface Props {
|
||||
title: React.ReactNode;
|
||||
error: Error;
|
||||
error?: Error;
|
||||
actions?: JSX.Element;
|
||||
isCentered?: boolean;
|
||||
}
|
||||
|
@ -32,30 +32,30 @@ export const PageError: React.FunctionComponent<Props> = ({
|
|||
isCentered,
|
||||
...rest
|
||||
}) => {
|
||||
const {
|
||||
error: errorString,
|
||||
cause, // wrapEsError() on the server adds a "cause" array
|
||||
message,
|
||||
} = error;
|
||||
const errorString = error?.error;
|
||||
const cause = error?.cause; // wrapEsError() on the server adds a "cause" array
|
||||
const message = error?.message;
|
||||
|
||||
const errorContent = (
|
||||
<EuiPageContent verticalPosition="center" horizontalPosition="center" color="danger">
|
||||
<EuiEmptyPrompt
|
||||
title={<h2>{title}</h2>}
|
||||
body={
|
||||
<>
|
||||
{cause ? message || errorString : <p>{message || errorString}</p>}
|
||||
{cause && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<ul>
|
||||
{cause.map((causeMsg, i) => (
|
||||
<li key={i}>{causeMsg}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
error && (
|
||||
<>
|
||||
{cause ? message || errorString : <p>{message || errorString}</p>}
|
||||
{cause && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<ul>
|
||||
{cause.map((causeMsg, i) => (
|
||||
<li key={i}>{causeMsg}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
iconType="alert"
|
||||
actions={actions}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { PageLoading } from './page_loading';
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiEmptyPrompt, EuiLoadingSpinner, EuiText, EuiPageContent } from '@elastic/eui';
|
||||
|
||||
export const PageLoading: React.FunctionComponent = ({ children }) => {
|
||||
return (
|
||||
<EuiPageContent verticalPosition="center" horizontalPosition="center" color="subdued">
|
||||
<EuiEmptyPrompt
|
||||
title={<EuiLoadingSpinner size="xl" />}
|
||||
body={<EuiText color="subdued">{children}</EuiText>}
|
||||
data-test-subj="sectionLoading"
|
||||
/>
|
||||
</EuiPageContent>
|
||||
);
|
||||
};
|
|
@ -17,6 +17,7 @@ import * as XJson from './xjson';
|
|||
|
||||
export { JsonEditor, OnJsonEditorUpdateHandler, JsonEditorState } from './components/json_editor';
|
||||
|
||||
export { PageLoading } from './components/page_loading';
|
||||
export { SectionLoading } from './components/section_loading';
|
||||
|
||||
export { Frequency, CronEditor } from './components/cron_editor';
|
||||
|
|
|
@ -6,9 +6,12 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import axios from 'axios';
|
||||
import axiosXhrAdapter from 'axios/lib/adapters/xhr';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import sinon from 'sinon';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
import axiosXhrAdapter from 'axios/lib/adapters/xhr';
|
||||
|
||||
/**
|
||||
* The below import is required to avoid a console error warn from brace package
|
||||
|
@ -18,9 +21,9 @@ import { MemoryRouter } from 'react-router-dom';
|
|||
*/
|
||||
import { mountWithIntl, stubWebWorker } from '@kbn/test/jest'; // eslint-disable-line no-unused-vars
|
||||
|
||||
import { BASE_PATH, API_BASE_PATH } from '../../common/constants';
|
||||
import { AppWithoutRouter } from '../../public/application/app';
|
||||
import { AppContextProvider } from '../../public/application/app_context';
|
||||
import { Provider } from 'react-redux';
|
||||
import { loadIndicesSuccess } from '../../public/application/store/actions';
|
||||
import { breadcrumbService } from '../../public/application/services/breadcrumbs';
|
||||
import { UiMetricService } from '../../public/application/services/ui_metric';
|
||||
|
@ -29,10 +32,7 @@ import { httpService } from '../../public/application/services/http';
|
|||
import { setUiMetricService } from '../../public/application/services/api';
|
||||
import { indexManagementStore } from '../../public/application/store';
|
||||
import { setExtensionsService } from '../../public/application/store/selectors/extension_service';
|
||||
import { BASE_PATH, API_BASE_PATH } from '../../common/constants';
|
||||
import { ExtensionsService } from '../../public/services';
|
||||
import sinon from 'sinon';
|
||||
import { findTestSubject } from '@elastic/eui/lib/test';
|
||||
|
||||
/* eslint-disable @kbn/eslint/no-restricted-paths */
|
||||
import { notificationServiceMock } from '../../../../../src/core/public/notifications/notifications_service.mock';
|
||||
|
@ -40,9 +40,9 @@ import { notificationServiceMock } from '../../../../../src/core/public/notifica
|
|||
const mockHttpClient = axios.create({ adapter: axiosXhrAdapter });
|
||||
|
||||
let server = null;
|
||||
|
||||
let store = null;
|
||||
const indices = [];
|
||||
|
||||
for (let i = 0; i < 105; i++) {
|
||||
const baseFake = {
|
||||
health: i % 2 === 0 ? 'green' : 'yellow',
|
||||
|
@ -63,8 +63,12 @@ for (let i = 0; i < 105; i++) {
|
|||
name: `.admin${i}`,
|
||||
});
|
||||
}
|
||||
|
||||
let component = null;
|
||||
|
||||
// Resolve outstanding API requests. See https://www.benmvp.com/blog/asynchronous-testing-with-enzyme-react-jest/
|
||||
const runAllPromises = () => new Promise(setImmediate);
|
||||
|
||||
const status = (rendered, row = 0) => {
|
||||
rendered.update();
|
||||
return findTestSubject(rendered, 'indexTableCell-status')
|
||||
|
@ -76,39 +80,54 @@ const status = (rendered, row = 0) => {
|
|||
const snapshot = (rendered) => {
|
||||
expect(rendered).toMatchSnapshot();
|
||||
};
|
||||
|
||||
const openMenuAndClickButton = (rendered, rowIndex, buttonIndex) => {
|
||||
// Select a row.
|
||||
const checkboxes = findTestSubject(rendered, 'indexTableRowCheckbox');
|
||||
checkboxes.at(rowIndex).simulate('change', { target: { checked: true } });
|
||||
rendered.update();
|
||||
|
||||
// Click the bulk actions button to open the context menu.
|
||||
const actionButton = findTestSubject(rendered, 'indexActionsContextMenuButton');
|
||||
actionButton.simulate('click');
|
||||
rendered.update();
|
||||
|
||||
// Click an action in the context menu.
|
||||
const contextMenuButtons = findTestSubject(rendered, 'indexTableContextMenuButton');
|
||||
contextMenuButtons.at(buttonIndex).simulate('click');
|
||||
rendered.update();
|
||||
};
|
||||
const testEditor = (buttonIndex, rowIndex = 0) => {
|
||||
const rendered = mountWithIntl(component);
|
||||
|
||||
const testEditor = (rendered, buttonIndex, rowIndex = 0) => {
|
||||
openMenuAndClickButton(rendered, rowIndex, buttonIndex);
|
||||
rendered.update();
|
||||
snapshot(findTestSubject(rendered, 'detailPanelTabSelected').text());
|
||||
};
|
||||
const testAction = (buttonIndex, done, rowIndex = 0) => {
|
||||
const rendered = mountWithIntl(component);
|
||||
let count = 0;
|
||||
|
||||
const testAction = (rendered, buttonIndex, rowIndex = 0) => {
|
||||
// This is leaking some implementation details about how Redux works. Not sure exactly what's going on
|
||||
// but it looks like we're aware of how many Redux actions are dispatched in response to user interaction,
|
||||
// so we "time" our assertion based on how many Redux actions we observe. This is brittle because it
|
||||
// depends upon how our UI is architected, which will affect how many actions are dispatched.
|
||||
// Expect this to break when we rearchitect the UI.
|
||||
let dispatchedActionsCount = 0;
|
||||
store.subscribe(() => {
|
||||
if (count > 1) {
|
||||
if (dispatchedActionsCount === 1) {
|
||||
// Take snapshot of final state.
|
||||
snapshot(status(rendered, rowIndex));
|
||||
done();
|
||||
}
|
||||
count++;
|
||||
dispatchedActionsCount++;
|
||||
});
|
||||
expect.assertions(2);
|
||||
|
||||
openMenuAndClickButton(rendered, rowIndex, buttonIndex);
|
||||
// take snapshot of initial state.
|
||||
snapshot(status(rendered, rowIndex));
|
||||
};
|
||||
|
||||
const names = (rendered) => {
|
||||
return findTestSubject(rendered, 'indexTableIndexNameLink');
|
||||
};
|
||||
|
||||
const namesText = (rendered) => {
|
||||
return names(rendered).map((button) => button.text());
|
||||
};
|
||||
|
@ -142,23 +161,28 @@ describe('index table', () => {
|
|||
</MemoryRouter>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
store.dispatch(loadIndicesSuccess({ indices }));
|
||||
server = sinon.fakeServer.create();
|
||||
|
||||
server.respondWith(`${API_BASE_PATH}/indices`, [
|
||||
200,
|
||||
{ 'Content-Type': 'application/json' },
|
||||
JSON.stringify(indices),
|
||||
]);
|
||||
|
||||
server.respondWith([
|
||||
200,
|
||||
{ 'Content-Type': 'application/json' },
|
||||
JSON.stringify({ acknowledged: true }),
|
||||
]);
|
||||
|
||||
server.respondWith(`${API_BASE_PATH}/indices/reload`, [
|
||||
200,
|
||||
{ 'Content-Type': 'application/json' },
|
||||
JSON.stringify(indices),
|
||||
]);
|
||||
|
||||
server.respondImmediately = true;
|
||||
});
|
||||
afterEach(() => {
|
||||
|
@ -168,83 +192,124 @@ describe('index table', () => {
|
|||
server.restore();
|
||||
});
|
||||
|
||||
test('should change pages when a pagination link is clicked on', () => {
|
||||
test('should change pages when a pagination link is clicked on', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
|
||||
snapshot(namesText(rendered));
|
||||
|
||||
const pagingButtons = rendered.find('.euiPaginationButton');
|
||||
pagingButtons.at(2).simulate('click');
|
||||
rendered.update();
|
||||
snapshot(namesText(rendered));
|
||||
});
|
||||
test('should show more when per page value is increased', () => {
|
||||
|
||||
test('should show more when per page value is increased', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
|
||||
const perPageButton = rendered.find('EuiTablePagination EuiPopover').find('button');
|
||||
perPageButton.simulate('click');
|
||||
rendered.update();
|
||||
|
||||
const fiftyButton = rendered.find('.euiContextMenuItem').at(1);
|
||||
fiftyButton.simulate('click');
|
||||
rendered.update();
|
||||
expect(namesText(rendered).length).toBe(50);
|
||||
});
|
||||
test('should show the Actions menu button only when at least one row is selected', () => {
|
||||
|
||||
test('should show the Actions menu button only when at least one row is selected', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
|
||||
let button = findTestSubject(rendered, 'indexTableContextMenuButton');
|
||||
expect(button.length).toEqual(0);
|
||||
|
||||
const checkboxes = findTestSubject(rendered, 'indexTableRowCheckbox');
|
||||
checkboxes.at(0).simulate('change', { target: { checked: true } });
|
||||
rendered.update();
|
||||
button = findTestSubject(rendered, 'indexActionsContextMenuButton');
|
||||
expect(button.length).toEqual(1);
|
||||
});
|
||||
test('should update the Actions menu button text when more than one row is selected', () => {
|
||||
|
||||
test('should update the Actions menu button text when more than one row is selected', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
|
||||
let button = findTestSubject(rendered, 'indexTableContextMenuButton');
|
||||
expect(button.length).toEqual(0);
|
||||
|
||||
const checkboxes = findTestSubject(rendered, 'indexTableRowCheckbox');
|
||||
checkboxes.at(0).simulate('change', { target: { checked: true } });
|
||||
rendered.update();
|
||||
button = findTestSubject(rendered, 'indexActionsContextMenuButton');
|
||||
expect(button.text()).toEqual('Manage index');
|
||||
|
||||
checkboxes.at(1).simulate('change', { target: { checked: true } });
|
||||
rendered.update();
|
||||
button = findTestSubject(rendered, 'indexActionsContextMenuButton');
|
||||
expect(button.text()).toEqual('Manage 2 indices');
|
||||
});
|
||||
test('should show system indices only when the switch is turned on', () => {
|
||||
|
||||
test('should show system indices only when the switch is turned on', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
|
||||
snapshot(rendered.find('.euiPagination li').map((item) => item.text()));
|
||||
const switchControl = rendered.find('.euiSwitch__button');
|
||||
switchControl.simulate('click');
|
||||
snapshot(rendered.find('.euiPagination li').map((item) => item.text()));
|
||||
});
|
||||
test('should filter based on content of search input', () => {
|
||||
|
||||
test('should filter based on content of search input', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
|
||||
const searchInput = rendered.find('.euiFieldSearch').first();
|
||||
searchInput.instance().value = 'testy0';
|
||||
searchInput.simulate('keyup', { key: 'Enter', keyCode: 13, which: 13 });
|
||||
rendered.update();
|
||||
snapshot(namesText(rendered));
|
||||
});
|
||||
test('should sort when header is clicked', () => {
|
||||
|
||||
test('should sort when header is clicked', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
|
||||
const nameHeader = findTestSubject(rendered, 'indexTableHeaderCell-name').find('button');
|
||||
nameHeader.simulate('click');
|
||||
rendered.update();
|
||||
snapshot(namesText(rendered));
|
||||
|
||||
nameHeader.simulate('click');
|
||||
rendered.update();
|
||||
snapshot(namesText(rendered));
|
||||
});
|
||||
test('should open the index detail slideout when the index name is clicked', () => {
|
||||
|
||||
test('should open the index detail slideout when the index name is clicked', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
|
||||
expect(findTestSubject(rendered, 'indexDetailFlyout').length).toBe(0);
|
||||
|
||||
const indexNameLink = names(rendered).at(0);
|
||||
indexNameLink.simulate('click');
|
||||
rendered.update();
|
||||
expect(findTestSubject(rendered, 'indexDetailFlyout').length).toBe(1);
|
||||
});
|
||||
test('should show the right context menu options when one index is selected and open', () => {
|
||||
|
||||
test('should show the right context menu options when one index is selected and open', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
|
||||
const checkboxes = findTestSubject(rendered, 'indexTableRowCheckbox');
|
||||
checkboxes.at(0).simulate('change', { target: { checked: true } });
|
||||
rendered.update();
|
||||
|
@ -253,8 +318,12 @@ describe('index table', () => {
|
|||
rendered.update();
|
||||
snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map((span) => span.text()));
|
||||
});
|
||||
test('should show the right context menu options when one index is selected and closed', () => {
|
||||
|
||||
test('should show the right context menu options when one index is selected and closed', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
|
||||
const checkboxes = findTestSubject(rendered, 'indexTableRowCheckbox');
|
||||
checkboxes.at(1).simulate('change', { target: { checked: true } });
|
||||
rendered.update();
|
||||
|
@ -263,8 +332,12 @@ describe('index table', () => {
|
|||
rendered.update();
|
||||
snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map((span) => span.text()));
|
||||
});
|
||||
test('should show the right context menu options when one open and one closed index is selected', () => {
|
||||
|
||||
test('should show the right context menu options when one open and one closed index is selected', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
|
||||
const checkboxes = findTestSubject(rendered, 'indexTableRowCheckbox');
|
||||
checkboxes.at(0).simulate('change', { target: { checked: true } });
|
||||
checkboxes.at(1).simulate('change', { target: { checked: true } });
|
||||
|
@ -274,8 +347,12 @@ describe('index table', () => {
|
|||
rendered.update();
|
||||
snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map((span) => span.text()));
|
||||
});
|
||||
test('should show the right context menu options when more than one open index is selected', () => {
|
||||
|
||||
test('should show the right context menu options when more than one open index is selected', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
|
||||
const checkboxes = findTestSubject(rendered, 'indexTableRowCheckbox');
|
||||
checkboxes.at(0).simulate('change', { target: { checked: true } });
|
||||
checkboxes.at(2).simulate('change', { target: { checked: true } });
|
||||
|
@ -285,8 +362,12 @@ describe('index table', () => {
|
|||
rendered.update();
|
||||
snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map((span) => span.text()));
|
||||
});
|
||||
test('should show the right context menu options when more than one closed index is selected', () => {
|
||||
|
||||
test('should show the right context menu options when more than one closed index is selected', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
|
||||
const checkboxes = findTestSubject(rendered, 'indexTableRowCheckbox');
|
||||
checkboxes.at(1).simulate('change', { target: { checked: true } });
|
||||
checkboxes.at(3).simulate('change', { target: { checked: true } });
|
||||
|
@ -296,37 +377,57 @@ describe('index table', () => {
|
|||
rendered.update();
|
||||
snapshot(findTestSubject(rendered, 'indexTableContextMenuButton').map((span) => span.text()));
|
||||
});
|
||||
test('flush button works from context menu', (done) => {
|
||||
testAction(8, done);
|
||||
});
|
||||
test('clear cache button works from context menu', (done) => {
|
||||
testAction(7, done);
|
||||
});
|
||||
test('refresh button works from context menu', (done) => {
|
||||
testAction(6, done);
|
||||
});
|
||||
test('force merge button works from context menu', (done) => {
|
||||
|
||||
test('flush button works from context menu', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
testAction(rendered, 8);
|
||||
});
|
||||
|
||||
test('clear cache button works from context menu', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
testAction(rendered, 7);
|
||||
});
|
||||
|
||||
test('refresh button works from context menu', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
testAction(rendered, 6);
|
||||
});
|
||||
|
||||
test('force merge button works from context menu', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
|
||||
const rowIndex = 0;
|
||||
openMenuAndClickButton(rendered, rowIndex, 5);
|
||||
snapshot(status(rendered, rowIndex));
|
||||
expect(rendered.find('.euiModal').length).toBe(1);
|
||||
|
||||
let count = 0;
|
||||
store.subscribe(() => {
|
||||
if (count > 1) {
|
||||
if (count === 1) {
|
||||
snapshot(status(rendered, rowIndex));
|
||||
expect(rendered.find('.euiModal').length).toBe(0);
|
||||
done();
|
||||
}
|
||||
count++;
|
||||
});
|
||||
|
||||
const confirmButton = findTestSubject(rendered, 'confirmModalConfirmButton');
|
||||
confirmButton.simulate('click');
|
||||
snapshot(status(rendered, rowIndex));
|
||||
});
|
||||
// Commenting the following 2 tests as it works in the browser (status changes to "closed" or "open") but the
|
||||
// snapshot say the contrary. Need to be investigated.
|
||||
test('close index button works from context menu', (done) => {
|
||||
|
||||
test('close index button works from context menu', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
|
||||
const modifiedIndices = indices.map((index) => {
|
||||
return {
|
||||
...index,
|
||||
|
@ -339,32 +440,56 @@ describe('index table', () => {
|
|||
{ 'Content-Type': 'application/json' },
|
||||
JSON.stringify(modifiedIndices),
|
||||
]);
|
||||
testAction(4, done);
|
||||
|
||||
testAction(rendered, 4);
|
||||
});
|
||||
test('open index button works from context menu', (done) => {
|
||||
|
||||
test('open index button works from context menu', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
|
||||
const modifiedIndices = indices.map((index) => {
|
||||
return {
|
||||
...index,
|
||||
status: index.name === 'testy1' ? 'open' : index.status,
|
||||
};
|
||||
});
|
||||
|
||||
server.respondWith(`${API_BASE_PATH}/indices/reload`, [
|
||||
200,
|
||||
{ 'Content-Type': 'application/json' },
|
||||
JSON.stringify(modifiedIndices),
|
||||
]);
|
||||
testAction(3, done, 1);
|
||||
|
||||
testAction(rendered, 3, 1);
|
||||
});
|
||||
test('show settings button works from context menu', () => {
|
||||
testEditor(0);
|
||||
|
||||
test('show settings button works from context menu', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
testEditor(rendered, 0);
|
||||
});
|
||||
test('show mappings button works from context menu', () => {
|
||||
testEditor(1);
|
||||
|
||||
test('show mappings button works from context menu', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
testEditor(rendered, 1);
|
||||
});
|
||||
test('show stats button works from context menu', () => {
|
||||
testEditor(2);
|
||||
|
||||
test('show stats button works from context menu', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
testEditor(rendered, 2);
|
||||
});
|
||||
test('edit index button works from context menu', () => {
|
||||
testEditor(3);
|
||||
|
||||
test('edit index button works from context menu', async () => {
|
||||
const rendered = mountWithIntl(component);
|
||||
await runAllPromises();
|
||||
rendered.update();
|
||||
testEditor(rendered, 3);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -165,8 +165,10 @@ describe('<ComponentTemplateList />', () => {
|
|||
const { exists, find } = testBed;
|
||||
|
||||
expect(exists('componentTemplatesLoadError')).toBe(true);
|
||||
// The text here looks weird because the child elements' text values (title and description)
|
||||
// are concatenated when we retrive the error element's text value.
|
||||
expect(find('componentTemplatesLoadError').text()).toContain(
|
||||
'Unable to load component templates. Try again.'
|
||||
'Error loading component templatesInternal server error'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,8 +13,13 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
import { ScopedHistory } from 'kibana/public';
|
||||
import { EuiLink, EuiText, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { attemptToURIDecode } from '../../../../shared_imports';
|
||||
import { SectionLoading, ComponentTemplateDeserialized, GlobalFlyout } from '../shared_imports';
|
||||
import {
|
||||
APP_WRAPPER_CLASS,
|
||||
PageLoading,
|
||||
PageError,
|
||||
attemptToURIDecode,
|
||||
} from '../../../../shared_imports';
|
||||
import { ComponentTemplateDeserialized, GlobalFlyout } from '../shared_imports';
|
||||
import { UIM_COMPONENT_TEMPLATE_LIST_LOAD } from '../constants';
|
||||
import { useComponentTemplatesContext } from '../component_templates_context';
|
||||
import {
|
||||
|
@ -24,7 +29,6 @@ import {
|
|||
} from '../component_template_details';
|
||||
import { EmptyPrompt } from './empty_prompt';
|
||||
import { ComponentTable } from './table';
|
||||
import { LoadError } from './error';
|
||||
import { ComponentTemplatesDeleteModal } from './delete_modal';
|
||||
|
||||
interface Props {
|
||||
|
@ -138,18 +142,20 @@ export const ComponentTemplateList: React.FunctionComponent<Props> = ({
|
|||
}
|
||||
}, [componentTemplateName, removeContentFromGlobalFlyout]);
|
||||
|
||||
let content: React.ReactNode;
|
||||
|
||||
if (isLoading) {
|
||||
content = (
|
||||
<SectionLoading data-test-subj="sectionLoading">
|
||||
return (
|
||||
<PageLoading data-test-subj="sectionLoading">
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.home.componentTemplates.list.loadingMessage"
|
||||
defaultMessage="Loading component templates…"
|
||||
/>
|
||||
</SectionLoading>
|
||||
</PageLoading>
|
||||
);
|
||||
} else if (data?.length) {
|
||||
}
|
||||
|
||||
let content: React.ReactNode;
|
||||
|
||||
if (data?.length) {
|
||||
content = (
|
||||
<>
|
||||
<EuiText color="subdued">
|
||||
|
@ -183,11 +189,22 @@ export const ComponentTemplateList: React.FunctionComponent<Props> = ({
|
|||
} else if (data && data.length === 0) {
|
||||
content = <EmptyPrompt history={history} />;
|
||||
} else if (error) {
|
||||
content = <LoadError onReloadClick={resendRequest} />;
|
||||
content = (
|
||||
<PageError
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.home.componentTemplates.list.loadingErrorMessage"
|
||||
defaultMessage="Error loading component templates"
|
||||
/>
|
||||
}
|
||||
error={error}
|
||||
data-test-subj="componentTemplatesLoadError"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-test-subj="componentTemplateList">
|
||||
<div className={APP_WRAPPER_CLASS} data-test-subj="componentTemplateList">
|
||||
{content}
|
||||
|
||||
{/* delete modal */}
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiLink, EuiCallOut } from '@elastic/eui';
|
||||
|
||||
export interface Props {
|
||||
onReloadClick: () => void;
|
||||
}
|
||||
|
||||
export const LoadError: FunctionComponent<Props> = ({ onReloadClick }) => {
|
||||
return (
|
||||
<EuiCallOut
|
||||
iconType="faceSad"
|
||||
color="danger"
|
||||
data-test-subj="componentTemplatesLoadError"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.home.componentTemplates.list.loadErrorTitle"
|
||||
defaultMessage="Unable to load component templates. {reloadLink}"
|
||||
values={{
|
||||
reloadLink: (
|
||||
<EuiLink onClick={onReloadClick}>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.home.componentTemplates.list.loadErrorReloadLinkLabel"
|
||||
defaultMessage="Try again."
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -9,10 +9,10 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
import {
|
||||
SectionError,
|
||||
PageLoading,
|
||||
PageError,
|
||||
useAuthorizationContext,
|
||||
WithPrivileges,
|
||||
SectionLoading,
|
||||
NotAuthorizedSection,
|
||||
} from '../shared_imports';
|
||||
import { APP_CLUSTER_REQUIRED_PRIVILEGES } from '../constants';
|
||||
|
@ -26,7 +26,7 @@ export const ComponentTemplatesWithPrivileges: FunctionComponent = ({
|
|||
|
||||
if (apiError) {
|
||||
return (
|
||||
<SectionError
|
||||
<PageError
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.home.componentTemplates.checkingPrivilegesErrorMessage"
|
||||
|
@ -45,12 +45,12 @@ export const ComponentTemplatesWithPrivileges: FunctionComponent = ({
|
|||
{({ isLoading, hasPrivileges, privilegesMissing }) => {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<SectionLoading>
|
||||
<PageLoading>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.home.componentTemplates.checkingPrivilegesDescription"
|
||||
defaultMessage="Checking privileges…"
|
||||
/>
|
||||
</SectionLoading>
|
||||
</PageLoading>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import { RouteComponentProps } from 'react-router-dom';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { SectionLoading, attemptToURIDecode } from '../../shared_imports';
|
||||
import { PageLoading, attemptToURIDecode } from '../../shared_imports';
|
||||
import { useComponentTemplatesContext } from '../../component_templates_context';
|
||||
import { ComponentTemplateCreate } from '../component_template_create';
|
||||
|
||||
|
@ -30,7 +30,8 @@ export const ComponentTemplateClone: FunctionComponent<RouteComponentProps<Param
|
|||
|
||||
useEffect(() => {
|
||||
if (error && !isLoading) {
|
||||
toasts.addError(error, {
|
||||
// Toasts expects a generic Error object, which is typed as having a required name property.
|
||||
toasts.addError({ ...error, name: '' } as Error, {
|
||||
title: i18n.translate('xpack.idxMgmt.componentTemplateClone.loadComponentTemplateTitle', {
|
||||
defaultMessage: `Error loading component template '{sourceComponentTemplateName}'.`,
|
||||
values: { sourceComponentTemplateName },
|
||||
|
@ -42,12 +43,12 @@ export const ComponentTemplateClone: FunctionComponent<RouteComponentProps<Param
|
|||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<SectionLoading>
|
||||
<PageLoading>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.componentTemplateEdit.loadingDescription"
|
||||
defaultMessage="Loading component template…"
|
||||
/>
|
||||
</SectionLoading>
|
||||
</PageLoading>
|
||||
);
|
||||
} else {
|
||||
// We still show the create form (unpopulated) even if we were not able to load the
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||
import { EuiPageContentBody, EuiSpacer, EuiPageHeader } from '@elastic/eui';
|
||||
|
||||
import { ComponentTemplateDeserialized } from '../../shared_imports';
|
||||
import { useComponentTemplatesContext } from '../../component_templates_context';
|
||||
|
@ -59,27 +59,28 @@ export const ComponentTemplateCreate: React.FunctionComponent<RouteComponentProp
|
|||
}, [breadcrumbs]);
|
||||
|
||||
return (
|
||||
<EuiPageBody>
|
||||
<EuiPageContent>
|
||||
<EuiTitle size="l">
|
||||
<h1 data-test-subj="pageTitle">
|
||||
<EuiPageContentBody restrictWidth style={{ width: '100%' }}>
|
||||
<EuiPageHeader
|
||||
pageTitle={
|
||||
<span data-test-subj="pageTitle">
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.createComponentTemplate.pageTitle"
|
||||
defaultMessage="Create component template"
|
||||
/>
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
</span>
|
||||
}
|
||||
bottomBorder
|
||||
/>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<ComponentTemplateForm
|
||||
defaultValue={sourceComponentTemplate}
|
||||
onSave={onSave}
|
||||
isSaving={isSaving}
|
||||
saveError={saveError}
|
||||
clearSaveError={clearSaveError}
|
||||
/>
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
<ComponentTemplateForm
|
||||
defaultValue={sourceComponentTemplate}
|
||||
onSave={onSave}
|
||||
isSaving={isSaving}
|
||||
saveError={saveError}
|
||||
clearSaveError={clearSaveError}
|
||||
/>
|
||||
</EuiPageContentBody>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,13 +8,15 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiPageBody, EuiPageContent, EuiTitle, EuiSpacer, EuiCallOut } from '@elastic/eui';
|
||||
import { EuiPageContentBody, EuiPageHeader, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { useComponentTemplatesContext } from '../../component_templates_context';
|
||||
import {
|
||||
ComponentTemplateDeserialized,
|
||||
SectionLoading,
|
||||
PageLoading,
|
||||
PageError,
|
||||
attemptToURIDecode,
|
||||
Error,
|
||||
} from '../../shared_imports';
|
||||
import { ComponentTemplateForm } from '../component_template_form';
|
||||
|
||||
|
@ -65,64 +67,57 @@ export const ComponentTemplateEdit: React.FunctionComponent<RouteComponentProps<
|
|||
setSaveError(null);
|
||||
};
|
||||
|
||||
let content;
|
||||
|
||||
if (isLoading) {
|
||||
content = (
|
||||
<SectionLoading>
|
||||
return (
|
||||
<PageLoading>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.componentTemplateEdit.loadingDescription"
|
||||
defaultMessage="Loading component template…"
|
||||
/>
|
||||
</SectionLoading>
|
||||
</PageLoading>
|
||||
);
|
||||
} else if (error) {
|
||||
content = (
|
||||
<>
|
||||
<EuiCallOut
|
||||
title={
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<PageError
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.componentTemplateEdit.loadComponentTemplateError"
|
||||
defaultMessage="Error loading component template"
|
||||
/>
|
||||
}
|
||||
error={error as Error}
|
||||
data-test-subj="loadComponentTemplateError"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiPageContentBody restrictWidth style={{ width: '100%' }}>
|
||||
<EuiPageHeader
|
||||
pageTitle={
|
||||
<span data-test-subj="pageTitle">
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.componentTemplateEdit.loadComponentTemplateError"
|
||||
defaultMessage="Error loading component template"
|
||||
id="xpack.idxMgmt.componentTemplateEdit.editPageTitle"
|
||||
defaultMessage="Edit component template '{name}'"
|
||||
values={{ name: decodedName }}
|
||||
/>
|
||||
}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
data-test-subj="loadComponentTemplateError"
|
||||
>
|
||||
<div>{error.message}</div>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
);
|
||||
} else if (componentTemplate) {
|
||||
content = (
|
||||
</span>
|
||||
}
|
||||
bottomBorder
|
||||
/>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<ComponentTemplateForm
|
||||
defaultValue={componentTemplate}
|
||||
defaultValue={componentTemplate!}
|
||||
onSave={onSave}
|
||||
isSaving={isSaving}
|
||||
saveError={saveError}
|
||||
clearSaveError={clearSaveError}
|
||||
isEditing={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiPageBody>
|
||||
<EuiPageContent>
|
||||
<EuiTitle size="l">
|
||||
<h1 data-test-subj="pageTitle">
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.componentTemplateEdit.editPageTitle"
|
||||
defaultMessage="Edit component template '{name}'"
|
||||
values={{ name: decodedName }}
|
||||
/>
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="l" />
|
||||
{content}
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
</EuiPageContentBody>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
ComponentTemplateListItem,
|
||||
ComponentTemplateDeserialized,
|
||||
ComponentTemplateSerialized,
|
||||
Error,
|
||||
} from '../shared_imports';
|
||||
import {
|
||||
UIM_COMPONENT_TEMPLATE_DELETE_MANY,
|
||||
|
@ -26,7 +25,7 @@ export const getApi = (
|
|||
trackMetric: (type: UiCounterMetricType, eventName: string) => void
|
||||
) => {
|
||||
function useLoadComponentTemplates() {
|
||||
return useRequest<ComponentTemplateListItem[], Error>({
|
||||
return useRequest<ComponentTemplateListItem[]>({
|
||||
path: `${apiBasePath}/component_templates`,
|
||||
method: 'get',
|
||||
});
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
SendRequestResponse,
|
||||
sendRequest as _sendRequest,
|
||||
useRequest as _useRequest,
|
||||
Error,
|
||||
} from '../shared_imports';
|
||||
|
||||
export type UseRequestHook = <T = any, E = Error>(
|
||||
|
|
|
@ -12,10 +12,12 @@ export {
|
|||
SendRequestResponse,
|
||||
sendRequest,
|
||||
useRequest,
|
||||
SectionLoading,
|
||||
WithPrivileges,
|
||||
AuthorizationProvider,
|
||||
SectionError,
|
||||
SectionLoading,
|
||||
PageLoading,
|
||||
PageError,
|
||||
Error,
|
||||
useAuthorizationContext,
|
||||
NotAuthorizedSection,
|
||||
|
|
|
@ -6,9 +6,7 @@
|
|||
*/
|
||||
|
||||
export { SectionError, Error } from './section_error';
|
||||
export { SectionLoading } from './section_loading';
|
||||
export { NoMatch } from './no_match';
|
||||
export { PageErrorForbidden } from './page_error';
|
||||
export { TemplateDeleteModal } from './template_delete_modal';
|
||||
export { TemplateForm } from './template_form';
|
||||
export { DataHealth } from './data_health';
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { PageErrorForbidden } from './page_error_forbidden';
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { EuiEmptyPrompt, EuiPageContent } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
export function PageErrorForbidden() {
|
||||
return (
|
||||
<EuiPageContent>
|
||||
<EuiEmptyPrompt
|
||||
iconType="securityApp"
|
||||
iconColor={undefined}
|
||||
title={
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.pageErrorForbidden.title"
|
||||
defaultMessage="You do not have permissions to use Index Management"
|
||||
/>
|
||||
</h1>
|
||||
}
|
||||
/>
|
||||
</EuiPageContent>
|
||||
);
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { EuiEmptyPrompt, EuiLoadingSpinner, EuiText } from '@elastic/eui';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const SectionLoading: React.FunctionComponent<Props> = ({ children }) => {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
title={<EuiLoadingSpinner size="xl" />}
|
||||
body={<EuiText color="subdued">{children}</EuiText>}
|
||||
data-test-subj="sectionLoading"
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -8,7 +8,7 @@
|
|||
import React, { useState, useCallback, useRef } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiSpacer, EuiButton } from '@elastic/eui';
|
||||
import { EuiSpacer, EuiButton, EuiPageHeader } from '@elastic/eui';
|
||||
import { ScopedHistory } from 'kibana/public';
|
||||
|
||||
import { TemplateDeserialized } from '../../../../common';
|
||||
|
@ -292,7 +292,7 @@ export const TemplateForm = ({
|
|||
return (
|
||||
<>
|
||||
{/* Form header */}
|
||||
{title}
|
||||
<EuiPageHeader pageTitle={<span data-test-subj="pageTitle">{title}</span>} bottomBorder />
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
|
|
|
@ -24,8 +24,8 @@ import {
|
|||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { reactRouterNavigate } from '../../../../../shared_imports';
|
||||
import { SectionLoading, SectionError, Error, DataHealth } from '../../../../components';
|
||||
import { SectionLoading, reactRouterNavigate } from '../../../../../shared_imports';
|
||||
import { SectionError, Error, DataHealth } from '../../../../components';
|
||||
import { useLoadDataStream } from '../../../../services/api';
|
||||
import { DeleteDataStreamConfirmationModal } from '../delete_data_stream_confirmation_modal';
|
||||
import { humanizeTimeStamp } from '../humanize_time_stamp';
|
||||
|
|
|
@ -16,18 +16,22 @@ import {
|
|||
EuiText,
|
||||
EuiIconTip,
|
||||
EuiSpacer,
|
||||
EuiPageContent,
|
||||
EuiEmptyPrompt,
|
||||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
import { ScopedHistory } from 'kibana/public';
|
||||
|
||||
import {
|
||||
PageLoading,
|
||||
PageError,
|
||||
Error,
|
||||
reactRouterNavigate,
|
||||
extractQueryParams,
|
||||
attemptToURIDecode,
|
||||
APP_WRAPPER_CLASS,
|
||||
} from '../../../../shared_imports';
|
||||
import { useAppContext } from '../../../app_context';
|
||||
import { SectionError, SectionLoading, Error } from '../../../components';
|
||||
import { useLoadDataStreams } from '../../../services/api';
|
||||
import { documentationService } from '../../../services/documentation';
|
||||
import { Section } from '../home';
|
||||
|
@ -166,16 +170,16 @@ export const DataStreamList: React.FunctionComponent<RouteComponentProps<MatchPa
|
|||
|
||||
if (isLoading) {
|
||||
content = (
|
||||
<SectionLoading>
|
||||
<PageLoading>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.dataStreamList.loadingDataStreamsDescription"
|
||||
defaultMessage="Loading data streams…"
|
||||
/>
|
||||
</SectionLoading>
|
||||
</PageLoading>
|
||||
);
|
||||
} else if (error) {
|
||||
content = (
|
||||
<SectionError
|
||||
<PageError
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.dataStreamList.loadingDataStreamsErrorMessage"
|
||||
|
@ -252,10 +256,10 @@ export const DataStreamList: React.FunctionComponent<RouteComponentProps<MatchPa
|
|||
data-test-subj="emptyPrompt"
|
||||
/>
|
||||
);
|
||||
} else if (Array.isArray(dataStreams) && dataStreams.length > 0) {
|
||||
activateHiddenFilter(isSelectedDataStreamHidden(dataStreams, decodedDataStreamName));
|
||||
} else {
|
||||
activateHiddenFilter(isSelectedDataStreamHidden(dataStreams!, decodedDataStreamName));
|
||||
content = (
|
||||
<>
|
||||
<EuiPageContent hasShadow={false} paddingSize="none" data-test-subj="dataStreamList">
|
||||
{renderHeader()}
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
|
@ -270,12 +274,12 @@ export const DataStreamList: React.FunctionComponent<RouteComponentProps<MatchPa
|
|||
history={history as ScopedHistory}
|
||||
includeStats={isIncludeStatsChecked}
|
||||
/>
|
||||
</>
|
||||
</EuiPageContent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-test-subj="dataStreamList">
|
||||
<div className={APP_WRAPPER_CLASS}>
|
||||
{content}
|
||||
|
||||
{/*
|
||||
|
|
|
@ -8,12 +8,13 @@
|
|||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import { APP_WRAPPER_CLASS } from '../../../../shared_imports';
|
||||
import { DetailPanel } from './detail_panel';
|
||||
import { IndexTable } from './index_table';
|
||||
|
||||
export const IndexList: React.FunctionComponent<RouteComponentProps> = ({ history }) => {
|
||||
return (
|
||||
<div className="im-snapshotTestSubject" data-test-subj="indicesList">
|
||||
<div className={`${APP_WRAPPER_CLASS} im-snapshotTestSubject`} data-test-subj="indicesList">
|
||||
<IndexTable history={history} />
|
||||
<DetailPanel />
|
||||
</div>
|
||||
|
|
|
@ -19,7 +19,7 @@ import {
|
|||
EuiCheckbox,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLoadingSpinner,
|
||||
EuiPageContent,
|
||||
EuiScreenReaderOnly,
|
||||
EuiSpacer,
|
||||
EuiSearchBar,
|
||||
|
@ -37,13 +37,18 @@ import {
|
|||
} from '@elastic/eui';
|
||||
|
||||
import { UIM_SHOW_DETAILS_CLICK } from '../../../../../../common/constants';
|
||||
import { reactRouterNavigate, attemptToURIDecode } from '../../../../../shared_imports';
|
||||
import {
|
||||
PageLoading,
|
||||
PageError,
|
||||
reactRouterNavigate,
|
||||
attemptToURIDecode,
|
||||
} from '../../../../../shared_imports';
|
||||
import { REFRESH_RATE_INDEX_LIST } from '../../../../constants';
|
||||
import { getDataStreamDetailsLink } from '../../../../services/routing';
|
||||
import { documentationService } from '../../../../services/documentation';
|
||||
import { AppContextConsumer } from '../../../../app_context';
|
||||
import { renderBadges } from '../../../../lib/render_badges';
|
||||
import { NoMatch, PageErrorForbidden, DataHealth } from '../../../../components';
|
||||
import { NoMatch, DataHealth } from '../../../../components';
|
||||
import { IndexActionsContextMenu } from '../index_actions_context_menu';
|
||||
|
||||
const HEADERS = {
|
||||
|
@ -332,42 +337,6 @@ export class IndexTable extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
renderError() {
|
||||
const { indicesError } = this.props;
|
||||
|
||||
const data = indicesError.body ? indicesError.body : indicesError;
|
||||
|
||||
const { error: errorString, cause, message } = data;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.indexTable.serverErrorTitle"
|
||||
defaultMessage="Error loading indices"
|
||||
/>
|
||||
}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
>
|
||||
<div>{message || errorString}</div>
|
||||
{cause && (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m" />
|
||||
<ul>
|
||||
{cause.map((message, i) => (
|
||||
<li key={i}>{message}</li>
|
||||
))}
|
||||
</ul>
|
||||
</Fragment>
|
||||
)}
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="xl" />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
renderBanners(extensionsService) {
|
||||
const { allIndices = [], filterChanged } = this.props;
|
||||
return extensionsService.banners.map((bannerExtension, i) => {
|
||||
|
@ -470,37 +439,71 @@ export class IndexTable extends Component {
|
|||
} = this.props;
|
||||
|
||||
const { includeHiddenIndices } = this.readURLParams();
|
||||
const hasContent = !indicesLoading && !indicesError;
|
||||
|
||||
let emptyState;
|
||||
if (!hasContent) {
|
||||
const renderNoContent = () => {
|
||||
if (indicesLoading) {
|
||||
return (
|
||||
<PageLoading>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.indexTable.loadingIndicesDescription"
|
||||
defaultMessage="Loading indices…"
|
||||
/>
|
||||
</PageLoading>
|
||||
);
|
||||
}
|
||||
|
||||
if (indicesLoading) {
|
||||
emptyState = (
|
||||
<EuiFlexGroup justifyContent="spaceAround">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="xl" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
if (indicesError) {
|
||||
if (indicesError.status === 403) {
|
||||
return (
|
||||
<PageError
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.pageErrorForbidden.title"
|
||||
defaultMessage="You do not have permissions to use Index Management"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageError
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.indexTable.serverErrorTitle"
|
||||
defaultMessage="Error loading indices"
|
||||
/>
|
||||
}
|
||||
error={indicesError.body}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiPageContent
|
||||
hasShadow={false}
|
||||
paddingSize="none"
|
||||
verticalPosition="center"
|
||||
horizontalPosition="center"
|
||||
>
|
||||
{renderNoContent()}
|
||||
</EuiPageContent>
|
||||
);
|
||||
}
|
||||
|
||||
if (!indicesLoading && !indicesError) {
|
||||
emptyState = <NoMatch />;
|
||||
}
|
||||
|
||||
const { selectedIndicesMap } = this.state;
|
||||
const atLeastOneItemSelected = Object.keys(selectedIndicesMap).length > 0;
|
||||
|
||||
if (indicesError && indicesError.status === 403) {
|
||||
return <PageErrorForbidden />;
|
||||
}
|
||||
|
||||
return (
|
||||
<AppContextConsumer>
|
||||
{({ services }) => {
|
||||
const { extensionsService } = services;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiPageContent hasShadow={false} paddingSize="none">
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={true}>
|
||||
<EuiText color="subdued">
|
||||
|
@ -557,8 +560,6 @@ export class IndexTable extends Component {
|
|||
|
||||
{this.renderBanners(extensionsService)}
|
||||
|
||||
{indicesError && this.renderError()}
|
||||
|
||||
<EuiFlexGroup gutterSize="l" alignItems="center">
|
||||
{atLeastOneItemSelected ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
|
@ -665,13 +666,13 @@ export class IndexTable extends Component {
|
|||
</EuiTable>
|
||||
</div>
|
||||
) : (
|
||||
emptyState
|
||||
<NoMatch />
|
||||
)}
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
{indices.length > 0 ? this.renderPager() : null}
|
||||
</Fragment>
|
||||
</EuiPageContent>
|
||||
);
|
||||
}}
|
||||
</AppContextConsumer>
|
||||
|
|
|
@ -33,8 +33,8 @@ import {
|
|||
UIM_TEMPLATE_DETAIL_PANEL_ALIASES_TAB,
|
||||
UIM_TEMPLATE_DETAIL_PANEL_PREVIEW_TAB,
|
||||
} from '../../../../../../common/constants';
|
||||
import { UseRequestResponse } from '../../../../../shared_imports';
|
||||
import { TemplateDeleteModal, SectionLoading, SectionError, Error } from '../../../../components';
|
||||
import { SectionLoading, UseRequestResponse } from '../../../../../shared_imports';
|
||||
import { TemplateDeleteModal, SectionError, Error } from '../../../../components';
|
||||
import { useLoadIndexTemplate } from '../../../../services/api';
|
||||
import { useServices } from '../../../../app_context';
|
||||
import { TabAliases, TabMappings, TabSettings } from '../../../../components/shared';
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { Fragment, useState, useEffect, useMemo } from 'react';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -24,13 +24,14 @@ import {
|
|||
|
||||
import { UIM_TEMPLATE_LIST_LOAD } from '../../../../../common/constants';
|
||||
import { TemplateListItem } from '../../../../../common';
|
||||
import { attemptToURIDecode } from '../../../../shared_imports';
|
||||
import {
|
||||
SectionError,
|
||||
SectionLoading,
|
||||
Error,
|
||||
LegacyIndexTemplatesDeprecation,
|
||||
} from '../../../components';
|
||||
APP_WRAPPER_CLASS,
|
||||
PageLoading,
|
||||
PageError,
|
||||
attemptToURIDecode,
|
||||
reactRouterNavigate,
|
||||
} from '../../../../shared_imports';
|
||||
import { LegacyIndexTemplatesDeprecation } from '../../../components';
|
||||
import { useLoadIndexTemplates } from '../../../services/api';
|
||||
import { documentationService } from '../../../services/documentation';
|
||||
import { useServices } from '../../../app_context';
|
||||
|
@ -130,7 +131,8 @@ export const TemplateList: React.FunctionComponent<RouteComponentProps<MatchPara
|
|||
};
|
||||
|
||||
const renderHeader = () => (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
// flex-grow: 0 is needed here because the parent element is a flex column and the header would otherwise expand.
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" style={{ flexGrow: 0 }}>
|
||||
<EuiFlexItem grow={true}>
|
||||
<EuiText color="subdued">
|
||||
<FormattedMessage
|
||||
|
@ -218,77 +220,99 @@ export const TemplateList: React.FunctionComponent<RouteComponentProps<MatchPara
|
|||
</>
|
||||
);
|
||||
|
||||
const renderContent = () => {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<SectionLoading>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.indexTemplatesList.loadingIndexTemplatesDescription"
|
||||
defaultMessage="Loading templates…"
|
||||
/>
|
||||
</SectionLoading>
|
||||
);
|
||||
} else if (error) {
|
||||
return (
|
||||
<SectionError
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.indexTemplatesList.loadingIndexTemplatesErrorMessage"
|
||||
defaultMessage="Error loading templates"
|
||||
/>
|
||||
}
|
||||
error={error as Error}
|
||||
/>
|
||||
);
|
||||
} else if (!hasTemplates) {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
iconType="managementApp"
|
||||
title={
|
||||
<h1 data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.indexTemplatesList.emptyPrompt.noIndexTemplatesTitle"
|
||||
defaultMessage="You don't have any templates yet"
|
||||
/>
|
||||
</h1>
|
||||
}
|
||||
data-test-subj="emptyPrompt"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Fragment>
|
||||
{/* Header */}
|
||||
{renderHeader()}
|
||||
|
||||
{/* Composable index templates table */}
|
||||
{renderTemplatesTable()}
|
||||
|
||||
{/* Legacy index templates table. We discourage their adoption if the user isn't already using them. */}
|
||||
{filteredTemplates.legacyTemplates.length > 0 && renderLegacyTemplatesTable()}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Track component loaded
|
||||
// Track this component mounted.
|
||||
useEffect(() => {
|
||||
uiMetricService.trackMetric(METRIC_TYPE.LOADED, UIM_TEMPLATE_LIST_LOAD);
|
||||
}, [uiMetricService]);
|
||||
|
||||
return (
|
||||
<div data-test-subj="templateList">
|
||||
{renderContent()}
|
||||
let content;
|
||||
|
||||
{isTemplateDetailsVisible && (
|
||||
<TemplateDetails
|
||||
template={selectedTemplate!}
|
||||
onClose={closeTemplateDetails}
|
||||
editTemplate={editTemplate}
|
||||
cloneTemplate={cloneTemplate}
|
||||
reload={reload}
|
||||
if (isLoading) {
|
||||
content = (
|
||||
<PageLoading>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.indexTemplatesList.loadingIndexTemplatesDescription"
|
||||
defaultMessage="Loading templates…"
|
||||
/>
|
||||
)}
|
||||
</PageLoading>
|
||||
);
|
||||
} else if (error) {
|
||||
content = (
|
||||
<PageError
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.indexTemplatesList.loadingIndexTemplatesErrorMessage"
|
||||
defaultMessage="Error loading templates"
|
||||
/>
|
||||
}
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
} else if (!hasTemplates) {
|
||||
content = (
|
||||
<EuiEmptyPrompt
|
||||
iconType="managementApp"
|
||||
title={
|
||||
<h1 data-test-subj="title">
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.indexTemplatesList.emptyPrompt.noIndexTemplatesTitle"
|
||||
defaultMessage="Create your first index template"
|
||||
/>
|
||||
</h1>
|
||||
}
|
||||
body={
|
||||
<>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.indexTemplatesList.emptyPrompt.noIndexTemplatesDescription"
|
||||
defaultMessage="An index template automatically applies settings, mappings, and aliases to new indices."
|
||||
/>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
<EuiButton
|
||||
{...reactRouterNavigate(history, '/create_template')}
|
||||
fill
|
||||
iconType="plusInCircle"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.indexTemplatesList.emptyPrompt.createTemplatesButtonLabel"
|
||||
defaultMessage="Create template"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
data-test-subj="emptyPrompt"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<>
|
||||
{/* Header */}
|
||||
{renderHeader()}
|
||||
|
||||
{/* Composable index templates table */}
|
||||
{renderTemplatesTable()}
|
||||
|
||||
{/* Legacy index templates table. We discourage their adoption if the user isn't already using them. */}
|
||||
{filteredTemplates.legacyTemplates.length > 0 && renderLegacyTemplatesTable()}
|
||||
|
||||
{isTemplateDetailsVisible && (
|
||||
<TemplateDetails
|
||||
template={selectedTemplate!}
|
||||
onClose={closeTemplateDetails}
|
||||
editTemplate={editTemplate}
|
||||
cloneTemplate={cloneTemplate}
|
||||
reload={reload}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-test-subj="templateList" className={APP_WRAPPER_CLASS}>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,11 +8,12 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiPageBody, EuiPageContent, EuiTitle } from '@elastic/eui';
|
||||
import { EuiPageContentBody } from '@elastic/eui';
|
||||
import { ScopedHistory } from 'kibana/public';
|
||||
|
||||
import { PageLoading, PageError, Error } from '../../../shared_imports';
|
||||
import { TemplateDeserialized } from '../../../../common';
|
||||
import { TemplateForm, SectionLoading, SectionError, Error } from '../../components';
|
||||
import { TemplateForm } from '../../components';
|
||||
import { breadcrumbService } from '../../services/breadcrumbs';
|
||||
import { getTemplateDetailsLink } from '../../services/routing';
|
||||
import { saveTemplate, useLoadIndexTemplate } from '../../services/api';
|
||||
|
@ -62,24 +63,22 @@ export const TemplateClone: React.FunctionComponent<RouteComponentProps<MatchPar
|
|||
setSaveError(null);
|
||||
};
|
||||
|
||||
let content;
|
||||
|
||||
useEffect(() => {
|
||||
breadcrumbService.setBreadcrumbs('templateClone');
|
||||
}, []);
|
||||
|
||||
if (isLoading) {
|
||||
content = (
|
||||
<SectionLoading>
|
||||
return (
|
||||
<PageLoading>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.templateCreate.loadingTemplateToCloneDescription"
|
||||
defaultMessage="Loading template to clone…"
|
||||
/>
|
||||
</SectionLoading>
|
||||
</PageLoading>
|
||||
);
|
||||
} else if (templateToCloneError) {
|
||||
content = (
|
||||
<SectionError
|
||||
return (
|
||||
<PageError
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.templateCreate.loadingTemplateToCloneErrorMessage"
|
||||
|
@ -90,24 +89,22 @@ export const TemplateClone: React.FunctionComponent<RouteComponentProps<MatchPar
|
|||
data-test-subj="sectionError"
|
||||
/>
|
||||
);
|
||||
} else if (templateToClone) {
|
||||
const templateData = {
|
||||
...templateToClone,
|
||||
name: `${decodedTemplateName}-copy`,
|
||||
} as TemplateDeserialized;
|
||||
}
|
||||
|
||||
content = (
|
||||
const templateData = {
|
||||
...templateToClone,
|
||||
name: `${decodedTemplateName}-copy`,
|
||||
} as TemplateDeserialized;
|
||||
|
||||
return (
|
||||
<EuiPageContentBody restrictWidth style={{ width: '100%' }}>
|
||||
<TemplateForm
|
||||
title={
|
||||
<EuiTitle size="l">
|
||||
<h1 data-test-subj="pageTitle">
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.createTemplate.cloneTemplatePageTitle"
|
||||
defaultMessage="Clone template '{name}'"
|
||||
values={{ name: decodedTemplateName }}
|
||||
/>
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.createTemplate.cloneTemplatePageTitle"
|
||||
defaultMessage="Clone template '{name}'"
|
||||
values={{ name: decodedTemplateName }}
|
||||
/>
|
||||
}
|
||||
defaultValue={templateData}
|
||||
onSave={onSave}
|
||||
|
@ -117,12 +114,6 @@ export const TemplateClone: React.FunctionComponent<RouteComponentProps<MatchPar
|
|||
isLegacy={isLegacy}
|
||||
history={history as ScopedHistory}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiPageBody>
|
||||
<EuiPageContent>{content}</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
</EuiPageContentBody>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiPageBody, EuiPageContent, EuiTitle } from '@elastic/eui';
|
||||
import { EuiPageContentBody } from '@elastic/eui';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { parse } from 'query-string';
|
||||
import { ScopedHistory } from 'kibana/public';
|
||||
|
@ -52,34 +52,28 @@ export const TemplateCreate: React.FunctionComponent<RouteComponentProps> = ({ h
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<EuiPageBody>
|
||||
<EuiPageContent>
|
||||
<TemplateForm
|
||||
title={
|
||||
<EuiTitle size="l">
|
||||
<h1 data-test-subj="pageTitle">
|
||||
{isLegacy ? (
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.createTemplate.createLegacyTemplatePageTitle"
|
||||
defaultMessage="Create legacy template"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.createTemplate.createTemplatePageTitle"
|
||||
defaultMessage="Create template"
|
||||
/>
|
||||
)}
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
}
|
||||
onSave={onSave}
|
||||
isSaving={isSaving}
|
||||
saveError={saveError}
|
||||
clearSaveError={clearSaveError}
|
||||
isLegacy={isLegacy}
|
||||
history={history as ScopedHistory}
|
||||
/>
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
<EuiPageContentBody restrictWidth style={{ width: '100%' }}>
|
||||
<TemplateForm
|
||||
title={
|
||||
isLegacy ? (
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.createTemplate.createLegacyTemplatePageTitle"
|
||||
defaultMessage="Create legacy template"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.createTemplate.createTemplatePageTitle"
|
||||
defaultMessage="Create template"
|
||||
/>
|
||||
)
|
||||
}
|
||||
onSave={onSave}
|
||||
isSaving={isSaving}
|
||||
saveError={saveError}
|
||||
clearSaveError={clearSaveError}
|
||||
isLegacy={isLegacy}
|
||||
history={history as ScopedHistory}
|
||||
/>
|
||||
</EuiPageContentBody>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,16 +7,17 @@
|
|||
|
||||
import React, { useEffect, useState, Fragment } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiPageBody, EuiPageContent, EuiTitle, EuiSpacer, EuiCallOut } from '@elastic/eui';
|
||||
import { EuiPageContentBody, EuiSpacer, EuiCallOut } from '@elastic/eui';
|
||||
import { ScopedHistory } from 'kibana/public';
|
||||
|
||||
import { TemplateDeserialized } from '../../../../common';
|
||||
import { attemptToURIDecode } from '../../../shared_imports';
|
||||
import { PageError, PageLoading, attemptToURIDecode, Error } from '../../../shared_imports';
|
||||
import { breadcrumbService } from '../../services/breadcrumbs';
|
||||
import { useLoadIndexTemplate, updateTemplate } from '../../services/api';
|
||||
import { getTemplateDetailsLink } from '../../services/routing';
|
||||
import { SectionLoading, SectionError, TemplateForm, Error } from '../../components';
|
||||
import { TemplateForm } from '../../components';
|
||||
import { getIsLegacyFromQueryParams } from '../../lib/index_templates';
|
||||
|
||||
interface MatchParams {
|
||||
|
@ -62,27 +63,27 @@ export const TemplateEdit: React.FunctionComponent<RouteComponentProps<MatchPara
|
|||
setSaveError(null);
|
||||
};
|
||||
|
||||
let content;
|
||||
let isSystemTemplate;
|
||||
|
||||
if (isLoading) {
|
||||
content = (
|
||||
<SectionLoading>
|
||||
return (
|
||||
<PageLoading>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.templateEdit.loadingIndexTemplateDescription"
|
||||
defaultMessage="Loading template…"
|
||||
/>
|
||||
</SectionLoading>
|
||||
</PageLoading>
|
||||
);
|
||||
} else if (error) {
|
||||
content = (
|
||||
<SectionError
|
||||
return (
|
||||
<PageError
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.templateEdit.loadingIndexTemplateErrorMessage"
|
||||
defaultMessage="Error loading template"
|
||||
/>
|
||||
}
|
||||
error={error as Error}
|
||||
error={error}
|
||||
data-test-subj="sectionError"
|
||||
/>
|
||||
);
|
||||
|
@ -91,80 +92,75 @@ export const TemplateEdit: React.FunctionComponent<RouteComponentProps<MatchPara
|
|||
name: templateName,
|
||||
_kbnMeta: { type },
|
||||
} = template;
|
||||
const isSystemTemplate = templateName && templateName.startsWith('.');
|
||||
|
||||
isSystemTemplate = templateName && templateName.startsWith('.');
|
||||
|
||||
if (type === 'cloudManaged') {
|
||||
content = (
|
||||
<EuiCallOut
|
||||
return (
|
||||
<PageError
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.templateEdit.managedTemplateWarningTitle"
|
||||
defaultMessage="Editing a managed template is not permitted"
|
||||
/>
|
||||
}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
data-test-subj="systemTemplateEditCallout"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.templateEdit.managedTemplateWarningDescription"
|
||||
defaultMessage="Managed templates are critical for internal operations."
|
||||
/>
|
||||
</EuiCallOut>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<Fragment>
|
||||
{isSystemTemplate && (
|
||||
<Fragment>
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.templateEdit.systemTemplateWarningTitle"
|
||||
defaultMessage="Editing a system template can break Kibana"
|
||||
/>
|
||||
error={
|
||||
{
|
||||
message: i18n.translate(
|
||||
'xpack.idxMgmt.templateEdit.managedTemplateWarningDescription',
|
||||
{
|
||||
defaultMessage: 'Managed templates are critical for internal operations.',
|
||||
}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
data-test-subj="systemTemplateEditCallout"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.templateEdit.systemTemplateWarningDescription"
|
||||
defaultMessage="System templates are critical for internal operations."
|
||||
/>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="l" />
|
||||
</Fragment>
|
||||
)}
|
||||
<TemplateForm
|
||||
title={
|
||||
<EuiTitle size="l">
|
||||
<h1 data-test-subj="pageTitle">
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.editTemplate.editTemplatePageTitle"
|
||||
defaultMessage="Edit template '{name}'"
|
||||
values={{ name: decodedTemplateName }}
|
||||
/>
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
}
|
||||
defaultValue={template}
|
||||
onSave={onSave}
|
||||
isSaving={isSaving}
|
||||
saveError={saveError}
|
||||
clearSaveError={clearSaveError}
|
||||
isEditing={true}
|
||||
isLegacy={isLegacy}
|
||||
history={history as ScopedHistory}
|
||||
/>
|
||||
</Fragment>
|
||||
),
|
||||
} as Error
|
||||
}
|
||||
data-test-subj="systemTemplateEditCallout"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiPageBody>
|
||||
<EuiPageContent>{content}</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
<EuiPageContentBody restrictWidth style={{ width: '100%' }}>
|
||||
{isSystemTemplate && (
|
||||
<Fragment>
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.templateEdit.systemTemplateWarningTitle"
|
||||
defaultMessage="Editing a system template can break Kibana"
|
||||
/>
|
||||
}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
data-test-subj="systemTemplateEditCallout"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.templateEdit.systemTemplateWarningDescription"
|
||||
defaultMessage="System templates are critical for internal operations."
|
||||
/>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="l" />
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
<TemplateForm
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.editTemplate.editTemplatePageTitle"
|
||||
defaultMessage="Edit template '{name}'"
|
||||
values={{ name: decodedTemplateName }}
|
||||
/>
|
||||
}
|
||||
defaultValue={template!}
|
||||
onSave={onSave}
|
||||
isSaving={isSaving}
|
||||
saveError={saveError}
|
||||
clearSaveError={clearSaveError}
|
||||
isEditing={true}
|
||||
isLegacy={isLegacy}
|
||||
history={history as ScopedHistory}
|
||||
/>
|
||||
</EuiPageContentBody>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
UseRequestConfig,
|
||||
sendRequest as _sendRequest,
|
||||
useRequest as _useRequest,
|
||||
Error,
|
||||
} from '../../shared_imports';
|
||||
|
||||
import { httpService } from './http';
|
||||
|
@ -19,6 +20,6 @@ export const sendRequest = (config: SendRequestConfig): Promise<SendRequestRespo
|
|||
return _sendRequest(httpService.httpClient, config);
|
||||
};
|
||||
|
||||
export const useRequest = <T = any>(config: UseRequestConfig) => {
|
||||
return _useRequest<T>(httpService.httpClient, config);
|
||||
export const useRequest = <T = any, E = Error>(config: UseRequestConfig) => {
|
||||
return _useRequest<T, E>(httpService.httpClient, config);
|
||||
};
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { APP_WRAPPER_CLASS } from '../../../../src/core/public';
|
||||
|
||||
export {
|
||||
SendRequestConfig,
|
||||
SendRequestResponse,
|
||||
|
@ -16,6 +18,10 @@ export {
|
|||
extractQueryParams,
|
||||
GlobalFlyout,
|
||||
attemptToURIDecode,
|
||||
PageLoading,
|
||||
PageError,
|
||||
Error,
|
||||
SectionLoading,
|
||||
} from '../../../../src/plugins/es_ui_shared/public';
|
||||
|
||||
export {
|
||||
|
|
|
@ -17,28 +17,40 @@ import { getCloudManagedTemplatePrefix } from '../../../lib/get_managed_template
|
|||
import { RouteDependencies } from '../../../types';
|
||||
import { addBasePath } from '../index';
|
||||
|
||||
export function registerGetAllRoute({ router }: RouteDependencies) {
|
||||
export function registerGetAllRoute({ router, lib: { isEsError } }: RouteDependencies) {
|
||||
router.get({ path: addBasePath('/index_templates'), validate: false }, async (ctx, req, res) => {
|
||||
const { callAsCurrentUser } = ctx.dataManagement!.client;
|
||||
const cloudManagedTemplatePrefix = await getCloudManagedTemplatePrefix(callAsCurrentUser);
|
||||
|
||||
const legacyTemplatesEs = await callAsCurrentUser('indices.getTemplate');
|
||||
const { index_templates: templatesEs } = await callAsCurrentUser(
|
||||
'dataManagement.getComposableIndexTemplates'
|
||||
);
|
||||
try {
|
||||
const cloudManagedTemplatePrefix = await getCloudManagedTemplatePrefix(callAsCurrentUser);
|
||||
|
||||
const legacyTemplates = deserializeLegacyTemplateList(
|
||||
legacyTemplatesEs,
|
||||
cloudManagedTemplatePrefix
|
||||
);
|
||||
const templates = deserializeTemplateList(templatesEs, cloudManagedTemplatePrefix);
|
||||
const legacyTemplatesEs = await callAsCurrentUser('indices.getTemplate');
|
||||
const { index_templates: templatesEs } = await callAsCurrentUser(
|
||||
'dataManagement.getComposableIndexTemplates'
|
||||
);
|
||||
|
||||
const body = {
|
||||
templates,
|
||||
legacyTemplates,
|
||||
};
|
||||
const legacyTemplates = deserializeLegacyTemplateList(
|
||||
legacyTemplatesEs,
|
||||
cloudManagedTemplatePrefix
|
||||
);
|
||||
const templates = deserializeTemplateList(templatesEs, cloudManagedTemplatePrefix);
|
||||
|
||||
return res.ok({ body });
|
||||
const body = {
|
||||
templates,
|
||||
legacyTemplates,
|
||||
};
|
||||
|
||||
return res.ok({ body });
|
||||
} catch (error) {
|
||||
if (isEsError(error)) {
|
||||
return res.customError({
|
||||
statusCode: error.statusCode,
|
||||
body: error,
|
||||
});
|
||||
}
|
||||
// Case: default
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -9795,8 +9795,6 @@
|
|||
"xpack.idxMgmt.home.componentTemplates.emptyPromptDocumentionLink": "詳細情報",
|
||||
"xpack.idxMgmt.home.componentTemplates.emptyPromptTitle": "コンポーネントテンプレートを作成して開始",
|
||||
"xpack.idxMgmt.home.componentTemplates.list.componentTemplatesDescription": "コンポーネントテンプレートを使用して、複数のインデックステンプレートで設定、マッピング、エイリアス構成を再利用します。{learnMoreLink}",
|
||||
"xpack.idxMgmt.home.componentTemplates.list.loadErrorReloadLinkLabel": "再試行してください。",
|
||||
"xpack.idxMgmt.home.componentTemplates.list.loadErrorTitle": "コンポーネントテンプレートを読み込めません。{reloadLink}",
|
||||
"xpack.idxMgmt.home.componentTemplates.list.loadingMessage": "コンポーネントテンプレートを読み込んでいます…",
|
||||
"xpack.idxMgmt.home.componentTemplatesTabTitle": "コンポーネントテンプレート",
|
||||
"xpack.idxMgmt.home.dataStreamsTabTitle": "データストリーム",
|
||||
|
|
|
@ -9902,8 +9902,6 @@
|
|||
"xpack.idxMgmt.home.componentTemplates.emptyPromptDocumentionLink": "了解详情。",
|
||||
"xpack.idxMgmt.home.componentTemplates.emptyPromptTitle": "首先创建组件模板",
|
||||
"xpack.idxMgmt.home.componentTemplates.list.componentTemplatesDescription": "使用组件模板可在多个索引模板中重复使用设置、映射和别名。{learnMoreLink}",
|
||||
"xpack.idxMgmt.home.componentTemplates.list.loadErrorReloadLinkLabel": "请重试。",
|
||||
"xpack.idxMgmt.home.componentTemplates.list.loadErrorTitle": "无法加载组件模板。{reloadLink}",
|
||||
"xpack.idxMgmt.home.componentTemplates.list.loadingMessage": "正在加载组件模板……",
|
||||
"xpack.idxMgmt.home.componentTemplatesTabTitle": "组件模板",
|
||||
"xpack.idxMgmt.home.dataStreamsTabTitle": "数据流",
|
||||
|
|
Loading…
Reference in a new issue