Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Index details page] Implement index actions #164741

Merged
merged 10 commits into from
Aug 28, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ import {
IndexDetailsSection,
} from '../../../public/application/sections/home/index_list/details_page';
import { WithAppDependencies } from '../helpers';
import { testIndexName } from './mocks';

let routerMock: typeof reactRouterMock;
const testBedConfig: AsyncTestBedConfig = {
memoryRouter: {
initialEntries: [`/indices/test_index`],
initialEntries: [`/indices/${testIndexName}`],
componentRoutePath: `/indices/:indexName/:indexDetailsSection?`,
onRouter: (router) => {
routerMock = router;
Expand All @@ -42,6 +43,9 @@ export interface IndexDetailsPageTestBed extends TestBed {
contextMenu: {
clickManageIndexButton: () => Promise<void>;
isOpened: () => boolean;
clickIndexAction: (indexAction: string) => Promise<void>;
confirmForcemerge: (numSegments: string) => Promise<void>;
confirmDelete: () => Promise<void>;
};
errorSection: {
isDisplayed: () => boolean;
Expand Down Expand Up @@ -108,6 +112,28 @@ export const setup = async (
isOpened: () => {
return exists('indexContextMenu');
},
clickIndexAction: async (indexAction: string) => {
await act(async () => {
find(`indexContextMenu.${indexAction}`).simulate('click');
});
component.update();
},
confirmForcemerge: async (numSegments: string) => {
await act(async () => {
testBed.form.setInputValue('indexActionsForcemergeNumSegments', numSegments);
});
component.update();
await act(async () => {
find('confirmModalConfirmButton').simulate('click');
});
component.update();
},
confirmDelete: async () => {
await act(async () => {
find('confirmModalConfirmButton').simulate('click');
});
component.update();
},
};
return {
...testBed,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { setupEnvironment } from '../helpers';
import { IndexDetailsPageTestBed, setup } from './index_details_page.helpers';
import { act } from 'react-dom/test-utils';
import { IndexDetailsSection } from '../../../public/application/sections/home/index_list/details_page';
import { testIndexMock } from './mocks';
import { testIndexMock, testIndexName } from './mocks';
import { API_BASE_PATH, INTERNAL_API_BASE_PATH } from '../../../common';

describe('<IndexDetailsPage />', () => {
let testBed: IndexDetailsPageTestBed;
Expand All @@ -19,8 +20,8 @@ describe('<IndexDetailsPage />', () => {
beforeEach(async () => {
const mockEnvironment = setupEnvironment();
({ httpSetup, httpRequestsMockHelpers } = mockEnvironment);
// test_index is configured in initialEntries of the memory router
httpRequestsMockHelpers.setLoadIndexDetailsResponse('test_index', testIndexMock);
// testIndexName is configured in initialEntries of the memory router
httpRequestsMockHelpers.setLoadIndexDetailsResponse(testIndexName, testIndexMock);

await act(async () => {
testBed = await setup(httpSetup, {
Expand All @@ -36,9 +37,9 @@ describe('<IndexDetailsPage />', () => {

describe('error section', () => {
beforeEach(async () => {
httpRequestsMockHelpers.setLoadIndexDetailsResponse('test_index', undefined, {
httpRequestsMockHelpers.setLoadIndexDetailsResponse(testIndexName, undefined, {
statusCode: 400,
message: 'Data for index .apm-agent-configuration was not found',
message: `Data for index ${testIndexName} was not found`,
});
await act(async () => {
testBed = await setup(httpSetup);
Expand All @@ -59,10 +60,17 @@ describe('<IndexDetailsPage />', () => {
});
});

it('loads index details from the API', async () => {
expect(httpSetup.get).toHaveBeenLastCalledWith(
`${INTERNAL_API_BASE_PATH}/indices/${testIndexName}`,
{ asSystemRequest: undefined, body: undefined, query: undefined, version: undefined }
);
});

it('displays index name in the header', () => {
const header = testBed.actions.getHeader();
// test_index is configured in initialEntries of the memory router
expect(header).toEqual('test_index');
// testIndexName is configured in initialEntries of the memory router
expect(header).toEqual(testIndexName);
});

it('defaults to overview tab', () => {
Expand Down Expand Up @@ -106,12 +114,140 @@ describe('<IndexDetailsPage />', () => {
expect(testBed.actions.discoverLinkExists()).toBe(true);
});

it('opens an index context menu when "manage index" button is clicked', async () => {
const {
actions: { contextMenu },
} = testBed;
expect(contextMenu.isOpened()).toBe(false);
await testBed.actions.contextMenu.clickManageIndexButton();
expect(contextMenu.isOpened()).toBe(true);
describe('context menu', () => {
it('opens an index context menu when "manage index" button is clicked', async () => {
expect(testBed.actions.contextMenu.isOpened()).toBe(false);
await testBed.actions.contextMenu.clickManageIndexButton();
expect(testBed.actions.contextMenu.isOpened()).toBe(true);
});

it('closes an index', async () => {
// already sent 1 request while setting up the component
const numberOfRequests = 1;
expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests);

await testBed.actions.contextMenu.clickManageIndexButton();
await testBed.actions.contextMenu.clickIndexAction('closeIndexMenuButton');
expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/close`, {
body: JSON.stringify({ indices: [testIndexName] }),
});
expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1);
});

it('opens an index', async () => {
httpRequestsMockHelpers.setLoadIndexDetailsResponse(testIndexName, {
...testIndexMock,
status: 'close',
});

await act(async () => {
testBed = await setup(httpSetup);
});
testBed.component.update();

// already sent 2 requests while setting up the component
const numberOfRequests = 2;
expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests);

await testBed.actions.contextMenu.clickManageIndexButton();
await testBed.actions.contextMenu.clickIndexAction('openIndexMenuButton');
expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/open`, {
body: JSON.stringify({ indices: [testIndexName] }),
});
expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1);
});

it('forcemerges an index', async () => {
// already sent 1 request while setting up the component
const numberOfRequests = 1;
expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests);

await testBed.actions.contextMenu.clickManageIndexButton();
await testBed.actions.contextMenu.clickIndexAction('forcemergeIndexMenuButton');
await testBed.actions.contextMenu.confirmForcemerge('2');
expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/forcemerge`, {
body: JSON.stringify({ indices: [testIndexName], maxNumSegments: '2' }),
});
expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1);
});

it('refreshes an index', async () => {
// already sent 1 request while setting up the component
const numberOfRequests = 1;
expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests);

await testBed.actions.contextMenu.clickManageIndexButton();
await testBed.actions.contextMenu.clickIndexAction('refreshIndexMenuButton');
expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/refresh`, {
body: JSON.stringify({ indices: [testIndexName] }),
});
expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1);
});

it(`clears an index's cache`, async () => {
// already sent 1 request while setting up the component
const numberOfRequests = 1;
expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests);

await testBed.actions.contextMenu.clickManageIndexButton();
await testBed.actions.contextMenu.clickIndexAction('clearCacheIndexMenuButton');
expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/clear_cache`, {
body: JSON.stringify({ indices: [testIndexName] }),
});
expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1);
});

it(`flushes an index`, async () => {
// already sent 1 request while setting up the component
const numberOfRequests = 1;
expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests);

await testBed.actions.contextMenu.clickManageIndexButton();
await testBed.actions.contextMenu.clickIndexAction('flushIndexMenuButton');
expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/flush`, {
body: JSON.stringify({ indices: [testIndexName] }),
});
expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1);
});

it(`deletes an index`, async () => {
jest.spyOn(testBed.routerMock.history, 'push');
// already sent 1 request while setting up the component
const numberOfRequests = 1;
expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests);

await testBed.actions.contextMenu.clickManageIndexButton();
await testBed.actions.contextMenu.clickIndexAction('deleteIndexMenuButton');
await testBed.actions.contextMenu.confirmDelete();
expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/delete`, {
body: JSON.stringify({ indices: [testIndexName] }),
});

expect(testBed.routerMock.history.push).toHaveBeenCalledTimes(1);
expect(testBed.routerMock.history.push).toHaveBeenCalledWith('/indices');
});

it(`unfreezes a frozen index`, async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove this functionality (separate PR is OK). AFAICT frozen indices and the unfreeze/freeze APIs were deprecated in 7.14 (docs).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, I created an issue to track this work.

httpRequestsMockHelpers.setLoadIndexDetailsResponse(testIndexName, {
...testIndexMock,
isFrozen: true,
});

await act(async () => {
testBed = await setup(httpSetup);
});
testBed.component.update();

// already sent 1 request while setting up the component
const numberOfRequests = 2;
expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests);

await testBed.actions.contextMenu.clickManageIndexButton();
await testBed.actions.contextMenu.clickIndexAction('unfreezeIndexMenuButton');
expect(httpSetup.post).toHaveBeenCalledWith(`${API_BASE_PATH}/indices/unfreeze`, {
body: JSON.stringify({ indices: [testIndexName] }),
});
expect(httpSetup.get).toHaveBeenCalledTimes(numberOfRequests + 1);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@

import { Index } from '../../../public';

export const testIndexName = 'test_index';
export const testIndexMock: Index = {
health: 'green',
status: 'open',
name: 'test_index',
name: testIndexName,
uuid: 'test1234',
primary: '1',
replica: '1',
Expand All @@ -21,7 +22,6 @@ export const testIndexMock: Index = {
isFrozen: false,
aliases: 'none',
hidden: false,
// @ts-expect-error ts upgrade v4.7.4
isRollupIndex: false,
ilm: {
index: 'test_index',
Expand Down
9 changes: 9 additions & 0 deletions x-pack/plugins/index_management/common/types/indices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ export interface Index {
hidden: boolean;
aliases: string | string[];
data_stream?: string;

// The types below are added by extension services if corresponding plugins are enabled (ILM, Rollup, CCR)
isRollupIndex?: boolean;
ilm?: {
index: string;
managed: boolean;
};
isFollowerIndex?: boolean;

// The types from here below represent information returned from the index stats API;
// treated optional as the stats API is not available on serverless
health?: HealthStatus;
Expand Down
Loading