Discourage use of legacy index templates (#101533)

* Hide legacy index templates table if the user doesn't have any.
* Render deprecation warning above legacy index templates table and in legacy index template wizard.
* Update index template doc link to point to the new docs.
This commit is contained in:
CJ Cenizal 2021-06-14 15:24:24 -07:00 committed by GitHub
parent 6e0aed79c3
commit 55a0dbbc09
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 247 additions and 47 deletions

View file

@ -8,18 +8,39 @@
import React, { Component, ComponentType } from 'react';
import { MemoryRouter, Route, withRouter } from 'react-router-dom';
import * as H from 'history';
import { History, LocationDescriptor } from 'history';
export const WithMemoryRouter = (initialEntries: string[] = ['/'], initialIndex: number = 0) => (
WrappedComponent: ComponentType
) => (props: any) => (
const stringifyPath = (path: LocationDescriptor): string => {
if (typeof path === 'string') {
return path;
}
return path.pathname || '/';
};
const locationDescriptorToRoutePath = (
paths: LocationDescriptor | LocationDescriptor[]
): string | string[] => {
if (Array.isArray(paths)) {
return paths.map((path: LocationDescriptor) => {
return stringifyPath(path);
});
}
return stringifyPath(paths);
};
export const WithMemoryRouter = (
initialEntries: LocationDescriptor[] = ['/'],
initialIndex: number = 0
) => (WrappedComponent: ComponentType) => (props: any) => (
<MemoryRouter initialEntries={initialEntries} initialIndex={initialIndex}>
<WrappedComponent {...props} />
</MemoryRouter>
);
export const WithRoute = (
componentRoutePath: string | string[] = '/',
componentRoutePath: LocationDescriptor | LocationDescriptor[] = ['/'],
onRouter = (router: any) => {}
) => (WrappedComponent: ComponentType) => {
// Create a class component that will catch the router
@ -40,16 +61,16 @@ export const WithRoute = (
return (props: any) => (
<Route
path={componentRoutePath}
path={locationDescriptorToRoutePath(componentRoutePath)}
render={(routerProps) => <CatchRouter {...routerProps} {...props} />}
/>
);
};
interface Router {
history: Partial<H.History>;
history: Partial<History>;
route: {
location: H.Location;
location: LocationDescriptor;
};
}

View file

@ -8,6 +8,7 @@
import { Store } from 'redux';
import { ReactWrapper } from 'enzyme';
import { LocationDescriptor } from 'history';
export type SetupFunc<T> = (props?: any) => TestBed<T> | Promise<TestBed<T>>;
@ -161,11 +162,11 @@ export interface MemoryRouterConfig {
/** Flag to add or not the `MemoryRouter`. If set to `false`, there won't be any router and the component won't be wrapped on a `<Route />`. */
wrapComponent?: boolean;
/** The React Router **initial entries** setting ([see documentation](https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/MemoryRouter.md)) */
initialEntries?: string[];
initialEntries?: LocationDescriptor[];
/** The React Router **initial index** setting ([see documentation](https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/MemoryRouter.md)) */
initialIndex?: number;
/** The route **path** for the mounted component (defaults to `"/"`) */
componentRoutePath?: string | string[];
componentRoutePath?: LocationDescriptor | LocationDescriptor[];
/** A callBack that will be called with the React Router instance once mounted */
onRouter?: (router: any) => void;
}

View file

@ -142,7 +142,7 @@ export class DocLinksService {
dataStreams: `${ELASTICSEARCH_DOCS}data-streams.html`,
indexModules: `${ELASTICSEARCH_DOCS}index-modules.html`,
indexSettings: `${ELASTICSEARCH_DOCS}index-modules.html#index-modules-settings`,
indexTemplates: `${ELASTICSEARCH_DOCS}indices-templates.html`,
indexTemplates: `${ELASTICSEARCH_DOCS}index-templates.html`,
mapping: `${ELASTICSEARCH_DOCS}mapping.html`,
mappingAnalyzer: `${ELASTICSEARCH_DOCS}analyzer.html`,
mappingCoerce: `${ELASTICSEARCH_DOCS}coerce.html`,

View file

@ -31,7 +31,7 @@ describe('Index Templates tab', () => {
server.restore();
});
describe('when there are no index templates', () => {
describe('when there are no index templates of either kind', () => {
test('should display an empty prompt', async () => {
httpRequestsMockHelpers.setLoadTemplatesResponse({ templates: [], legacyTemplates: [] });
@ -46,6 +46,26 @@ describe('Index Templates tab', () => {
});
});
describe('when there are composable index templates but no legacy index templates', () => {
test('only the composable index templates table is visible', async () => {
httpRequestsMockHelpers.setLoadTemplatesResponse({
templates: [fixtures.getComposableTemplate()],
legacyTemplates: [],
});
await act(async () => {
testBed = await setup();
});
const { exists, component } = testBed;
component.update();
expect(exists('sectionLoading')).toBe(false);
expect(exists('emptyPrompt')).toBe(false);
expect(exists('templateTable')).toBe(true);
expect(exists('legacyTemplateTable')).toBe(false);
});
});
describe('when there are index templates', () => {
// Add a default loadIndexTemplate response
httpRequestsMockHelpers.setLoadTemplateResponse(fixtures.getTemplate());

View file

@ -11,17 +11,23 @@ import { WithAppDependencies } from '../helpers';
import { formSetup, TestSubjects } from './template_form.helpers';
const testBedConfig: TestBedConfig = {
memoryRouter: {
initialEntries: [`/create_template`],
componentRoutePath: `/create_template`,
},
doMountAsync: true,
export const setup: any = (isLegacy: boolean = false) => {
const route = isLegacy
? { pathname: '/create_template', search: '?legacy=true' }
: { pathname: '/create_template' };
const testBedConfig: TestBedConfig = {
memoryRouter: {
initialEntries: [route],
componentRoutePath: route,
},
doMountAsync: true,
};
const initTestBed = registerTestBed<TestSubjects>(
WithAppDependencies(TemplateCreate),
testBedConfig
);
return formSetup.call(null, initTestBed);
};
const initTestBed = registerTestBed<TestSubjects>(
WithAppDependencies(TemplateCreate),
testBedConfig
);
export const setup: any = formSetup.bind(null, initTestBed);

View file

@ -101,7 +101,7 @@ describe('<TemplateCreate />', () => {
(window as any)['__react-beautiful-dnd-disable-dev-warnings'] = false;
});
describe('on component mount', () => {
describe('composable index template', () => {
beforeEach(async () => {
await act(async () => {
testBed = await setup();
@ -115,6 +115,11 @@ describe('<TemplateCreate />', () => {
expect(find('pageTitle').text()).toEqual('Create template');
});
test('renders no deprecation warning', async () => {
const { exists } = testBed;
expect(exists('legacyIndexTemplateDeprecationWarning')).toBe(false);
});
test('should not let the user go to the next step with invalid fields', async () => {
const { find, actions, component } = testBed;
@ -129,6 +134,26 @@ describe('<TemplateCreate />', () => {
});
});
describe('legacy index template', () => {
beforeEach(async () => {
await act(async () => {
testBed = await setup(true);
});
});
test('should set the correct page title', () => {
const { exists, find } = testBed;
expect(exists('pageTitle')).toBe(true);
expect(find('pageTitle').text()).toEqual('Create legacy template');
});
test('renders deprecation warning', async () => {
const { exists } = testBed;
expect(exists('legacyIndexTemplateDeprecationWarning')).toBe(true);
});
});
describe('form validation', () => {
beforeEach(async () => {
await act(async () => {
@ -150,6 +175,11 @@ describe('<TemplateCreate />', () => {
expect(find('stepTitle').text()).toEqual('Component templates (optional)');
});
it(`doesn't render the deprecated legacy index template warning`, () => {
const { exists } = testBed;
expect(exists('legacyIndexTemplateDeprecationWarning')).toBe(false);
});
it('should list the available component templates', () => {
const {
actions: {

View file

@ -306,6 +306,7 @@ export type TestSubjects =
| 'indexPatternsField'
| 'indexPatternsWarning'
| 'indexPatternsWarningDescription'
| 'legacyIndexTemplateDeprecationWarning'
| 'mappingsEditorFieldEdit'
| 'mockCodeEditor'
| 'mockComboBox'

View file

@ -5,4 +5,12 @@
* 2.0.
*/
export * from './simulate_template';
export {
SimulateTemplateFlyoutContent,
simulateTemplateFlyoutProps,
SimulateTemplateProps,
SimulateTemplate,
SimulateTemplateFilters,
} from './simulate_template';
export { LegacyIndexTemplatesDeprecation } from './legacy_index_template_deprecation';

View file

@ -0,0 +1,84 @@
/*
* 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 { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { EuiCallOut, EuiLink } from '@elastic/eui';
import { ScopedHistory } from 'kibana/public';
import { reactRouterNavigate } from '../../../shared_imports';
import { documentationService } from '../../services/documentation';
interface Props {
history?: ScopedHistory;
showCta?: boolean;
}
export const LegacyIndexTemplatesDeprecation: React.FunctionComponent<Props> = ({
history,
showCta,
}) => {
return (
<EuiCallOut
title={i18n.translate('xpack.idxMgmt.legacyIndexTemplatesDeprecation.title', {
defaultMessage:
'Legacy index templates are deprecated in favor of composable index templates',
})}
color="warning"
iconType="alert"
data-test-subj="legacyIndexTemplateDeprecationWarning"
>
{showCta && history && (
<p>
<FormattedMessage
id="xpack.idxMgmt.legacyIndexTemplatesDeprecation.description"
defaultMessage="{createTemplateButton} or {learnMoreLink}"
values={{
createTemplateButton: (
<EuiLink
data-test-subj="createTemplateButton"
{...reactRouterNavigate(history, '/create_template')}
>
<FormattedMessage
id="xpack.idxMgmt.legacyIndexTemplatesDeprecation.createTemplatesButtonLabel"
defaultMessage="Create composable template"
/>
</EuiLink>
),
learnMoreLink: (
<EuiLink
href={documentationService.getTemplatesDocumentationLink()}
target="_blank"
external
>
{i18n.translate(
'xpack.idxMgmt.home.legacyIndexTemplatesDeprecation.ctaLearnMoreLinkText',
{
defaultMessage: 'learn more.',
}
)}
</EuiLink>
),
}}
/>
</p>
)}
{!showCta && (
<EuiLink
href={documentationService.getTemplatesDocumentationLink()}
target="_blank"
external
>
{i18n.translate('xpack.idxMgmt.home.legacyIndexTemplatesDeprecation.learnMoreLinkText', {
defaultMessage: 'Learn more.',
})}
</EuiLink>
)}
</EuiCallOut>
);
};

View file

@ -9,17 +9,10 @@ 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 { ScopedHistory } from 'kibana/public';
import { TemplateDeserialized } from '../../../../common';
import { serializers, Forms, GlobalFlyout } from '../../../shared_imports';
import { SectionError } from '../section_error';
import {
SimulateTemplateFlyoutContent,
SimulateTemplateProps,
simulateTemplateFlyoutProps,
SimulateTemplateFilters,
} from '../index_templates';
import { StepLogisticsContainer, StepComponentContainer, StepReviewContainer } from './steps';
import {
CommonWizardSteps,
StepSettingsContainer,
@ -27,6 +20,15 @@ import {
StepAliasesContainer,
} from '../shared';
import { documentationService } from '../../services/documentation';
import { SectionError } from '../section_error';
import {
SimulateTemplateFlyoutContent,
SimulateTemplateProps,
simulateTemplateFlyoutProps,
SimulateTemplateFilters,
LegacyIndexTemplatesDeprecation,
} from '../index_templates';
import { StepLogisticsContainer, StepComponentContainer, StepReviewContainer } from './steps';
const { stripEmptyFields } = serializers;
const { FormWizard, FormWizardStep } = Forms;
@ -38,6 +40,7 @@ interface Props {
clearSaveError: () => void;
isSaving: boolean;
saveError: any;
history?: ScopedHistory;
isLegacy?: boolean;
defaultValue?: TemplateDeserialized;
isEditing?: boolean;
@ -98,6 +101,7 @@ export const TemplateForm = ({
saveError,
clearSaveError,
onSave,
history,
}: Props) => {
const [wizardContent, setWizardContent] = useState<Forms.Content<WizardContent> | null>(null);
const { addContent: addContentToGlobalFlyout, closeFlyout } = useGlobalFlyout();
@ -283,12 +287,20 @@ export const TemplateForm = ({
);
};
const isLegacyIndexTemplate = indexTemplate._kbnMeta.isLegacy === true;
return (
<>
{/* Form header */}
{title}
<EuiSpacer size="l" />
<EuiSpacer size="m" />
{isLegacyIndexTemplate && (
<LegacyIndexTemplatesDeprecation history={history} showCta={true} />
)}
<EuiSpacer size="s" />
<FormWizard<WizardContent, WizardSection>
defaultValue={wizardDefaultValue}
@ -311,7 +323,7 @@ export const TemplateForm = ({
/>
</FormWizardStep>
{indexTemplate._kbnMeta.isLegacy !== true && (
{!isLegacyIndexTemplate && (
<FormWizardStep id={wizardSections.components.id} label={wizardSections.components.label}>
<StepComponentContainer />
</FormWizardStep>

View file

@ -5,4 +5,4 @@
* 2.0.
*/
export * from './template_type_indicator';
export { TemplateTypeIndicator } from './template_type_indicator';

View file

@ -24,7 +24,13 @@ import {
import { UIM_TEMPLATE_LIST_LOAD } from '../../../../../common/constants';
import { TemplateListItem } from '../../../../../common';
import { SectionError, SectionLoading, Error } from '../../../components';
import { attemptToURIDecode } from '../../../../shared_imports';
import {
SectionError,
SectionLoading,
Error,
LegacyIndexTemplatesDeprecation,
} from '../../../components';
import { useLoadIndexTemplates } from '../../../services/api';
import { documentationService } from '../../../services/documentation';
import { useServices } from '../../../app_context';
@ -34,11 +40,10 @@ import {
getTemplateCloneLink,
} from '../../../services/routing';
import { getIsLegacyFromQueryParams } from '../../../lib/index_templates';
import { FilterListButton, Filters } from '../components';
import { TemplateTable } from './template_table';
import { TemplateDetails } from './template_details';
import { LegacyTemplateTable } from './legacy_templates/template_table';
import { FilterListButton, Filters } from '../components';
import { attemptToURIDecode } from '../../../../shared_imports';
type FilterName = 'managed' | 'cloudManaged' | 'system';
interface MatchParams {
@ -130,7 +135,7 @@ export const TemplateList: React.FunctionComponent<RouteComponentProps<MatchPara
<EuiText color="subdued">
<FormattedMessage
id="xpack.idxMgmt.home.indexTemplatesDescription"
defaultMessage="Use index templates to automatically apply settings, mappings, and aliases to indices. {learnMoreLink}"
defaultMessage="Use composable index templates to automatically apply settings, mappings, and aliases to indices. {learnMoreLink}"
values={{
learnMoreLink: (
<EuiLink
@ -196,7 +201,13 @@ export const TemplateList: React.FunctionComponent<RouteComponentProps<MatchPara
/>
</h1>
</EuiTitle>
<EuiSpacer />
<EuiSpacer size="s" />
<LegacyIndexTemplatesDeprecation />
<EuiSpacer size="m" />
<LegacyTemplateTable
templates={filteredTemplates.legacyTemplates}
reload={reload}
@ -253,8 +264,8 @@ export const TemplateList: React.FunctionComponent<RouteComponentProps<MatchPara
{/* Composable index templates table */}
{renderTemplatesTable()}
{/* Legacy index templates table */}
{renderLegacyTemplatesTable()}
{/* Legacy index templates table. We discourage their adoption if the user isn't already using them. */}
{filteredTemplates.legacyTemplates.length > 0 && renderLegacyTemplatesTable()}
</Fragment>
);
}

View file

@ -9,6 +9,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 { ScopedHistory } from 'kibana/public';
import { TemplateDeserialized } from '../../../../common';
import { TemplateForm, SectionLoading, SectionError, Error } from '../../components';
@ -114,6 +115,7 @@ export const TemplateClone: React.FunctionComponent<RouteComponentProps<MatchPar
saveError={saveError}
clearSaveError={clearSaveError}
isLegacy={isLegacy}
history={history as ScopedHistory}
/>
);
}

View file

@ -11,10 +11,11 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { EuiPageBody, EuiPageContent, EuiTitle } from '@elastic/eui';
import { useLocation } from 'react-router-dom';
import { parse } from 'query-string';
import { ScopedHistory } from 'kibana/public';
import { TemplateDeserialized } from '../../../../common';
import { TemplateForm } from '../../components';
import { breadcrumbService } from '../../services/breadcrumbs';
import { TemplateDeserialized } from '../../../../common';
import { saveTemplate } from '../../services/api';
import { getTemplateDetailsLink } from '../../services/routing';
@ -76,6 +77,7 @@ export const TemplateCreate: React.FunctionComponent<RouteComponentProps> = ({ h
saveError={saveError}
clearSaveError={clearSaveError}
isLegacy={isLegacy}
history={history as ScopedHistory}
/>
</EuiPageContent>
</EuiPageBody>

View file

@ -9,14 +9,15 @@ import React, { useEffect, useState, Fragment } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiPageBody, EuiPageContent, EuiTitle, EuiSpacer, EuiCallOut } from '@elastic/eui';
import { ScopedHistory } from 'kibana/public';
import { TemplateDeserialized } from '../../../../common';
import { attemptToURIDecode } 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 { getIsLegacyFromQueryParams } from '../../lib/index_templates';
import { attemptToURIDecode } from '../../../shared_imports';
interface MatchParams {
name: string;
@ -154,6 +155,7 @@ export const TemplateEdit: React.FunctionComponent<RouteComponentProps<MatchPara
clearSaveError={clearSaveError}
isEditing={true}
isLegacy={isLegacy}
history={history as ScopedHistory}
/>
</Fragment>
);