[Index Management] Disable legacy index templates (#163518)

This commit is contained in:
Alison Goryachev 2023-08-14 10:40:16 -04:00 committed by GitHub
parent e12ad9785c
commit 97f44c1e50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 259 additions and 26 deletions

View file

@ -32,8 +32,11 @@ xpack.remote_clusters.enabled: false
xpack.snapshot_restore.enabled: false xpack.snapshot_restore.enabled: false
xpack.license_management.enabled: false xpack.license_management.enabled: false
# Disable index management actions from the UI # Management team UI configurations
# Disable index actions from the Index Management UI
xpack.index_management.enableIndexActions: false xpack.index_management.enableIndexActions: false
# Disable legacy index templates from Index Management UI
xpack.index_management.enableLegacyTemplates: false
# Keep deeplinks visible so that they are shown in the sidenav # Keep deeplinks visible so that they are shown in the sidenav
dev_tools.deeplinks.navLinkStatus: visible dev_tools.deeplinks.navLinkStatus: visible

View file

@ -240,6 +240,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.ilm.ui.enabled (boolean)', 'xpack.ilm.ui.enabled (boolean)',
'xpack.index_management.ui.enabled (boolean)', 'xpack.index_management.ui.enabled (boolean)',
'xpack.index_management.enableIndexActions (any)', 'xpack.index_management.enableIndexActions (any)',
'xpack.index_management.enableLegacyTemplates (any)',
'xpack.infra.sources.default.fields.message (array)', 'xpack.infra.sources.default.fields.message (array)',
/** /**
* xpack.infra.logs is conditional and will resolve to an object of properties * xpack.infra.logs is conditional and will resolve to an object of properties

View file

@ -53,7 +53,7 @@ POST %25%7B%5B%40metadata%5D%5Bbeat%5D%7D-%25%7B%5B%40metadata%5D%5Bversion%5D%7
### Quick steps for testing ### Quick steps for testing
By default, **legacy index templates** are not shown in the UI. Make them appear by creating one in Console: **Legacy index templates** are only shown in the UI on stateful *and* if a user has existing legacy index templates. You can test this functionality by creating one in Console:
``` ```
PUT _template/template_1 PUT _template/template_1
@ -62,6 +62,8 @@ PUT _template/template_1
} }
``` ```
On serverless, Elasticsearch does not support legacy index templates and therefore this functionality is disabled in Kibana via the config `xpack.index_management.enableLegacyTemplates`. For more details, see [#163518](https://github.com/elastic/kibana/pull/163518).
To test **Cloud-managed templates**: To test **Cloud-managed templates**:
1. Add `cluster.metadata.managed_index_templates` setting via Dev Tools: 1. Add `cluster.metadata.managed_index_templates` setting via Dev Tools:

View file

@ -56,6 +56,11 @@ const appDependencies = {
executionContext: executionContextServiceMock.createStartContract(), executionContext: executionContextServiceMock.createStartContract(),
}, },
plugins: {}, plugins: {},
// Default stateful configuration
config: {
enableLegacyTemplates: true,
enableIndexActions: true,
},
} as any; } as any;
export const kibanaVersion = new SemVer(MAJOR_VERSION); export const kibanaVersion = new SemVer(MAJOR_VERSION);
@ -82,7 +87,6 @@ export const WithAppDependencies =
(props: any) => { (props: any) => {
httpService.setup(httpSetup); httpService.setup(httpSetup);
const mergedDependencies = merge({}, appDependencies, overridingDependencies); const mergedDependencies = merge({}, appDependencies, overridingDependencies);
return ( return (
<KibanaReactContextProvider> <KibanaReactContextProvider>
<AppContextProvider value={mergedDependencies}> <AppContextProvider value={mergedDependencies}>

View file

@ -225,11 +225,11 @@ describe('<IndexManagementHome />', () => {
]); ]);
httpRequestsMockHelpers.setReloadIndicesResponse({ indexNames: [indexNameA, indexNameB] }); httpRequestsMockHelpers.setReloadIndicesResponse({ indexNames: [indexNameA, indexNameB] });
testBed = await setup(httpSetup, { await act(async () => {
enableIndexActions: true, testBed = await setup(httpSetup);
}); });
const { component, find } = testBed;
const { component, find } = testBed;
component.update(); component.update();
find('indexTableIndexNameLink').at(0).simulate('click'); find('indexTableIndexNameLink').at(0).simulate('click');
@ -270,8 +270,8 @@ describe('<IndexManagementHome />', () => {
}); });
test('should be able to open a closed index', async () => { test('should be able to open a closed index', async () => {
testBed = await setup(httpSetup, { await act(async () => {
enableIndexActions: true, testBed = await setup(httpSetup);
}); });
const { component, find, actions } = testBed; const { component, find, actions } = testBed;

View file

@ -168,7 +168,11 @@ describe('index table', () => {
}, },
plugins: {}, plugins: {},
url: urlServiceMock, url: urlServiceMock,
enableIndexActions: true, // Default stateful configuration
config: {
enableLegacyTemplates: true,
enableIndexActions: true,
},
}; };
component = ( component = (
@ -515,8 +519,8 @@ describe('index table', () => {
describe('Common index actions', () => { describe('Common index actions', () => {
beforeEach(() => { beforeEach(() => {
// Mock initialization of services // Mock initialization of services; set enableIndexActions=false to verify config behavior
setupMockComponent({ enableIndexActions: false }); setupMockComponent({ config: { enableIndexActions: false, enableLegacyTemplates: true } });
}); });
test('Common index actions should be hidden when feature is turned off', async () => { test('Common index actions should be hidden when feature is turned off', async () => {

View file

@ -44,6 +44,10 @@ export interface AppDependencies {
httpService: HttpService; httpService: HttpService;
notificationService: NotificationService; notificationService: NotificationService;
}; };
config: {
enableIndexActions: boolean;
enableLegacyTemplates: boolean;
};
history: ScopedHistory; history: ScopedHistory;
setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs']; setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs'];
uiSettings: IUiSettingsClient; uiSettings: IUiSettingsClient;
@ -52,7 +56,6 @@ export interface AppDependencies {
docLinks: DocLinksStart; docLinks: DocLinksStart;
kibanaVersion: SemVer; kibanaVersion: SemVer;
theme$: Observable<CoreTheme>; theme$: Observable<CoreTheme>;
enableIndexActions: boolean;
} }
export const AppContextProvider = ({ export const AppContextProvider = ({

View file

@ -53,7 +53,8 @@ export async function mountManagementSection(
extensionsService: ExtensionsService, extensionsService: ExtensionsService,
isFleetEnabled: boolean, isFleetEnabled: boolean,
kibanaVersion: SemVer, kibanaVersion: SemVer,
enableIndexActions: boolean = true enableIndexActions: boolean = true,
enableLegacyTemplates: boolean = true
) { ) {
const { element, setBreadcrumbs, history, theme$ } = params; const { element, setBreadcrumbs, history, theme$ } = params;
const [core, startDependencies] = await coreSetup.getStartServices(); const [core, startDependencies] = await coreSetup.getStartServices();
@ -95,7 +96,10 @@ export async function mountManagementSection(
uiMetricService, uiMetricService,
extensionsService, extensionsService,
}, },
enableIndexActions, config: {
enableIndexActions,
enableLegacyTemplates,
},
history, history,
setBreadcrumbs, setBreadcrumbs,
uiSettings, uiSettings,

View file

@ -49,7 +49,9 @@ export class IndexActionsContextMenu extends Component {
this.setState({ isActionConfirmed }); this.setState({ isActionConfirmed });
}; };
panels({ services: { extensionsService }, core: { getUrlForApp } }) { panels({ services: { extensionsService }, core: { getUrlForApp } }) {
const { enableIndexActions } = this.context; const {
config: { enableIndexActions },
} = this.context;
const { const {
closeIndices, closeIndices,

View file

@ -19,6 +19,7 @@ import { getTemplateDetailsLink } from '../../services/routing';
import { saveTemplate, useLoadIndexTemplate } from '../../services/api'; import { saveTemplate, useLoadIndexTemplate } from '../../services/api';
import { getIsLegacyFromQueryParams } from '../../lib/index_templates'; import { getIsLegacyFromQueryParams } from '../../lib/index_templates';
import { attemptToURIDecode } from '../../../shared_imports'; import { attemptToURIDecode } from '../../../shared_imports';
import { useAppContext } from '../../app_context';
interface MatchParams { interface MatchParams {
name: string; name: string;
@ -32,7 +33,11 @@ export const TemplateClone: React.FunctionComponent<RouteComponentProps<MatchPar
history, history,
}) => { }) => {
const decodedTemplateName = attemptToURIDecode(name)!; const decodedTemplateName = attemptToURIDecode(name)!;
const isLegacy = getIsLegacyFromQueryParams(location); const {
config: { enableLegacyTemplates },
} = useAppContext();
// We don't expect the `legacy` query to be used when legacy templates are disabled, however, we add the `enableLegacyTemplates` check as a safeguard
const isLegacy = enableLegacyTemplates && getIsLegacyFromQueryParams(location);
const [isSaving, setIsSaving] = useState<boolean>(false); const [isSaving, setIsSaving] = useState<boolean>(false);
const [saveError, setSaveError] = useState<any>(null); const [saveError, setSaveError] = useState<any>(null);

View file

@ -18,12 +18,17 @@ import { TemplateForm } from '../../components';
import { breadcrumbService } from '../../services/breadcrumbs'; import { breadcrumbService } from '../../services/breadcrumbs';
import { saveTemplate } from '../../services/api'; import { saveTemplate } from '../../services/api';
import { getTemplateDetailsLink } from '../../services/routing'; import { getTemplateDetailsLink } from '../../services/routing';
import { useAppContext } from '../../app_context';
export const TemplateCreate: React.FunctionComponent<RouteComponentProps> = ({ history }) => { export const TemplateCreate: React.FunctionComponent<RouteComponentProps> = ({ history }) => {
const [isSaving, setIsSaving] = useState<boolean>(false); const [isSaving, setIsSaving] = useState<boolean>(false);
const [saveError, setSaveError] = useState<any>(null); const [saveError, setSaveError] = useState<any>(null);
const {
config: { enableLegacyTemplates },
} = useAppContext();
const search = parse(useLocation().search.substring(1)); const search = parse(useLocation().search.substring(1));
const isLegacy = Boolean(search.legacy); // We don't expect the `legacy` query to be used when legacy templates are disabled, however, we add the `enableLegacyTemplates` check as a safeguard
const isLegacy = enableLegacyTemplates && Boolean(search.legacy);
const onSave = async (template: TemplateDeserialized) => { const onSave = async (template: TemplateDeserialized) => {
const { name } = template; const { name } = template;

View file

@ -23,6 +23,7 @@ import { useLoadIndexTemplate, updateTemplate } from '../../services/api';
import { getTemplateDetailsLink } from '../../services/routing'; import { getTemplateDetailsLink } from '../../services/routing';
import { TemplateForm } from '../../components'; import { TemplateForm } from '../../components';
import { getIsLegacyFromQueryParams } from '../../lib/index_templates'; import { getIsLegacyFromQueryParams } from '../../lib/index_templates';
import { useAppContext } from '../../app_context';
interface MatchParams { interface MatchParams {
name: string; name: string;
@ -36,7 +37,12 @@ export const TemplateEdit: React.FunctionComponent<RouteComponentProps<MatchPara
history, history,
}) => { }) => {
const decodedTemplateName = attemptToURIDecode(name)!; const decodedTemplateName = attemptToURIDecode(name)!;
const isLegacy = getIsLegacyFromQueryParams(location); const {
config: { enableLegacyTemplates },
} = useAppContext();
// We don't expect the `legacy` query to be used when legacy templates are disabled, however, we add the enableLegacyTemplates check as a safeguard
const isLegacy = enableLegacyTemplates && getIsLegacyFromQueryParams(location);
const [isSaving, setIsSaving] = useState<boolean>(false); const [isSaving, setIsSaving] = useState<boolean>(false);
const [saveError, setSaveError] = useState<any>(null); const [saveError, setSaveError] = useState<any>(null);

View file

@ -39,6 +39,7 @@ export class IndexMgmtUIPlugin {
const { const {
ui: { enabled: isIndexManagementUiEnabled }, ui: { enabled: isIndexManagementUiEnabled },
enableIndexActions, enableIndexActions,
enableLegacyTemplates,
} = this.ctx.config.get<ClientConfigType>(); } = this.ctx.config.get<ClientConfigType>();
if (isIndexManagementUiEnabled) { if (isIndexManagementUiEnabled) {
@ -57,7 +58,8 @@ export class IndexMgmtUIPlugin {
this.extensionsService, this.extensionsService,
Boolean(fleet), Boolean(fleet),
kibanaVersion, kibanaVersion,
enableIndexActions enableIndexActions,
enableLegacyTemplates
); );
}, },
}); });

View file

@ -29,4 +29,5 @@ export interface ClientConfigType {
enabled: boolean; enabled: boolean;
}; };
enableIndexActions?: boolean; enableIndexActions?: boolean;
enableLegacyTemplates?: boolean;
} }

View file

@ -30,6 +30,14 @@ const schemaLatest = schema.object(
schema.boolean({ defaultValue: true }), schema.boolean({ defaultValue: true }),
schema.never() schema.never()
), ),
enableLegacyTemplates: schema.conditional(
schema.contextRef('serverless'),
true,
// Legacy templates functionality is disabled in serverless; refer to the serverless.yml file as the source of truth
// We take this approach in order to have a central place (serverless.yml) for serverless config across Kibana
schema.boolean({ defaultValue: true }),
schema.never()
),
}, },
{ defaultValue: undefined } { defaultValue: undefined }
); );
@ -38,6 +46,7 @@ const configLatest: PluginConfigDescriptor<IndexManagementConfig> = {
exposeToBrowser: { exposeToBrowser: {
ui: true, ui: true,
enableIndexActions: true, enableIndexActions: true,
enableLegacyTemplates: true,
}, },
schema: schemaLatest, schema: schemaLatest,
deprecations: () => [], deprecations: () => [],

View file

@ -12,6 +12,7 @@ import { Dependencies } from './types';
import { ApiRoutes } from './routes'; import { ApiRoutes } from './routes';
import { IndexDataEnricher } from './services'; import { IndexDataEnricher } from './services';
import { handleEsError } from './shared_imports'; import { handleEsError } from './shared_imports';
import { IndexManagementConfig } from './config';
export interface IndexManagementPluginSetup { export interface IndexManagementPluginSetup {
indexDataEnricher: { indexDataEnricher: {
@ -22,10 +23,12 @@ export interface IndexManagementPluginSetup {
export class IndexMgmtServerPlugin implements Plugin<IndexManagementPluginSetup, void, any, any> { export class IndexMgmtServerPlugin implements Plugin<IndexManagementPluginSetup, void, any, any> {
private readonly apiRoutes: ApiRoutes; private readonly apiRoutes: ApiRoutes;
private readonly indexDataEnricher: IndexDataEnricher; private readonly indexDataEnricher: IndexDataEnricher;
private readonly config: IndexManagementConfig;
constructor(initContext: PluginInitializerContext) { constructor(initContext: PluginInitializerContext) {
this.apiRoutes = new ApiRoutes(); this.apiRoutes = new ApiRoutes();
this.indexDataEnricher = new IndexDataEnricher(); this.indexDataEnricher = new IndexDataEnricher();
this.config = initContext.config.get();
} }
setup( setup(
@ -51,6 +54,7 @@ export class IndexMgmtServerPlugin implements Plugin<IndexManagementPluginSetup,
router: http.createRouter(), router: http.createRouter(),
config: { config: {
isSecurityEnabled: () => security !== undefined && security.license.isEnabled(), isSecurityEnabled: () => security !== undefined && security.license.isEnabled(),
isLegacyTemplatesEnabled: this.config.enableLegacyTemplates,
}, },
indexDataEnricher: this.indexDataEnricher, indexDataEnricher: this.indexDataEnricher,
lib: { lib: {

View file

@ -46,6 +46,7 @@ describe('GET privileges', () => {
router, router,
config: { config: {
isSecurityEnabled: () => true, isSecurityEnabled: () => true,
isLegacyTemplatesEnabled: true,
}, },
indexDataEnricher: mockedIndexDataEnricher, indexDataEnricher: mockedIndexDataEnricher,
lib: { lib: {
@ -112,6 +113,7 @@ describe('GET privileges', () => {
router, router,
config: { config: {
isSecurityEnabled: () => false, isSecurityEnabled: () => false,
isLegacyTemplatesEnabled: true,
}, },
indexDataEnricher: mockedIndexDataEnricher, indexDataEnricher: mockedIndexDataEnricher,
lib: { lib: {

View file

@ -17,7 +17,7 @@ import { getCloudManagedTemplatePrefix } from '../../../lib/get_managed_template
import { RouteDependencies } from '../../../types'; import { RouteDependencies } from '../../../types';
import { addBasePath } from '..'; import { addBasePath } from '..';
export function registerGetAllRoute({ router, lib: { handleEsError } }: RouteDependencies) { export function registerGetAllRoute({ router, config, lib: { handleEsError } }: RouteDependencies) {
router.get( router.get(
{ path: addBasePath('/index_templates'), validate: false }, { path: addBasePath('/index_templates'), validate: false },
async (context, request, response) => { async (context, request, response) => {
@ -25,17 +25,24 @@ export function registerGetAllRoute({ router, lib: { handleEsError } }: RouteDep
try { try {
const cloudManagedTemplatePrefix = await getCloudManagedTemplatePrefix(client); const cloudManagedTemplatePrefix = await getCloudManagedTemplatePrefix(client);
const legacyTemplatesEs = await client.asCurrentUser.indices.getTemplate();
const { index_templates: templatesEs } = const { index_templates: templatesEs } =
await client.asCurrentUser.indices.getIndexTemplate(); await client.asCurrentUser.indices.getIndexTemplate();
// @ts-expect-error TemplateSerialized.index_patterns not compatible with IndicesIndexTemplate.index_patterns
const templates = deserializeTemplateList(templatesEs, cloudManagedTemplatePrefix);
if (config.isLegacyTemplatesEnabled === false) {
// If isLegacyTemplatesEnabled=false, we do not want to fetch legacy templates and return an empty array;
// we retain the same response format to limit changes required on the client
return response.ok({ body: { templates, legacyTemplates: [] } });
}
const legacyTemplatesEs = await client.asCurrentUser.indices.getTemplate();
const legacyTemplates = deserializeLegacyTemplateList( const legacyTemplates = deserializeLegacyTemplateList(
legacyTemplatesEs, legacyTemplatesEs,
cloudManagedTemplatePrefix cloudManagedTemplatePrefix
); );
// @ts-expect-error TemplateSerialized.index_patterns not compatible with IndicesIndexTemplate.index_patterns
const templates = deserializeTemplateList(templatesEs, cloudManagedTemplatePrefix);
const body = { const body = {
templates, templates,
@ -59,7 +66,7 @@ const querySchema = schema.object({
legacy: schema.maybe(schema.oneOf([schema.literal('true'), schema.literal('false')])), legacy: schema.maybe(schema.oneOf([schema.literal('true'), schema.literal('false')])),
}); });
export function registerGetOneRoute({ router, lib: { handleEsError } }: RouteDependencies) { export function registerGetOneRoute({ router, config, lib: { handleEsError } }: RouteDependencies) {
router.get( router.get(
{ {
path: addBasePath('/index_templates/{name}'), path: addBasePath('/index_templates/{name}'),
@ -68,7 +75,10 @@ export function registerGetOneRoute({ router, lib: { handleEsError } }: RouteDep
async (context, request, response) => { async (context, request, response) => {
const { client } = (await context.core).elasticsearch; const { client } = (await context.core).elasticsearch;
const { name } = request.params as TypeOf<typeof paramsSchema>; const { name } = request.params as TypeOf<typeof paramsSchema>;
const isLegacy = (request.query as TypeOf<typeof querySchema>).legacy === 'true'; // We don't expect the `legacy` query to be used when legacy templates are disabled, however, we add the `enableLegacyTemplates` check as a safeguard
const isLegacy =
config.isLegacyTemplatesEnabled !== false &&
(request.query as TypeOf<typeof querySchema>).legacy === 'true';
try { try {
const cloudManagedTemplatePrefix = await getCloudManagedTemplatePrefix(client); const cloudManagedTemplatePrefix = await getCloudManagedTemplatePrefix(client);

View file

@ -12,6 +12,7 @@ import type { RouteDependencies } from '../../types';
export const routeDependencies: Omit<RouteDependencies, 'router'> = { export const routeDependencies: Omit<RouteDependencies, 'router'> = {
config: { config: {
isSecurityEnabled: jest.fn().mockReturnValue(true), isSecurityEnabled: jest.fn().mockReturnValue(true),
isLegacyTemplatesEnabled: true,
}, },
indexDataEnricher: new IndexDataEnricher(), indexDataEnricher: new IndexDataEnricher(),
lib: { lib: {

View file

@ -23,6 +23,7 @@ export interface RouteDependencies {
router: IRouter; router: IRouter;
config: { config: {
isSecurityEnabled: () => boolean; isSecurityEnabled: () => boolean;
isLegacyTemplatesEnabled: boolean;
}; };
indexDataEnricher: IndexDataEnricher; indexDataEnricher: IndexDataEnricher;
lib: { lib: {

View file

@ -13,5 +13,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./spaces')); loadTestFile(require.resolve('./spaces'));
loadTestFile(require.resolve('./security_response_headers')); loadTestFile(require.resolve('./security_response_headers'));
loadTestFile(require.resolve('./rollups')); loadTestFile(require.resolve('./rollups'));
loadTestFile(require.resolve('./index_management'));
}); });
} }

View file

@ -0,0 +1,14 @@
/*
* 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 { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ loadTestFile }: FtrProviderContext) {
describe('Index Management APIs', function () {
loadTestFile(require.resolve('./index_templates'));
});
}

View file

@ -0,0 +1,94 @@
/*
* 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 expect from 'expect';
import { FtrProviderContext } from '../../../ftr_provider_context';
const API_BASE_PATH = '/api/index_management';
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const es = getService('es');
const log = getService('log');
describe('Index templates', function () {
const templateName = `template-${Math.random()}`;
const indexTemplate = {
name: templateName,
body: {
index_patterns: ['test*'],
},
};
before(async () => {
// Create a new index template to test against
try {
await es.indices.putIndexTemplate(indexTemplate);
} catch (err) {
log.debug('[Setup error] Error creating index template');
throw err;
}
});
after(async () => {
// Cleanup template created for testing purposes
try {
await es.indices.deleteIndexTemplate({
name: templateName,
});
} catch (err) {
log.debug('[Cleanup error] Error deleting index template');
throw err;
}
});
describe('get all', () => {
it('should list all the index templates with the expected parameters', async () => {
const { body: allTemplates } = await supertest
.get(`${API_BASE_PATH}/index_templates`)
.set('kbn-xsrf', 'xxx')
.set('x-elastic-internal-origin', 'xxx')
.expect(200);
// Legacy templates are not applicable on serverless
expect(allTemplates.legacyTemplates.length).toEqual(0);
const indexTemplateFound = allTemplates.templates.find(
(template: { name: string }) => template.name === indexTemplate.name
);
expect(indexTemplateFound).toBeTruthy();
const expectedKeys = [
'name',
'indexPatterns',
'hasSettings',
'hasAliases',
'hasMappings',
'_kbnMeta',
].sort();
expect(Object.keys(indexTemplateFound).sort()).toEqual(expectedKeys);
});
});
describe('get one', () => {
it('should return an index template with the expected parameters', async () => {
const { body } = await supertest
.get(`${API_BASE_PATH}/index_templates/${templateName}`)
.set('kbn-xsrf', 'xxx')
.set('x-elastic-internal-origin', 'xxx')
.expect(200);
const expectedKeys = ['name', 'indexPatterns', 'template', '_kbnMeta'].sort();
expect(body.name).toEqual(templateName);
expect(Object.keys(body).sort()).toEqual(expectedKeys);
});
});
});
}

View file

@ -55,6 +55,9 @@ export function createTestConfig(options: CreateTestConfigOptions) {
management: { management: {
pathname: '/app/management', pathname: '/app/management',
}, },
indexManagement: {
pathname: '/app/management/data/index_management',
},
}, },
// choose where screenshots should be saved // choose where screenshots should be saved
screenshots: { screenshots: {

View file

@ -14,5 +14,8 @@ export default function ({ loadTestFile }: FtrProviderContext) {
// platform security // platform security
loadTestFile(require.resolve('./security/navigation/avatar_menu')); loadTestFile(require.resolve('./security/navigation/avatar_menu'));
// Management
loadTestFile(require.resolve('./index_management'));
}); });
} }

View file

@ -0,0 +1,14 @@
/*
* 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 { FtrProviderContext } from '../../../ftr_provider_context';
export default ({ loadTestFile }: FtrProviderContext) => {
describe('Index Management', function () {
loadTestFile(require.resolve('./index_templates'));
});
};

View file

@ -0,0 +1,35 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../../ftr_provider_context';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const testSubjects = getService('testSubjects');
const pageObjects = getPageObjects(['common', 'indexManagement', 'header']);
const browser = getService('browser');
const security = getService('security');
const retry = getService('retry');
describe('Index Templates', function () {
before(async () => {
await security.testUser.setRoles(['index_management_user']);
await pageObjects.common.navigateToApp('indexManagement');
// Navigate to the index templates tab
await pageObjects.indexManagement.changeTabs('templatesTab');
});
it('renders the index templates tab', async () => {
await retry.waitFor('index templates list to be visible', async () => {
return await testSubjects.exists('templateList');
});
const url = await browser.getCurrentUrl();
expect(url).to.contain(`/templates`);
});
});
};