mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Index Management] Disable index stats on serverless (#163849)
This commit is contained in:
parent
615c450b37
commit
a14f76d96c
29 changed files with 406 additions and 102 deletions
|
@ -37,6 +37,8 @@ xpack.license_management.enabled: false
|
|||
xpack.index_management.enableIndexActions: false
|
||||
# Disable legacy index templates from Index Management UI
|
||||
xpack.index_management.enableLegacyTemplates: false
|
||||
# Disable index stats information from Index Management UI
|
||||
xpack.index_management.enableIndexStats: false
|
||||
|
||||
# Keep deeplinks visible so that they are shown in the sidenav
|
||||
dev_tools.deeplinks.navLinkStatus: visible
|
||||
|
|
|
@ -243,6 +243,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
|||
'xpack.index_management.enableIndexActions (any)',
|
||||
'xpack.index_management.enableLegacyTemplates (any)',
|
||||
'xpack.index_management.dev.enableIndexDetailsPage (boolean)',
|
||||
'xpack.index_management.enableIndexStats (any)',
|
||||
'xpack.infra.sources.default.fields.message (array)',
|
||||
/**
|
||||
* xpack.infra.logs is conditional and will resolve to an object of properties
|
||||
|
|
|
@ -80,7 +80,7 @@ const indexWithLifecyclePolicy: Index = {
|
|||
},
|
||||
};
|
||||
|
||||
const indexWithLifecycleError = {
|
||||
const indexWithLifecycleError: Index = {
|
||||
health: 'yellow',
|
||||
status: 'open',
|
||||
name: 'testy3',
|
||||
|
|
|
@ -60,6 +60,7 @@ const appDependencies = {
|
|||
config: {
|
||||
enableLegacyTemplates: true,
|
||||
enableIndexActions: true,
|
||||
enableIndexStats: true,
|
||||
},
|
||||
} as any;
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ export type TestSubjects =
|
|||
| 'deleteSystemTemplateCallOut'
|
||||
| 'deleteTemplateButton'
|
||||
| 'deleteTemplatesConfirmation'
|
||||
| 'descriptionTitle'
|
||||
| 'documentationLink'
|
||||
| 'emptyPrompt'
|
||||
| 'forcemergeIndexMenuButton'
|
||||
|
|
|
@ -62,14 +62,13 @@ describe('<IndexManagementHome />', () => {
|
|||
beforeEach(async () => {
|
||||
httpRequestsMockHelpers.setLoadIndicesResponse([]);
|
||||
|
||||
testBed = await setup(httpSetup);
|
||||
|
||||
await act(async () => {
|
||||
const { component } = testBed;
|
||||
|
||||
await nextTick();
|
||||
component.update();
|
||||
testBed = await setup(httpSetup);
|
||||
});
|
||||
|
||||
const { component } = testBed;
|
||||
|
||||
component.update();
|
||||
});
|
||||
|
||||
test('toggles the include hidden button through URL hash correctly', () => {
|
||||
|
@ -423,4 +422,103 @@ describe('<IndexManagementHome />', () => {
|
|||
expect(exists('updateIndexSettingsErrorCallout')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Index stats', () => {
|
||||
const indexName = 'test';
|
||||
|
||||
beforeEach(async () => {
|
||||
httpRequestsMockHelpers.setLoadIndicesResponse([createNonDataStreamIndex(indexName)]);
|
||||
|
||||
await act(async () => {
|
||||
testBed = await setup(httpSetup);
|
||||
});
|
||||
|
||||
const { component } = testBed;
|
||||
|
||||
component.update();
|
||||
});
|
||||
|
||||
test('renders the table column with index stats by default', () => {
|
||||
const { table } = testBed;
|
||||
const { tableCellsValues } = table.getMetaData('indexTable');
|
||||
|
||||
expect(tableCellsValues).toEqual([
|
||||
['', 'test', 'green', 'open', '1', '1', '10000', '156kb', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
test('renders index stats in details flyout by default', async () => {
|
||||
const { component, find } = testBed;
|
||||
|
||||
await act(async () => {
|
||||
find('indexTableIndexNameLink').at(0).simulate('click');
|
||||
});
|
||||
|
||||
component.update();
|
||||
|
||||
const descriptions = find('descriptionTitle');
|
||||
|
||||
const descriptionText = descriptions
|
||||
.map((description) => {
|
||||
return description.text();
|
||||
})
|
||||
.sort();
|
||||
|
||||
expect(descriptionText).toEqual([
|
||||
'Aliases',
|
||||
'Docs count',
|
||||
'Docs deleted',
|
||||
'Health',
|
||||
'Primaries',
|
||||
'Primary storage size',
|
||||
'Replicas',
|
||||
'Status',
|
||||
'Storage size',
|
||||
]);
|
||||
});
|
||||
|
||||
describe('Disabled', () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
testBed = await setup(httpSetup, {
|
||||
config: {
|
||||
enableLegacyTemplates: true,
|
||||
enableIndexActions: true,
|
||||
enableIndexStats: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const { component } = testBed;
|
||||
|
||||
component.update();
|
||||
});
|
||||
|
||||
test('hides index stats information from table', async () => {
|
||||
const { table } = testBed;
|
||||
const { tableCellsValues } = table.getMetaData('indexTable');
|
||||
|
||||
expect(tableCellsValues).toEqual([['', 'test', '1', '1', '']]);
|
||||
});
|
||||
|
||||
test('hides index stats information from details panel', async () => {
|
||||
const { component, find } = testBed;
|
||||
await act(async () => {
|
||||
find('indexTableIndexNameLink').at(0).simulate('click');
|
||||
});
|
||||
|
||||
component.update();
|
||||
|
||||
const descriptions = find('descriptionTitle');
|
||||
|
||||
const descriptionText = descriptions
|
||||
.map((description) => {
|
||||
return description.text();
|
||||
})
|
||||
.sort();
|
||||
|
||||
expect(descriptionText).toEqual(['Aliases', 'Primaries', 'Replicas']);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -172,6 +172,7 @@ describe('index table', () => {
|
|||
config: {
|
||||
enableLegacyTemplates: true,
|
||||
enableIndexActions: true,
|
||||
enableIndexStats: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -5,6 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
HealthStatus,
|
||||
IndicesStatsIndexMetadataState,
|
||||
Uuid,
|
||||
} from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
interface IndexModule {
|
||||
number_of_shards: number | string;
|
||||
codec: string;
|
||||
|
@ -50,21 +56,21 @@ export interface IndexSettings {
|
|||
analysis?: AnalysisModule;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface Index {
|
||||
health?: string;
|
||||
status?: string;
|
||||
name: string;
|
||||
uuid?: string;
|
||||
primary?: number | string;
|
||||
replica?: number | string;
|
||||
documents: number;
|
||||
documents_deleted: number;
|
||||
size: string;
|
||||
primary_size: string;
|
||||
isFrozen: boolean;
|
||||
hidden: boolean;
|
||||
aliases: string | string[];
|
||||
data_stream?: string;
|
||||
[key: string]: any;
|
||||
// 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;
|
||||
status?: IndicesStatsIndexMetadataState;
|
||||
uuid?: Uuid;
|
||||
documents?: number;
|
||||
size?: string;
|
||||
primary_size?: string;
|
||||
documents_deleted?: number;
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ export interface AppDependencies {
|
|||
enableIndexActions: boolean;
|
||||
enableLegacyTemplates: boolean;
|
||||
enableIndexDetailsPage: boolean;
|
||||
enableIndexStats: boolean;
|
||||
};
|
||||
history: ScopedHistory;
|
||||
setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs'];
|
||||
|
|
|
@ -56,6 +56,7 @@ export async function mountManagementSection({
|
|||
enableIndexActions = true,
|
||||
enableLegacyTemplates = true,
|
||||
enableIndexDetailsPage = false,
|
||||
enableIndexStats = true,
|
||||
}: {
|
||||
coreSetup: CoreSetup<StartDependencies>;
|
||||
usageCollection: UsageCollectionSetup;
|
||||
|
@ -66,6 +67,7 @@ export async function mountManagementSection({
|
|||
enableIndexActions?: boolean;
|
||||
enableLegacyTemplates?: boolean;
|
||||
enableIndexDetailsPage?: boolean;
|
||||
enableIndexStats?: boolean;
|
||||
}) {
|
||||
const { element, setBreadcrumbs, history, theme$ } = params;
|
||||
const [core, startDependencies] = await coreSetup.getStartServices();
|
||||
|
@ -111,6 +113,7 @@ export async function mountManagementSection({
|
|||
enableIndexActions,
|
||||
enableLegacyTemplates,
|
||||
enableIndexDetailsPage,
|
||||
enableIndexStats,
|
||||
},
|
||||
history,
|
||||
setBreadcrumbs,
|
||||
|
|
|
@ -34,7 +34,7 @@ import { IndexActionsContextMenu } from '../index_actions_context_menu';
|
|||
import { ShowJson } from './show_json';
|
||||
import { Summary } from './summary';
|
||||
import { EditSettingsJson } from './edit_settings_json';
|
||||
import { useServices } from '../../../../app_context';
|
||||
import { useServices, useAppContext } from '../../../../app_context';
|
||||
import { renderDiscoverLink } from '../../../../lib/render_discover_link';
|
||||
|
||||
const tabToHumanizedMap = {
|
||||
|
@ -58,12 +58,19 @@ const tabToHumanizedMap = {
|
|||
),
|
||||
};
|
||||
|
||||
const tabs = [TAB_SUMMARY, TAB_SETTINGS, TAB_MAPPING, TAB_STATS, TAB_EDIT_SETTINGS];
|
||||
const getTabs = (showStats) => {
|
||||
if (showStats) {
|
||||
return [TAB_SUMMARY, TAB_SETTINGS, TAB_MAPPING, TAB_STATS, TAB_EDIT_SETTINGS];
|
||||
}
|
||||
return [TAB_SUMMARY, TAB_SETTINGS, TAB_MAPPING, TAB_EDIT_SETTINGS];
|
||||
};
|
||||
|
||||
export const DetailPanel = ({ panelType, indexName, index, openDetailPanel, closeDetailPanel }) => {
|
||||
const { extensionsService } = useServices();
|
||||
const { config } = useAppContext();
|
||||
|
||||
const renderTabs = () => {
|
||||
const tabs = getTabs(config.enableIndexStats);
|
||||
return tabs.map((tab, i) => {
|
||||
const isSelected = tab === panelType;
|
||||
return (
|
||||
|
|
|
@ -21,36 +21,43 @@ import {
|
|||
import { DataHealth } from '../../../../../components';
|
||||
import { AppContextConsumer } from '../../../../../app_context';
|
||||
|
||||
const getHeaders = () => {
|
||||
return {
|
||||
health: i18n.translate('xpack.idxMgmt.summary.headers.healthHeader', {
|
||||
defaultMessage: 'Health',
|
||||
}),
|
||||
status: i18n.translate('xpack.idxMgmt.summary.headers.statusHeader', {
|
||||
defaultMessage: 'Status',
|
||||
}),
|
||||
const getHeaders = (showStats) => {
|
||||
const baseHeaders = {
|
||||
primary: i18n.translate('xpack.idxMgmt.summary.headers.primaryHeader', {
|
||||
defaultMessage: 'Primaries',
|
||||
}),
|
||||
replica: i18n.translate('xpack.idxMgmt.summary.headers.replicaHeader', {
|
||||
defaultMessage: 'Replicas',
|
||||
}),
|
||||
documents: i18n.translate('xpack.idxMgmt.summary.headers.documentsHeader', {
|
||||
defaultMessage: 'Docs count',
|
||||
}),
|
||||
documents_deleted: i18n.translate('xpack.idxMgmt.summary.headers.deletedDocumentsHeader', {
|
||||
defaultMessage: 'Docs deleted',
|
||||
}),
|
||||
size: i18n.translate('xpack.idxMgmt.summary.headers.storageSizeHeader', {
|
||||
defaultMessage: 'Storage size',
|
||||
}),
|
||||
primary_size: i18n.translate('xpack.idxMgmt.summary.headers.primaryStorageSizeHeader', {
|
||||
defaultMessage: 'Primary storage size',
|
||||
}),
|
||||
aliases: i18n.translate('xpack.idxMgmt.summary.headers.aliases', {
|
||||
defaultMessage: 'Aliases',
|
||||
}),
|
||||
};
|
||||
|
||||
if (showStats) {
|
||||
return {
|
||||
...baseHeaders,
|
||||
health: i18n.translate('xpack.idxMgmt.summary.headers.healthHeader', {
|
||||
defaultMessage: 'Health',
|
||||
}),
|
||||
status: i18n.translate('xpack.idxMgmt.summary.headers.statusHeader', {
|
||||
defaultMessage: 'Status',
|
||||
}),
|
||||
documents: i18n.translate('xpack.idxMgmt.summary.headers.documentsHeader', {
|
||||
defaultMessage: 'Docs count',
|
||||
}),
|
||||
documents_deleted: i18n.translate('xpack.idxMgmt.summary.headers.deletedDocumentsHeader', {
|
||||
defaultMessage: 'Docs deleted',
|
||||
}),
|
||||
size: i18n.translate('xpack.idxMgmt.summary.headers.storageSizeHeader', {
|
||||
defaultMessage: 'Storage size',
|
||||
}),
|
||||
primary_size: i18n.translate('xpack.idxMgmt.summary.headers.primaryStorageSizeHeader', {
|
||||
defaultMessage: 'Primary storage size',
|
||||
}),
|
||||
};
|
||||
}
|
||||
return baseHeaders;
|
||||
};
|
||||
|
||||
export class Summary extends React.PureComponent {
|
||||
|
@ -67,9 +74,9 @@ export class Summary extends React.PureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
buildRows() {
|
||||
buildRows(config) {
|
||||
const { index } = this.props;
|
||||
const headers = getHeaders();
|
||||
const headers = getHeaders(config.enableIndexStats);
|
||||
const rows = {
|
||||
left: [],
|
||||
right: [],
|
||||
|
@ -84,7 +91,7 @@ export class Summary extends React.PureComponent {
|
|||
content = content.join(', ');
|
||||
}
|
||||
const cell = [
|
||||
<EuiDescriptionListTitle key={fieldName}>
|
||||
<EuiDescriptionListTitle key={fieldName} data-test-subj="descriptionTitle">
|
||||
<strong>{headers[fieldName]}</strong>
|
||||
</EuiDescriptionListTitle>,
|
||||
<EuiDescriptionListDescription key={fieldName + '_desc'}>
|
||||
|
@ -103,8 +110,8 @@ export class Summary extends React.PureComponent {
|
|||
render() {
|
||||
return (
|
||||
<AppContextConsumer>
|
||||
{({ services, core }) => {
|
||||
const { left, right } = this.buildRows();
|
||||
{({ services, core, config }) => {
|
||||
const { left, right } = this.buildRows(config);
|
||||
const additionalContent = this.getAdditionalContent(
|
||||
services.extensionsService,
|
||||
core.getUrlForApp
|
||||
|
|
|
@ -51,31 +51,46 @@ import { renderBadges } from '../../../../lib/render_badges';
|
|||
import { NoMatch, DataHealth } from '../../../../components';
|
||||
import { IndexActionsContextMenu } from '../index_actions_context_menu';
|
||||
|
||||
const HEADERS = {
|
||||
name: i18n.translate('xpack.idxMgmt.indexTable.headers.nameHeader', {
|
||||
const getHeaders = ({ showIndexStats }) => {
|
||||
const headers = {};
|
||||
|
||||
headers.name = i18n.translate('xpack.idxMgmt.indexTable.headers.nameHeader', {
|
||||
defaultMessage: 'Name',
|
||||
}),
|
||||
health: i18n.translate('xpack.idxMgmt.indexTable.headers.healthHeader', {
|
||||
defaultMessage: 'Health',
|
||||
}),
|
||||
status: i18n.translate('xpack.idxMgmt.indexTable.headers.statusHeader', {
|
||||
defaultMessage: 'Status',
|
||||
}),
|
||||
primary: i18n.translate('xpack.idxMgmt.indexTable.headers.primaryHeader', {
|
||||
});
|
||||
|
||||
if (showIndexStats) {
|
||||
headers.health = i18n.translate('xpack.idxMgmt.indexTable.headers.healthHeader', {
|
||||
defaultMessage: 'Health',
|
||||
});
|
||||
|
||||
headers.status = i18n.translate('xpack.idxMgmt.indexTable.headers.statusHeader', {
|
||||
defaultMessage: 'Status',
|
||||
});
|
||||
}
|
||||
|
||||
headers.primary = i18n.translate('xpack.idxMgmt.indexTable.headers.primaryHeader', {
|
||||
defaultMessage: 'Primaries',
|
||||
}),
|
||||
replica: i18n.translate('xpack.idxMgmt.indexTable.headers.replicaHeader', {
|
||||
});
|
||||
|
||||
headers.replica = i18n.translate('xpack.idxMgmt.indexTable.headers.replicaHeader', {
|
||||
defaultMessage: 'Replicas',
|
||||
}),
|
||||
documents: i18n.translate('xpack.idxMgmt.indexTable.headers.documentsHeader', {
|
||||
defaultMessage: 'Docs count',
|
||||
}),
|
||||
size: i18n.translate('xpack.idxMgmt.indexTable.headers.storageSizeHeader', {
|
||||
defaultMessage: 'Storage size',
|
||||
}),
|
||||
data_stream: i18n.translate('xpack.idxMgmt.indexTable.headers.dataStreamHeader', {
|
||||
});
|
||||
|
||||
if (showIndexStats) {
|
||||
headers.documents = i18n.translate('xpack.idxMgmt.indexTable.headers.documentsHeader', {
|
||||
defaultMessage: 'Docs count',
|
||||
});
|
||||
|
||||
headers.size = i18n.translate('xpack.idxMgmt.indexTable.headers.storageSizeHeader', {
|
||||
defaultMessage: 'Storage size',
|
||||
});
|
||||
}
|
||||
|
||||
headers.data_stream = i18n.translate('xpack.idxMgmt.indexTable.headers.dataStreamHeader', {
|
||||
defaultMessage: 'Data stream',
|
||||
}),
|
||||
});
|
||||
|
||||
return headers;
|
||||
};
|
||||
|
||||
export class IndexTable extends Component {
|
||||
|
@ -246,9 +261,10 @@ export class IndexTable extends Component {
|
|||
return indexOfUnselectedItem === -1;
|
||||
};
|
||||
|
||||
buildHeader() {
|
||||
buildHeader(config) {
|
||||
const { sortField, isSortAscending } = this.props;
|
||||
return Object.entries(HEADERS).map(([fieldName, label]) => {
|
||||
const headers = getHeaders({ showIndexStats: config.enableIndexStats });
|
||||
return Object.entries(headers).map(([fieldName, label]) => {
|
||||
const isSorted = sortField === fieldName;
|
||||
return (
|
||||
<EuiTableHeaderCell
|
||||
|
@ -302,8 +318,9 @@ export class IndexTable extends Component {
|
|||
return value;
|
||||
}
|
||||
|
||||
buildRowCells(index, appServices) {
|
||||
return Object.keys(HEADERS).map((fieldName) => {
|
||||
buildRowCells(index, appServices, config) {
|
||||
const headers = getHeaders({ showIndexStats: config.enableIndexStats });
|
||||
return Object.keys(headers).map((fieldName) => {
|
||||
const { name } = index;
|
||||
const value = index[fieldName];
|
||||
|
||||
|
@ -363,7 +380,7 @@ export class IndexTable extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
buildRows(appServices) {
|
||||
buildRows(appServices, config) {
|
||||
const { indices = [], detailPanelIndexName } = this.props;
|
||||
return indices.map((index) => {
|
||||
const { name } = index;
|
||||
|
@ -388,7 +405,7 @@ export class IndexTable extends Component {
|
|||
})}
|
||||
/>
|
||||
</EuiTableRowCellCheckbox>
|
||||
{this.buildRowCells(index, appServices)}
|
||||
{this.buildRowCells(index, appServices, config)}
|
||||
</EuiTableRow>
|
||||
);
|
||||
});
|
||||
|
@ -479,7 +496,7 @@ export class IndexTable extends Component {
|
|||
|
||||
return (
|
||||
<AppContextConsumer>
|
||||
{({ services }) => {
|
||||
{({ services, config }) => {
|
||||
const { extensionsService } = services;
|
||||
|
||||
return (
|
||||
|
@ -639,10 +656,10 @@ export class IndexTable extends Component {
|
|||
)}
|
||||
/>
|
||||
</EuiTableHeaderCellCheckbox>
|
||||
{this.buildHeader()}
|
||||
{this.buildHeader(config)}
|
||||
</EuiTableHeader>
|
||||
|
||||
<EuiTableBody>{this.buildRows(services)}</EuiTableBody>
|
||||
<EuiTableBody>{this.buildRows(services, config)}</EuiTableBody>
|
||||
</EuiTable>
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
@ -123,15 +123,19 @@ export const getPageOfIndices = createSelector(
|
|||
const { firstItemIndex, lastItemIndex } = pager;
|
||||
const pagedIndexes = sortedIndexes.slice(firstItemIndex, lastItemIndex + 1);
|
||||
return pagedIndexes.map((index) => {
|
||||
const status =
|
||||
indexStatusLabels[rowStatuses[index.name]] || // user friendly version of row status
|
||||
rowStatuses[index.name] || // row status
|
||||
indexStatusLabels[index.status] || // user friendly version of index status
|
||||
index.status; // index status
|
||||
return {
|
||||
...index,
|
||||
status,
|
||||
};
|
||||
if (index.status) {
|
||||
const status =
|
||||
indexStatusLabels[rowStatuses[index.name]] || // user friendly version of row status
|
||||
rowStatuses[index.name] || // row status
|
||||
indexStatusLabels[index.status] || // user friendly version of index status
|
||||
index.status; // index status
|
||||
return {
|
||||
...index,
|
||||
status,
|
||||
};
|
||||
}
|
||||
|
||||
return index;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -40,6 +40,7 @@ export class IndexMgmtUIPlugin {
|
|||
ui: { enabled: isIndexManagementUiEnabled },
|
||||
enableIndexActions,
|
||||
enableLegacyTemplates,
|
||||
enableIndexStats,
|
||||
dev: { enableIndexDetailsPage },
|
||||
} = this.ctx.config.get<ClientConfigType>();
|
||||
|
||||
|
@ -62,6 +63,7 @@ export class IndexMgmtUIPlugin {
|
|||
enableIndexActions,
|
||||
enableLegacyTemplates,
|
||||
enableIndexDetailsPage,
|
||||
enableIndexStats,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -31,6 +31,7 @@ export interface ClientConfigType {
|
|||
};
|
||||
enableIndexActions?: boolean;
|
||||
enableLegacyTemplates?: boolean;
|
||||
enableIndexStats?: boolean;
|
||||
dev: {
|
||||
enableIndexDetailsPage?: boolean;
|
||||
};
|
||||
|
|
|
@ -33,6 +33,11 @@ const schemaLatest = schema.object(
|
|||
serverless: schema.boolean({ defaultValue: true }),
|
||||
}),
|
||||
dev: schema.object({ enableIndexDetailsPage: schema.boolean({ defaultValue: false }) }),
|
||||
enableIndexStats: offeringBasedSchema({
|
||||
// Index stats information 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
|
||||
serverless: schema.boolean({ defaultValue: true }),
|
||||
}),
|
||||
},
|
||||
{ defaultValue: undefined }
|
||||
);
|
||||
|
@ -45,6 +50,7 @@ const configLatest: PluginConfigDescriptor<IndexManagementConfig> = {
|
|||
dev: {
|
||||
enableIndexDetailsPage: true,
|
||||
},
|
||||
enableIndexStats: true,
|
||||
},
|
||||
schema: schemaLatest,
|
||||
deprecations: () => [],
|
||||
|
|
|
@ -9,9 +9,11 @@ import { ByteSizeValue } from '@kbn/config-schema';
|
|||
import { IScopedClusterClient } from '@kbn/core/server';
|
||||
import { IndexDataEnricher } from '../services';
|
||||
import { Index } from '..';
|
||||
import { RouteDependencies } from '../types';
|
||||
|
||||
async function fetchIndicesCall(
|
||||
client: IScopedClusterClient,
|
||||
config: RouteDependencies['config'],
|
||||
indexNames?: string[]
|
||||
): Promise<Index[]> {
|
||||
const indexNamesString = indexNames && indexNames.length ? indexNames.join(',') : '*';
|
||||
|
@ -38,41 +40,77 @@ async function fetchIndicesCall(
|
|||
return [];
|
||||
}
|
||||
|
||||
const { indices: indicesStats = {} } = await client.asCurrentUser.indices.stats({
|
||||
const indicesNames = Object.keys(indices);
|
||||
|
||||
// Return response without index stats, if isIndexStatsEnabled === false
|
||||
if (config.isIndexStatsEnabled === false) {
|
||||
return indicesNames.map((indexName: string) => {
|
||||
const indexData = indices[indexName];
|
||||
const aliases = Object.keys(indexData.aliases!);
|
||||
return {
|
||||
name: indexName,
|
||||
primary: indexData.settings?.index?.number_of_shards,
|
||||
replica: indexData.settings?.index?.number_of_replicas,
|
||||
isFrozen: indexData.settings?.index?.frozen === 'true',
|
||||
aliases: aliases.length ? aliases : 'none',
|
||||
hidden: indexData.settings?.index?.hidden === 'true',
|
||||
data_stream: indexData.data_stream,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const { indices: indicesStats } = await client.asCurrentUser.indices.stats({
|
||||
index: indexNamesString,
|
||||
expand_wildcards: ['hidden', 'all'],
|
||||
forbid_closed_indices: false,
|
||||
metric: ['docs', 'store'],
|
||||
});
|
||||
const indicesNames = Object.keys(indices);
|
||||
|
||||
return indicesNames.map((indexName: string) => {
|
||||
const indexData = indices[indexName];
|
||||
const indexStats = indicesStats[indexName];
|
||||
const aliases = Object.keys(indexData.aliases!);
|
||||
return {
|
||||
health: indexStats?.health,
|
||||
status: indexStats?.status,
|
||||
const baseResponse = {
|
||||
name: indexName,
|
||||
uuid: indexStats?.uuid,
|
||||
primary: indexData.settings?.index?.number_of_shards,
|
||||
replica: indexData.settings?.index?.number_of_replicas,
|
||||
documents: indexStats?.primaries?.docs?.count ?? 0,
|
||||
documents_deleted: indexStats?.primaries?.docs?.deleted ?? 0,
|
||||
size: new ByteSizeValue(indexStats?.total?.store?.size_in_bytes ?? 0).toString(),
|
||||
primary_size: new ByteSizeValue(indexStats?.primaries?.store?.size_in_bytes ?? 0).toString(),
|
||||
isFrozen: indexData.settings?.index?.frozen === 'true',
|
||||
aliases: aliases.length ? aliases : 'none',
|
||||
hidden: indexData.settings?.index?.hidden === 'true',
|
||||
data_stream: indexData.data_stream,
|
||||
};
|
||||
|
||||
if (indicesStats) {
|
||||
const indexStats = indicesStats[indexName];
|
||||
|
||||
return {
|
||||
...baseResponse,
|
||||
health: indexStats?.health,
|
||||
status: indexStats?.status,
|
||||
uuid: indexStats?.uuid,
|
||||
documents: indexStats?.primaries?.docs?.count ?? 0,
|
||||
documents_deleted: indexStats?.primaries?.docs?.deleted ?? 0,
|
||||
size: new ByteSizeValue(indexStats?.total?.store?.size_in_bytes ?? 0).toString(),
|
||||
primary_size: new ByteSizeValue(
|
||||
indexStats?.primaries?.store?.size_in_bytes ?? 0
|
||||
).toString(),
|
||||
};
|
||||
}
|
||||
|
||||
return baseResponse;
|
||||
});
|
||||
}
|
||||
|
||||
export const fetchIndices = async (
|
||||
client: IScopedClusterClient,
|
||||
indexDataEnricher: IndexDataEnricher,
|
||||
indexNames?: string[]
|
||||
) => {
|
||||
const indices = await fetchIndicesCall(client, indexNames);
|
||||
export const fetchIndices = async ({
|
||||
client,
|
||||
indexDataEnricher,
|
||||
config,
|
||||
indexNames,
|
||||
}: {
|
||||
client: IScopedClusterClient;
|
||||
indexDataEnricher: IndexDataEnricher;
|
||||
config: RouteDependencies['config'];
|
||||
indexNames?: string[];
|
||||
}) => {
|
||||
const indices = await fetchIndicesCall(client, config, indexNames);
|
||||
return await indexDataEnricher.enrichIndices(indices, client);
|
||||
};
|
||||
|
|
|
@ -55,6 +55,7 @@ export class IndexMgmtServerPlugin implements Plugin<IndexManagementPluginSetup,
|
|||
config: {
|
||||
isSecurityEnabled: () => security !== undefined && security.license.isEnabled(),
|
||||
isLegacyTemplatesEnabled: this.config.enableLegacyTemplates,
|
||||
isIndexStatsEnabled: this.config.enableIndexStats,
|
||||
},
|
||||
indexDataEnricher: this.indexDataEnricher,
|
||||
lib: {
|
||||
|
|
|
@ -47,6 +47,7 @@ describe('GET privileges', () => {
|
|||
config: {
|
||||
isSecurityEnabled: () => true,
|
||||
isLegacyTemplatesEnabled: true,
|
||||
isIndexStatsEnabled: true,
|
||||
},
|
||||
indexDataEnricher: mockedIndexDataEnricher,
|
||||
lib: {
|
||||
|
@ -114,6 +115,7 @@ describe('GET privileges', () => {
|
|||
config: {
|
||||
isSecurityEnabled: () => false,
|
||||
isLegacyTemplatesEnabled: true,
|
||||
isIndexStatsEnabled: true,
|
||||
},
|
||||
indexDataEnricher: mockedIndexDataEnricher,
|
||||
lib: {
|
||||
|
|
|
@ -13,13 +13,14 @@ export function registerListRoute({
|
|||
router,
|
||||
indexDataEnricher,
|
||||
lib: { handleEsError },
|
||||
config,
|
||||
}: RouteDependencies) {
|
||||
router.get(
|
||||
{ path: addBasePath('/indices'), validate: false },
|
||||
async (context, request, response) => {
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
try {
|
||||
const indices = await fetchIndices(client, indexDataEnricher);
|
||||
const indices = await fetchIndices({ client, indexDataEnricher, config });
|
||||
return response.ok({ body: indices });
|
||||
} catch (error) {
|
||||
return handleEsError({ error, response });
|
||||
|
|
|
@ -21,6 +21,7 @@ export function registerReloadRoute({
|
|||
router,
|
||||
indexDataEnricher,
|
||||
lib: { handleEsError },
|
||||
config,
|
||||
}: RouteDependencies) {
|
||||
router.post(
|
||||
{ path: addBasePath('/indices/reload'), validate: { body: bodySchema } },
|
||||
|
@ -29,7 +30,7 @@ export function registerReloadRoute({
|
|||
const { indexNames = [] } = (request.body as typeof bodySchema.type) ?? {};
|
||||
|
||||
try {
|
||||
const indices = await fetchIndices(client, indexDataEnricher, indexNames);
|
||||
const indices = await fetchIndices({ client, indexDataEnricher, config, indexNames });
|
||||
return response.ok({ body: indices });
|
||||
} catch (error) {
|
||||
return handleEsError({ error, response });
|
||||
|
|
|
@ -23,11 +23,14 @@ export class ApiRoutes {
|
|||
registerIndicesRoutes(dependencies);
|
||||
registerTemplateRoutes(dependencies);
|
||||
registerSettingsRoutes(dependencies);
|
||||
registerStatsRoute(dependencies);
|
||||
registerMappingRoute(dependencies);
|
||||
registerComponentTemplateRoutes(dependencies);
|
||||
registerNodesRoute(dependencies);
|
||||
registerEnrichPoliciesRoute(dependencies);
|
||||
|
||||
if (dependencies.config.isIndexStatsEnabled !== false) {
|
||||
registerStatsRoute(dependencies);
|
||||
}
|
||||
}
|
||||
|
||||
start() {}
|
||||
|
|
|
@ -13,6 +13,7 @@ export const routeDependencies: Omit<RouteDependencies, 'router'> = {
|
|||
config: {
|
||||
isSecurityEnabled: jest.fn().mockReturnValue(true),
|
||||
isLegacyTemplatesEnabled: true,
|
||||
isIndexStatsEnabled: true,
|
||||
},
|
||||
indexDataEnricher: new IndexDataEnricher(),
|
||||
lib: {
|
||||
|
|
|
@ -24,6 +24,7 @@ export interface RouteDependencies {
|
|||
config: {
|
||||
isSecurityEnabled: () => boolean;
|
||||
isLegacyTemplatesEnabled: boolean;
|
||||
isIndexStatsEnabled: boolean;
|
||||
};
|
||||
indexDataEnricher: IndexDataEnricher;
|
||||
lib: {
|
||||
|
|
|
@ -10,5 +10,6 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
|
|||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('Index Management APIs', function () {
|
||||
loadTestFile(require.resolve('./index_templates'));
|
||||
loadTestFile(require.resolve('./indices'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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('Indices', function () {
|
||||
const indexName = `index-${Math.random()}`;
|
||||
|
||||
before(async () => {
|
||||
// Create a new index to test against
|
||||
try {
|
||||
await es.indices.create({ index: indexName });
|
||||
} catch (err) {
|
||||
log.debug('[Setup error] Error creating index');
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
// Cleanup index created for testing purposes
|
||||
try {
|
||||
await es.indices.delete({
|
||||
index: indexName,
|
||||
});
|
||||
} catch (err) {
|
||||
log.debug('[Cleanup error] Error deleting index');
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
describe('get all', () => {
|
||||
it('should list indices with the expected parameters', async () => {
|
||||
const { body: indices } = await supertest
|
||||
.get(`${API_BASE_PATH}/indices`)
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.set('x-elastic-internal-origin', 'xxx')
|
||||
.expect(200);
|
||||
|
||||
const indexFound = indices.find((index: { name: string }) => index.name === indexName);
|
||||
|
||||
expect(indexFound).toBeTruthy();
|
||||
|
||||
const expectedKeys = ['aliases', 'hidden', 'isFrozen', 'primary', 'replica', 'name'].sort();
|
||||
|
||||
expect(Object.keys(indexFound).sort()).toEqual(expectedKeys);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -10,5 +10,6 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
|
|||
export default ({ loadTestFile }: FtrProviderContext) => {
|
||||
describe('Index Management', function () {
|
||||
loadTestFile(require.resolve('./index_templates'));
|
||||
loadTestFile(require.resolve('./indices'));
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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('Indices', function () {
|
||||
before(async () => {
|
||||
await security.testUser.setRoles(['index_management_user']);
|
||||
await pageObjects.common.navigateToApp('indexManagement');
|
||||
// Navigate to the indices tab
|
||||
await pageObjects.indexManagement.changeTabs('indicesTab');
|
||||
});
|
||||
|
||||
it('renders the indices tab', async () => {
|
||||
await retry.waitFor('indices list to be visible', async () => {
|
||||
return await testSubjects.exists('indicesList');
|
||||
});
|
||||
|
||||
const url = await browser.getCurrentUrl();
|
||||
expect(url).to.contain(`/indices`);
|
||||
});
|
||||
});
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue