[Component template] Details flyout (#68732)
This commit is contained in:
parent
ca9a1626a2
commit
a34a3a7e09
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import { setupEnvironment, pageHelpers } from './helpers';
|
||||
import { ComponentTemplateDetailsTestBed } from './helpers/component_template_details.helpers';
|
||||
import { ComponentTemplateDeserialized } from '../../shared_imports';
|
||||
|
||||
const { setup } = pageHelpers.componentTemplateDetails;
|
||||
|
||||
jest.mock('ui/i18n', () => {
|
||||
const I18nContext = ({ children }: any) => children;
|
||||
return { I18nContext };
|
||||
});
|
||||
|
||||
const COMPONENT_TEMPLATE: ComponentTemplateDeserialized = {
|
||||
name: 'comp-1',
|
||||
template: {
|
||||
mappings: { properties: { ip_address: { type: 'ip' } } },
|
||||
aliases: { mydata: {} },
|
||||
settings: { number_of_shards: 1 },
|
||||
},
|
||||
version: 1,
|
||||
_meta: { description: 'component template test' },
|
||||
_kbnMeta: { usedBy: ['template_1'] },
|
||||
};
|
||||
|
||||
const COMPONENT_TEMPLATE_ONLY_REQUIRED_FIELDS: ComponentTemplateDeserialized = {
|
||||
name: 'comp-base',
|
||||
template: {},
|
||||
_kbnMeta: { usedBy: [] },
|
||||
};
|
||||
|
||||
describe('<ComponentTemplateDetails />', () => {
|
||||
const { server, httpRequestsMockHelpers } = setupEnvironment();
|
||||
let testBed: ComponentTemplateDetailsTestBed;
|
||||
|
||||
afterAll(() => {
|
||||
server.restore();
|
||||
});
|
||||
|
||||
describe('With component template details', () => {
|
||||
beforeEach(async () => {
|
||||
httpRequestsMockHelpers.setLoadComponentTemplateResponse(COMPONENT_TEMPLATE);
|
||||
|
||||
await act(async () => {
|
||||
testBed = setup({
|
||||
componentTemplateName: COMPONENT_TEMPLATE.name,
|
||||
onClose: () => {},
|
||||
});
|
||||
});
|
||||
|
||||
testBed.component.update();
|
||||
});
|
||||
|
||||
test('renders the details flyout', () => {
|
||||
const { exists, find, actions, component } = testBed;
|
||||
|
||||
// Verify flyout exists with correct title
|
||||
expect(exists('componentTemplateDetails')).toBe(true);
|
||||
expect(find('componentTemplateDetails.title').text()).toBe(COMPONENT_TEMPLATE.name);
|
||||
|
||||
// Verify footer does not display since "actions" prop was not provided
|
||||
expect(exists('componentTemplateDetails.footer')).toBe(false);
|
||||
|
||||
// Verify tabs exist
|
||||
expect(exists('settingsTab')).toBe(true);
|
||||
expect(exists('mappingsTab')).toBe(true);
|
||||
expect(exists('aliasesTab')).toBe(true);
|
||||
// Summary tab should be active by default
|
||||
expect(find('summaryTab').props()['aria-selected']).toBe(true);
|
||||
|
||||
// [Summary tab] Verify description list items
|
||||
expect(exists('summaryTabContent.usedByTitle')).toBe(true);
|
||||
expect(exists('summaryTabContent.versionTitle')).toBe(true);
|
||||
expect(exists('summaryTabContent.metaTitle')).toBe(true);
|
||||
|
||||
// [Settings tab] Navigate to tab and verify content
|
||||
act(() => {
|
||||
actions.clickSettingsTab();
|
||||
});
|
||||
|
||||
component.update();
|
||||
|
||||
expect(exists('settingsTabContent')).toBe(true);
|
||||
|
||||
// [Mappings tab] Navigate to tab and verify content
|
||||
act(() => {
|
||||
actions.clickMappingsTab();
|
||||
});
|
||||
|
||||
component.update();
|
||||
expect(exists('mappingsTabContent')).toBe(true);
|
||||
|
||||
// [Aliases tab] Navigate to tab and verify content
|
||||
act(() => {
|
||||
actions.clickAliasesTab();
|
||||
});
|
||||
|
||||
component.update();
|
||||
expect(exists('aliasesTabContent')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('With only required component template fields', () => {
|
||||
beforeEach(async () => {
|
||||
httpRequestsMockHelpers.setLoadComponentTemplateResponse(
|
||||
COMPONENT_TEMPLATE_ONLY_REQUIRED_FIELDS
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
testBed = setup({
|
||||
componentTemplateName: COMPONENT_TEMPLATE_ONLY_REQUIRED_FIELDS.name,
|
||||
onClose: () => {},
|
||||
});
|
||||
});
|
||||
|
||||
testBed.component.update();
|
||||
});
|
||||
|
||||
test('renders the details flyout', () => {
|
||||
const { exists, actions, component } = testBed;
|
||||
|
||||
// [Summary tab] Verify optional description list items do not display
|
||||
expect(exists('summaryTabContent.usedByTitle')).toBe(false);
|
||||
expect(exists('summaryTabContent.versionTitle')).toBe(false);
|
||||
expect(exists('summaryTabContent.metaTitle')).toBe(false);
|
||||
// Verify callout renders indicating the component template is not in use
|
||||
expect(exists('notInUseCallout')).toBe(true);
|
||||
|
||||
// [Settings tab] Navigate to tab and verify info callout
|
||||
act(() => {
|
||||
actions.clickSettingsTab();
|
||||
});
|
||||
|
||||
component.update();
|
||||
|
||||
expect(exists('noSettingsCallout')).toBe(true);
|
||||
|
||||
// [Mappings tab] Navigate to tab and verify info callout
|
||||
act(() => {
|
||||
actions.clickMappingsTab();
|
||||
});
|
||||
|
||||
component.update();
|
||||
expect(exists('noMappingsCallout')).toBe(true);
|
||||
|
||||
// [Aliases tab] Navigate to tab and verify info callout
|
||||
act(() => {
|
||||
actions.clickAliasesTab();
|
||||
});
|
||||
|
||||
component.update();
|
||||
expect(exists('noAliasesCallout')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('With actions', () => {
|
||||
beforeEach(async () => {
|
||||
httpRequestsMockHelpers.setLoadComponentTemplateResponse(COMPONENT_TEMPLATE);
|
||||
|
||||
await act(async () => {
|
||||
testBed = setup({
|
||||
componentTemplateName: COMPONENT_TEMPLATE.name,
|
||||
onClose: () => {},
|
||||
actions: [
|
||||
{
|
||||
name: 'Test',
|
||||
icon: 'info',
|
||||
closePopoverOnClick: true,
|
||||
handleActionClick: () => {},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
testBed.component.update();
|
||||
});
|
||||
|
||||
test('should render a footer with context menu', () => {
|
||||
const { exists, actions, component, find } = testBed;
|
||||
|
||||
// Verify footer exists
|
||||
expect(exists('componentTemplateDetails.footer')).toBe(true);
|
||||
expect(exists('manageComponentTemplateButton')).toBe(true);
|
||||
|
||||
// Click manage button and verify actions
|
||||
act(() => {
|
||||
actions.clickManageButton();
|
||||
});
|
||||
|
||||
component.update();
|
||||
|
||||
expect(exists('manageComponentTemplateContextMenu')).toBe(true);
|
||||
expect(find('manageComponentTemplateContextMenu.action').length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error handling', () => {
|
||||
const error = {
|
||||
status: 500,
|
||||
error: 'Internal server error',
|
||||
message: 'Internal server error',
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
httpRequestsMockHelpers.setLoadComponentTemplateResponse(undefined, { body: error });
|
||||
|
||||
await act(async () => {
|
||||
testBed = setup({
|
||||
componentTemplateName: COMPONENT_TEMPLATE.name,
|
||||
onClose: () => {},
|
||||
});
|
||||
});
|
||||
|
||||
testBed.component.update();
|
||||
});
|
||||
|
||||
test('should render an error message if error fetching pipelines', async () => {
|
||||
const { exists, find } = testBed;
|
||||
|
||||
expect(exists('sectionError')).toBe(true);
|
||||
expect(find('sectionError').text()).toContain('Error loading component template');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -6,10 +6,11 @@
|
|||
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import { ComponentTemplateListItem } from '../../shared_imports';
|
||||
|
||||
import { setupEnvironment, pageHelpers } from './helpers';
|
||||
import { ComponentTemplateListTestBed } from './helpers/component_template_list.helpers';
|
||||
import { API_BASE_PATH } from '../../../../../../common/constants';
|
||||
import { ComponentTemplateListItem } from '../../types';
|
||||
import { API_BASE_PATH } from './helpers/constants';
|
||||
|
||||
const { setup } = pageHelpers.componentTemplateList;
|
||||
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { registerTestBed, TestBed } from '../../../../../../../../../test_utils';
|
||||
import { WithAppDependencies } from './setup_environment';
|
||||
import { ComponentTemplateDetailsFlyout } from '../../../component_template_details';
|
||||
|
||||
export type ComponentTemplateDetailsTestBed = TestBed<ComponentTemplateDetailsTestSubjects> & {
|
||||
actions: ReturnType<typeof createActions>;
|
||||
};
|
||||
|
||||
const createActions = (testBed: TestBed<ComponentTemplateDetailsTestSubjects>) => {
|
||||
const { find } = testBed;
|
||||
|
||||
/**
|
||||
* User Actions
|
||||
*/
|
||||
const clickSettingsTab = () => {
|
||||
find('settingsTab').simulate('click');
|
||||
};
|
||||
|
||||
const clickMappingsTab = () => {
|
||||
find('mappingsTab').simulate('click');
|
||||
};
|
||||
|
||||
const clickAliasesTab = () => {
|
||||
find('aliasesTab').simulate('click');
|
||||
};
|
||||
|
||||
const clickManageButton = () => {
|
||||
find('manageComponentTemplateButton').simulate('click');
|
||||
};
|
||||
|
||||
return {
|
||||
clickSettingsTab,
|
||||
clickAliasesTab,
|
||||
clickMappingsTab,
|
||||
clickManageButton,
|
||||
};
|
||||
};
|
||||
|
||||
export const setup = (props: any): ComponentTemplateDetailsTestBed => {
|
||||
const setupTestBed = registerTestBed<ComponentTemplateDetailsTestSubjects>(
|
||||
WithAppDependencies(ComponentTemplateDetailsFlyout),
|
||||
{
|
||||
memoryRouter: {
|
||||
wrapComponent: false,
|
||||
},
|
||||
defaultProps: props,
|
||||
}
|
||||
);
|
||||
|
||||
const testBed = setupTestBed() as ComponentTemplateDetailsTestBed;
|
||||
|
||||
return {
|
||||
...testBed,
|
||||
actions: createActions(testBed),
|
||||
};
|
||||
};
|
||||
|
||||
export type ComponentTemplateDetailsTestSubjects =
|
||||
| 'componentTemplateDetails'
|
||||
| 'componentTemplateDetails.title'
|
||||
| 'componentTemplateDetails.footer'
|
||||
| 'summaryTab'
|
||||
| 'mappingsTab'
|
||||
| 'settingsTab'
|
||||
| 'aliasesTab'
|
||||
| 'sectionError'
|
||||
| 'summaryTabContent'
|
||||
| 'summaryTabContent.usedByTitle'
|
||||
| 'summaryTabContent.versionTitle'
|
||||
| 'summaryTabContent.metaTitle'
|
||||
| 'notInUseCallout'
|
||||
| 'aliasesTabContent'
|
||||
| 'noAliasesCallout'
|
||||
| 'mappingsTabContent'
|
||||
| 'noMappingsCallout'
|
||||
| 'settingsTabContent'
|
||||
| 'noSettingsCallout'
|
||||
| 'manageComponentTemplateButton'
|
||||
| 'manageComponentTemplateContextMenu'
|
||||
| 'manageComponentTemplateContextMenu.action';
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const API_BASE_PATH = '/api/index_management';
|
|
@ -5,11 +5,15 @@
|
|||
*/
|
||||
|
||||
import sinon, { SinonFakeServer } from 'sinon';
|
||||
import { API_BASE_PATH } from '../../../../../../../common';
|
||||
import { ComponentTemplateListItem, ComponentTemplateDeserialized } from '../../../shared_imports';
|
||||
import { API_BASE_PATH } from './constants';
|
||||
|
||||
// Register helpers to mock HTTP Requests
|
||||
const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
|
||||
const setLoadComponentTemplatesResponse = (response?: any[], error?: any) => {
|
||||
const setLoadComponentTemplatesResponse = (
|
||||
response?: ComponentTemplateListItem[],
|
||||
error?: any
|
||||
) => {
|
||||
const status = error ? error.status || 400 : 200;
|
||||
const body = error ? error.body : response;
|
||||
|
||||
|
@ -20,6 +24,20 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
|
|||
]);
|
||||
};
|
||||
|
||||
const setLoadComponentTemplateResponse = (
|
||||
response?: ComponentTemplateDeserialized,
|
||||
error?: any
|
||||
) => {
|
||||
const status = error ? error.status || 400 : 200;
|
||||
const body = error ? error.body : response;
|
||||
|
||||
server.respondWith('GET', `${API_BASE_PATH}/component_templates/:name`, [
|
||||
status,
|
||||
{ 'Content-Type': 'application/json' },
|
||||
JSON.stringify(body),
|
||||
]);
|
||||
};
|
||||
|
||||
const setDeleteComponentTemplateResponse = (response?: object) => {
|
||||
server.respondWith('DELETE', `${API_BASE_PATH}/component_templates/:name`, [
|
||||
200,
|
||||
|
@ -31,6 +49,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
|
|||
return {
|
||||
setLoadComponentTemplatesResponse,
|
||||
setDeleteComponentTemplateResponse,
|
||||
setLoadComponentTemplateResponse,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { setup as componentTemplatesListSetup } from './component_template_list.helpers';
|
||||
import { setup as componentTemplateDetailsSetup } from './component_template_details.helpers';
|
||||
|
||||
export { nextTick, getRandomString, findTestSubject } from '../../../../../../../../../test_utils';
|
||||
|
||||
|
@ -12,4 +13,5 @@ export { setupEnvironment } from './setup_environment';
|
|||
|
||||
export const pageHelpers = {
|
||||
componentTemplateList: { setup: componentTemplatesListSetup },
|
||||
componentTemplateDetails: { setup: componentTemplateDetailsSetup },
|
||||
};
|
||||
|
|
|
@ -9,21 +9,21 @@ import axios from 'axios';
|
|||
import axiosXhrAdapter from 'axios/lib/adapters/xhr';
|
||||
|
||||
import { HttpSetup } from 'kibana/public';
|
||||
import { BASE_PATH, API_BASE_PATH } from '../../../../../../../common/constants';
|
||||
import {
|
||||
notificationServiceMock,
|
||||
docLinksServiceMock,
|
||||
} from '../../../../../../../../../../src/core/public/mocks';
|
||||
|
||||
import { init as initHttpRequests } from './http_requests';
|
||||
import { ComponentTemplatesProvider } from '../../../component_templates_context';
|
||||
|
||||
import { init as initHttpRequests } from './http_requests';
|
||||
import { API_BASE_PATH } from './constants';
|
||||
|
||||
const mockHttpClient = axios.create({ adapter: axiosXhrAdapter });
|
||||
|
||||
const appDependencies = {
|
||||
httpClient: (mockHttpClient as unknown) as HttpSetup,
|
||||
apiBasePath: API_BASE_PATH,
|
||||
appBasePath: BASE_PATH,
|
||||
trackMetric: () => {},
|
||||
docLinks: docLinksServiceMock.createStartContract(),
|
||||
toasts: notificationServiceMock.createSetupContract().toasts,
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiFlyout,
|
||||
EuiFlyoutHeader,
|
||||
EuiTitle,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonEmpty,
|
||||
EuiSpacer,
|
||||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { SectionLoading, TabSettings, TabAliases, TabMappings } from '../shared_imports';
|
||||
import { useComponentTemplatesContext } from '../component_templates_context';
|
||||
import { TabSummary } from './tab_summary';
|
||||
import { ComponentTemplateTabs, TabType } from './tabs';
|
||||
import { ManageButton, ManageAction } from './manage_button';
|
||||
|
||||
interface Props {
|
||||
componentTemplateName: string;
|
||||
onClose: () => void;
|
||||
showFooter?: boolean;
|
||||
actions?: ManageAction[];
|
||||
}
|
||||
|
||||
export const ComponentTemplateDetailsFlyout: React.FunctionComponent<Props> = ({
|
||||
componentTemplateName,
|
||||
onClose,
|
||||
actions,
|
||||
}) => {
|
||||
const { api } = useComponentTemplatesContext();
|
||||
|
||||
const { data: componentTemplateDetails, isLoading, error } = api.useLoadComponentTemplate(
|
||||
componentTemplateName
|
||||
);
|
||||
|
||||
const [activeTab, setActiveTab] = useState<TabType>('summary');
|
||||
|
||||
let content: React.ReactNode | undefined;
|
||||
|
||||
if (isLoading) {
|
||||
content = (
|
||||
<SectionLoading>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.componentTemplateDetails.loadingIndexTemplateDescription"
|
||||
defaultMessage="Loading component template…"
|
||||
/>
|
||||
</SectionLoading>
|
||||
);
|
||||
} else if (error) {
|
||||
content = (
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.componentTemplateDetails.loadingErrorMessage"
|
||||
defaultMessage="Error loading component template"
|
||||
/>
|
||||
}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
data-test-subj="sectionError"
|
||||
>
|
||||
<p>{error.message}</p>
|
||||
</EuiCallOut>
|
||||
);
|
||||
} else if (componentTemplateDetails) {
|
||||
const {
|
||||
template: { settings, mappings, aliases },
|
||||
} = componentTemplateDetails;
|
||||
|
||||
const tabToComponentMap: Record<TabType, React.ReactNode> = {
|
||||
summary: <TabSummary componentTemplateDetails={componentTemplateDetails} />,
|
||||
settings: <TabSettings settings={settings} />,
|
||||
mappings: <TabMappings mappings={mappings} />,
|
||||
aliases: <TabAliases aliases={aliases} />,
|
||||
};
|
||||
|
||||
const tabContent = tabToComponentMap[activeTab];
|
||||
|
||||
content = (
|
||||
<>
|
||||
<ComponentTemplateTabs activeTab={activeTab} setActiveTab={setActiveTab} />
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
{tabContent}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlyout
|
||||
onClose={onClose}
|
||||
data-test-subj="componentTemplateDetails"
|
||||
aria-labelledby="componentTemplateDetailsFlyoutTitle"
|
||||
size="m"
|
||||
maxWidth={500}
|
||||
>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiTitle size="m">
|
||||
<h2 id="componentTemplateDetailsFlyoutTitle" data-test-subj="title">
|
||||
{componentTemplateName}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
|
||||
<EuiFlyoutBody data-test-subj="content">{content}</EuiFlyoutBody>
|
||||
|
||||
{actions && (
|
||||
<EuiFlyoutFooter data-test-subj="footer">
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
{/* "Close" link */}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
iconType="cross"
|
||||
flush="left"
|
||||
onClick={onClose}
|
||||
data-test-subj="closeDetailsButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.componentTemplateDetails.closeButtonLabel"
|
||||
defaultMessage="Close"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
|
||||
{/* "Manage" context menu */}
|
||||
{componentTemplateDetails && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<ManageButton
|
||||
actions={actions}
|
||||
componentTemplateDetails={componentTemplateDetails}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
)}
|
||||
</EuiFlyout>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { ComponentTemplateDetailsFlyout } from './component_template_details';
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiPopover,
|
||||
EuiButton,
|
||||
EuiContextMenu,
|
||||
EuiContextMenuPanelItemDescriptor,
|
||||
} from '@elastic/eui';
|
||||
import { ComponentTemplateDeserialized } from '../shared_imports';
|
||||
|
||||
export interface ManageAction {
|
||||
name: string;
|
||||
icon: string;
|
||||
handleActionClick: () => void;
|
||||
getIsDisabled?: (data: ComponentTemplateDeserialized) => boolean;
|
||||
closePopoverOnClick?: boolean;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
actions: ManageAction[];
|
||||
componentTemplateDetails: ComponentTemplateDeserialized;
|
||||
}
|
||||
|
||||
export const ManageButton: React.FunctionComponent<Props> = ({
|
||||
actions,
|
||||
componentTemplateDetails,
|
||||
}) => {
|
||||
const [isPopoverOpen, setIsPopOverOpen] = useState<boolean>(false);
|
||||
|
||||
const items: EuiContextMenuPanelItemDescriptor[] = actions.map(
|
||||
({ name, icon, getIsDisabled, closePopoverOnClick, handleActionClick }) => {
|
||||
const isDisabled = getIsDisabled ? getIsDisabled(componentTemplateDetails) : false;
|
||||
|
||||
return {
|
||||
name,
|
||||
icon,
|
||||
disabled: isDisabled,
|
||||
toolTipContent: isDisabled ? (
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.componentTemplateDetails.manageButtonDisabledTooltipLabel"
|
||||
defaultMessage="Template is in use and cannot be deleted"
|
||||
/>
|
||||
) : null,
|
||||
onClick: () => {
|
||||
handleActionClick();
|
||||
|
||||
if (closePopoverOnClick) {
|
||||
setIsPopOverOpen(false);
|
||||
}
|
||||
},
|
||||
'data-test-subj': 'action',
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
id="manageComponentTemplatePanel"
|
||||
button={
|
||||
<EuiButton
|
||||
fill
|
||||
data-test-subj="manageComponentTemplateButton"
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
onClick={() => setIsPopOverOpen((prevBoolean) => !prevBoolean)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.componentTemplateDetails.manageButtonLabel"
|
||||
defaultMessage="Manage"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={() => setIsPopOverOpen(false)}
|
||||
panelPaddingSize="none"
|
||||
withTitle
|
||||
anchorPosition="rightUp"
|
||||
repositionOnScroll
|
||||
>
|
||||
<EuiContextMenu
|
||||
initialPanelId={0}
|
||||
data-test-subj="manageComponentTemplateContextMenu"
|
||||
panels={[
|
||||
{
|
||||
id: 0,
|
||||
title: i18n.translate(
|
||||
'xpack.idxMgmt.componentTemplateDetails.manageContextMenuPanelTitle',
|
||||
{
|
||||
defaultMessage: 'Options',
|
||||
}
|
||||
),
|
||||
items,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiDescriptionList,
|
||||
EuiDescriptionListTitle,
|
||||
EuiDescriptionListDescription,
|
||||
EuiCodeBlock,
|
||||
EuiTitle,
|
||||
EuiCallOut,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { ComponentTemplateDeserialized } from '../shared_imports';
|
||||
|
||||
interface Props {
|
||||
componentTemplateDetails: ComponentTemplateDeserialized;
|
||||
}
|
||||
|
||||
export const TabSummary: React.FunctionComponent<Props> = ({ componentTemplateDetails }) => {
|
||||
const { version, _meta, _kbnMeta } = componentTemplateDetails;
|
||||
|
||||
const { usedBy } = _kbnMeta;
|
||||
const templateIsInUse = usedBy.length > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Callout when component template is not in use */}
|
||||
{!templateIsInUse && (
|
||||
<>
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.componentTemplateDetails.summaryTab.notInUseTitle"
|
||||
defaultMessage="This component template is not in use by any index templates."
|
||||
/>
|
||||
}
|
||||
iconType="pin"
|
||||
data-test-subj="notInUseCallout"
|
||||
size="s"
|
||||
/>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Summary description list */}
|
||||
<EuiDescriptionList textStyle="reverse" data-test-subj="summaryTabContent">
|
||||
{/* Used by templates */}
|
||||
{templateIsInUse && (
|
||||
<>
|
||||
<EuiDescriptionListTitle data-test-subj="usedByTitle">
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.componentTemplateDetails.summaryTab.usedByDescriptionListTitle"
|
||||
defaultMessage="Used by"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
<ul>
|
||||
{usedBy.map((templateName: string) => (
|
||||
<li key={templateName}>
|
||||
<EuiTitle size="xs">
|
||||
<span>{templateName}</span>
|
||||
</EuiTitle>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</EuiDescriptionListDescription>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Version (optional) */}
|
||||
{version && (
|
||||
<>
|
||||
<EuiDescriptionListTitle data-test-subj="versionTitle">
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.componentTemplateDetails.summaryTab.versionDescriptionListTitle"
|
||||
defaultMessage="Version"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>{version}</EuiDescriptionListDescription>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Metadata (optional) */}
|
||||
{_meta && (
|
||||
<>
|
||||
<EuiDescriptionListTitle data-test-subj="metaTitle">
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.componentTemplateDetails.summaryTab.metaDescriptionListTitle"
|
||||
defaultMessage="Metadata"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
<EuiCodeBlock lang="json">{JSON.stringify(_meta, null, 2)}</EuiCodeBlock>
|
||||
</EuiDescriptionListDescription>
|
||||
</>
|
||||
)}
|
||||
</EuiDescriptionList>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiTab, EuiTabs } from '@elastic/eui';
|
||||
|
||||
export type TabType = 'summary' | 'mappings' | 'aliases' | 'settings';
|
||||
|
||||
interface Props {
|
||||
setActiveTab: (id: TabType) => void;
|
||||
activeTab: TabType;
|
||||
}
|
||||
|
||||
interface Tab {
|
||||
id: TabType;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const TABS: Tab[] = [
|
||||
{
|
||||
id: 'summary',
|
||||
name: i18n.translate('xpack.idxMgmt.componentTemplateDetails.summaryTabTitle', {
|
||||
defaultMessage: 'Summary',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
name: i18n.translate('xpack.idxMgmt.componentTemplateDetails.settingsTabTitle', {
|
||||
defaultMessage: 'Settings',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'mappings',
|
||||
name: i18n.translate('xpack.idxMgmt.componentTemplateDetails.mappingsTabTitle', {
|
||||
defaultMessage: 'Mappings',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'aliases',
|
||||
name: i18n.translate('xpack.idxMgmt.componentTemplateDetails.aliasesTabTitle', {
|
||||
defaultMessage: 'Aliases',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
export const ComponentTemplateTabs: React.FunctionComponent<Props> = ({
|
||||
setActiveTab,
|
||||
activeTab,
|
||||
}) => {
|
||||
return (
|
||||
<EuiTabs>
|
||||
{TABS.map((tab) => (
|
||||
<EuiTab
|
||||
onClick={() => {
|
||||
setActiveTab(tab.id);
|
||||
}}
|
||||
isSelected={tab.id === activeTab}
|
||||
key={tab.id}
|
||||
data-test-subj={`${tab.id}Tab`}
|
||||
>
|
||||
{tab.name}
|
||||
</EuiTab>
|
||||
))}
|
||||
</EuiTabs>
|
||||
);
|
||||
};
|
|
@ -5,24 +5,39 @@
|
|||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { ScopedHistory } from 'kibana/public';
|
||||
|
||||
import { SectionLoading } from '../shared_imports';
|
||||
import { useComponentTemplatesContext } from '../component_templates_context';
|
||||
import { SectionLoading, ComponentTemplateDeserialized } from '../shared_imports';
|
||||
import { UIM_COMPONENT_TEMPLATE_LIST_LOAD } from '../constants';
|
||||
|
||||
import { useComponentTemplatesContext } from '../component_templates_context';
|
||||
import { ComponentTemplateDetailsFlyout } from '../component_template_details';
|
||||
import { EmptyPrompt } from './empty_prompt';
|
||||
import { ComponentTable } from './table';
|
||||
import { LoadError } from './error';
|
||||
import { ComponentTemplatesDeleteModal } from './delete_modal';
|
||||
|
||||
export const ComponentTemplateList: React.FunctionComponent = () => {
|
||||
interface Props {
|
||||
componentTemplateName?: string;
|
||||
history: RouteComponentProps['history'];
|
||||
}
|
||||
|
||||
export const ComponentTemplateList: React.FunctionComponent<Props> = ({
|
||||
componentTemplateName,
|
||||
history,
|
||||
}) => {
|
||||
const { api, trackMetric } = useComponentTemplatesContext();
|
||||
|
||||
const { data, isLoading, error, sendRequest } = api.useLoadComponentTemplates();
|
||||
|
||||
const [componentTemplatesToDelete, setComponentTemplatesToDelete] = useState<string[]>([]);
|
||||
|
||||
const goToList = () => {
|
||||
return history.push('component_templates');
|
||||
};
|
||||
|
||||
// Track component loaded
|
||||
useEffect(() => {
|
||||
trackMetric('loaded', UIM_COMPONENT_TEMPLATE_LIST_LOAD);
|
||||
|
@ -49,6 +64,7 @@ export const ComponentTemplateList: React.FunctionComponent = () => {
|
|||
componentTemplates={data}
|
||||
onReloadClick={sendRequest}
|
||||
onDeleteClick={setComponentTemplatesToDelete}
|
||||
history={history as ScopedHistory}
|
||||
/>
|
||||
);
|
||||
} else if (error) {
|
||||
|
@ -58,18 +74,44 @@ export const ComponentTemplateList: React.FunctionComponent = () => {
|
|||
return (
|
||||
<div data-test-subj="componentTemplateList">
|
||||
{content}
|
||||
|
||||
{/* delete modal */}
|
||||
{componentTemplatesToDelete?.length > 0 ? (
|
||||
<ComponentTemplatesDeleteModal
|
||||
callback={(deleteResponse) => {
|
||||
if (deleteResponse?.hasDeletedComponentTemplates) {
|
||||
// refetch the component templates
|
||||
sendRequest();
|
||||
// go back to list view (if deleted from details flyout)
|
||||
goToList();
|
||||
}
|
||||
setComponentTemplatesToDelete([]);
|
||||
}}
|
||||
componentTemplatesToDelete={componentTemplatesToDelete}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{/* details flyout */}
|
||||
{componentTemplateName && (
|
||||
<ComponentTemplateDetailsFlyout
|
||||
onClose={goToList}
|
||||
componentTemplateName={componentTemplateName}
|
||||
actions={[
|
||||
{
|
||||
name: i18n.translate('xpack.idxMgmt.componentTemplateDetails.deleteButtonLabel', {
|
||||
defaultMessage: 'Delete',
|
||||
}),
|
||||
icon: 'trash',
|
||||
getIsDisabled: (details: ComponentTemplateDeserialized) =>
|
||||
details._kbnMeta.usedBy.length > 0,
|
||||
closePopoverOnClick: true,
|
||||
handleActionClick: () => {
|
||||
setComponentTemplatesToDelete([componentTemplateName]);
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,16 +5,28 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import { ComponentTemplatesAuthProvider } from './auth_provider';
|
||||
import { ComponentTemplatesWithPrivileges } from './with_privileges';
|
||||
import { ComponentTemplateList } from './component_template_list';
|
||||
|
||||
export const ComponentTemplateListContainer: React.FunctionComponent = () => {
|
||||
interface MatchParams {
|
||||
componentTemplateName?: string;
|
||||
}
|
||||
|
||||
export const ComponentTemplateListContainer: React.FunctionComponent<RouteComponentProps<
|
||||
MatchParams
|
||||
>> = ({
|
||||
match: {
|
||||
params: { componentTemplateName },
|
||||
},
|
||||
history,
|
||||
}) => {
|
||||
return (
|
||||
<ComponentTemplatesAuthProvider>
|
||||
<ComponentTemplatesWithPrivileges>
|
||||
<ComponentTemplateList />
|
||||
<ComponentTemplateList componentTemplateName={componentTemplateName} history={history} />
|
||||
</ComponentTemplatesWithPrivileges>
|
||||
</ComponentTemplatesAuthProvider>
|
||||
);
|
||||
|
|
|
@ -12,21 +12,30 @@ import {
|
|||
EuiInMemoryTableProps,
|
||||
EuiTextColor,
|
||||
EuiIcon,
|
||||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
import { ScopedHistory } from 'kibana/public';
|
||||
|
||||
import { ComponentTemplateListItem } from '../types';
|
||||
import { reactRouterNavigate } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { ComponentTemplateListItem } from '../shared_imports';
|
||||
import { UIM_COMPONENT_TEMPLATE_DETAILS } from '../constants';
|
||||
import { useComponentTemplatesContext } from '../component_templates_context';
|
||||
|
||||
export interface Props {
|
||||
componentTemplates: ComponentTemplateListItem[];
|
||||
onReloadClick: () => void;
|
||||
onDeleteClick: (componentTemplateName: string[]) => void;
|
||||
history: ScopedHistory;
|
||||
}
|
||||
|
||||
export const ComponentTable: FunctionComponent<Props> = ({
|
||||
componentTemplates,
|
||||
onReloadClick,
|
||||
onDeleteClick,
|
||||
history,
|
||||
}) => {
|
||||
const { trackMetric } = useComponentTemplatesContext();
|
||||
|
||||
const [selection, setSelection] = useState<ComponentTemplateListItem[]>([]);
|
||||
|
||||
const tableProps: EuiInMemoryTableProps<ComponentTemplateListItem> = {
|
||||
|
@ -120,6 +129,21 @@ export const ComponentTable: FunctionComponent<Props> = ({
|
|||
defaultMessage: 'Name',
|
||||
}),
|
||||
sortable: true,
|
||||
render: (name: string) => (
|
||||
/* eslint-disable-next-line @elastic/eui/href-or-on-click */
|
||||
<EuiLink
|
||||
{...reactRouterNavigate(
|
||||
history,
|
||||
{
|
||||
pathname: `/component_templates/${name}`,
|
||||
},
|
||||
() => trackMetric('click', UIM_COMPONENT_TEMPLATE_DETAILS)
|
||||
)}
|
||||
data-test-subj="templateDetailsLink"
|
||||
>
|
||||
{name}
|
||||
</EuiLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'usedBy',
|
||||
|
|
|
@ -14,7 +14,6 @@ const ComponentTemplatesContext = createContext<Context | undefined>(undefined);
|
|||
interface Props {
|
||||
httpClient: HttpSetup;
|
||||
apiBasePath: string;
|
||||
appBasePath: string;
|
||||
trackMetric: (type: 'loaded' | 'click' | 'count', eventName: string) => void;
|
||||
docLinks: DocLinksSetup;
|
||||
toasts: NotificationsSetup['toasts'];
|
||||
|
@ -27,7 +26,6 @@ interface Context {
|
|||
documentation: ReturnType<typeof getDocumentation>;
|
||||
trackMetric: (type: 'loaded' | 'click' | 'count', eventName: string) => void;
|
||||
toasts: NotificationsSetup['toasts'];
|
||||
appBasePath: string;
|
||||
}
|
||||
|
||||
export const ComponentTemplatesProvider = ({
|
||||
|
@ -37,7 +35,7 @@ export const ComponentTemplatesProvider = ({
|
|||
value: Props;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const { httpClient, apiBasePath, trackMetric, docLinks, toasts, appBasePath } = value;
|
||||
const { httpClient, apiBasePath, trackMetric, docLinks, toasts } = value;
|
||||
|
||||
const useRequest = getUseRequest(httpClient);
|
||||
const sendRequest = getSendRequest(httpClient);
|
||||
|
@ -47,7 +45,7 @@ export const ComponentTemplatesProvider = ({
|
|||
|
||||
return (
|
||||
<ComponentTemplatesContext.Provider
|
||||
value={{ api, documentation, trackMetric, toasts, appBasePath, httpClient, apiBasePath }}
|
||||
value={{ api, documentation, trackMetric, toasts, httpClient, apiBasePath }}
|
||||
>
|
||||
{children}
|
||||
</ComponentTemplatesContext.Provider>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
export const UIM_COMPONENT_TEMPLATE_LIST_LOAD = 'component_template_list_load';
|
||||
export const UIM_COMPONENT_TEMPLATE_DELETE = 'component_template_delete';
|
||||
export const UIM_COMPONENT_TEMPLATE_DELETE_MANY = 'component_template_delete_many';
|
||||
export const UIM_COMPONENT_TEMPLATE_DETAILS = 'component_template_details';
|
||||
|
||||
// privileges
|
||||
export const APP_CLUSTER_REQUIRED_PRIVILEGES = ['manage_index_templates'];
|
||||
|
|
|
@ -8,4 +8,4 @@ export { ComponentTemplatesProvider } from './component_templates_context';
|
|||
|
||||
export { ComponentTemplateList } from './component_template_list';
|
||||
|
||||
export * from './types';
|
||||
export { ComponentTemplateDetailsFlyout } from './component_template_details';
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ComponentTemplateListItem } from '../types';
|
||||
import { UseRequestHook, SendRequestHook } from './request';
|
||||
import { ComponentTemplateListItem, ComponentTemplateDeserialized } from '../shared_imports';
|
||||
import { UIM_COMPONENT_TEMPLATE_DELETE_MANY, UIM_COMPONENT_TEMPLATE_DELETE } from '../constants';
|
||||
import { UseRequestHook, SendRequestHook } from './request';
|
||||
|
||||
export const getApi = (
|
||||
useRequest: UseRequestHook,
|
||||
|
@ -37,8 +37,16 @@ export const getApi = (
|
|||
return result;
|
||||
}
|
||||
|
||||
function useLoadComponentTemplate(name: string) {
|
||||
return useRequest<ComponentTemplateDeserialized>({
|
||||
path: `${apiBasePath}/component_templates/${encodeURIComponent(name)}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
useLoadComponentTemplates,
|
||||
deleteComponentTemplates,
|
||||
useLoadComponentTemplate,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -19,3 +19,11 @@ export {
|
|||
useAuthorizationContext,
|
||||
NotAuthorizedSection,
|
||||
} from '../../../../../../../src/plugins/es_ui_shared/public';
|
||||
|
||||
export { TabMappings, TabSettings, TabAliases } from '../shared';
|
||||
|
||||
export {
|
||||
ComponentTemplateSerialized,
|
||||
ComponentTemplateDeserialized,
|
||||
ComponentTemplateListItem,
|
||||
} from '../../../../common';
|
||||
|
|
|
@ -1,17 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
// Ideally, we shouldn't depend on anything in index management that is
|
||||
// outside of the components_templates directory
|
||||
// We could consider creating shared types or duplicating the types here if
|
||||
// the component_templates app were to move outside of index management
|
||||
import {
|
||||
ComponentTemplateSerialized,
|
||||
ComponentTemplateDeserialized,
|
||||
ComponentTemplateListItem,
|
||||
} from '../../../../common';
|
||||
|
||||
export { ComponentTemplateSerialized, ComponentTemplateDeserialized, ComponentTemplateListItem };
|
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { TabAliases } from './tab_aliases';
|
||||
export { TabMappings } from './tab_mappings';
|
||||
export { TabSettings } from './tab_settings';
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiCodeBlock, EuiCallOut } from '@elastic/eui';
|
||||
|
||||
import { Aliases } from '../../../../../../common';
|
||||
|
||||
interface Props {
|
||||
aliases: Aliases | undefined;
|
||||
}
|
||||
|
||||
export const TabAliases: React.FunctionComponent<Props> = ({ aliases }) => {
|
||||
if (aliases && Object.keys(aliases).length) {
|
||||
return (
|
||||
<div data-test-subj="aliasesTabContent">
|
||||
<EuiCodeBlock lang="json">{JSON.stringify(aliases, null, 2)}</EuiCodeBlock>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.aliasesTab.noAliasesTitle"
|
||||
defaultMessage="No aliases defined."
|
||||
/>
|
||||
}
|
||||
iconType="pin"
|
||||
data-test-subj="noAliasesCallout"
|
||||
size="s"
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiCodeBlock, EuiCallOut } from '@elastic/eui';
|
||||
import { Mappings } from '../../../../../../common';
|
||||
|
||||
interface Props {
|
||||
mappings: Mappings | undefined;
|
||||
}
|
||||
|
||||
export const TabMappings: React.FunctionComponent<Props> = ({ mappings }) => {
|
||||
if (mappings && Object.keys(mappings).length) {
|
||||
return (
|
||||
<div data-test-subj="mappingsTabContent">
|
||||
<EuiCodeBlock lang="json">{JSON.stringify(mappings, null, 2)}</EuiCodeBlock>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.mappingsTab.noMappingsTitle"
|
||||
defaultMessage="No mappings defined."
|
||||
/>
|
||||
}
|
||||
iconType="pin"
|
||||
data-test-subj="noMappingsCallout"
|
||||
size="s"
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiCodeBlock, EuiCallOut } from '@elastic/eui';
|
||||
import { IndexSettings } from '../../../../../../common';
|
||||
|
||||
interface Props {
|
||||
settings: IndexSettings | undefined;
|
||||
}
|
||||
|
||||
export const TabSettings: React.FunctionComponent<Props> = ({ settings }) => {
|
||||
if (settings && Object.keys(settings).length) {
|
||||
return (
|
||||
<div data-test-subj="settingsTabContent">
|
||||
<EuiCodeBlock lang="json">{JSON.stringify(settings, null, 2)}</EuiCodeBlock>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.settingsTab.noIndexSettingsTitle"
|
||||
defaultMessage="No settings defined."
|
||||
/>
|
||||
}
|
||||
iconType="pin"
|
||||
data-test-subj="noSettingsCallout"
|
||||
size="s"
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { TabAliases, TabMappings, TabSettings } from './details_panel';
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { TabAliases, TabMappings, TabSettings } from './components';
|
|
@ -10,7 +10,7 @@ import { render, unmountComponentAtNode } from 'react-dom';
|
|||
|
||||
import { CoreStart } from '../../../../../src/core/public';
|
||||
|
||||
import { API_BASE_PATH, BASE_PATH } from '../../common';
|
||||
import { API_BASE_PATH } from '../../common';
|
||||
|
||||
import { AppContextProvider, AppDependencies } from './app_context';
|
||||
import { App } from './app';
|
||||
|
@ -32,7 +32,6 @@ export const renderApp = (
|
|||
const componentTemplateProviderValues = {
|
||||
httpClient: services.httpService.httpClient,
|
||||
apiBasePath: API_BASE_PATH,
|
||||
appBasePath: BASE_PATH,
|
||||
trackMetric: services.uiMetricService.trackMetric.bind(services.uiMetricService),
|
||||
docLinks,
|
||||
toasts: notifications.toasts,
|
||||
|
|
|
@ -150,7 +150,14 @@ export const IndexManagementHome: React.FunctionComponent<RouteComponentProps<Ma
|
|||
path={[`/${Section.IndexTemplates}`, `/${Section.IndexTemplates}/:templateName?`]}
|
||||
component={TemplateList}
|
||||
/>
|
||||
<Route exact path={`/${Section.ComponentTemplates}`} component={ComponentTemplateList} />
|
||||
<Route
|
||||
exact
|
||||
path={[
|
||||
`/${Section.ComponentTemplates}`,
|
||||
`/${Section.ComponentTemplates}/:componentTemplateName?`,
|
||||
]}
|
||||
component={ComponentTemplateList}
|
||||
/>
|
||||
</Switch>
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
|
|
Loading…
Reference in a new issue