diff --git a/config/serverless.yml b/config/serverless.yml index 6006b85323d32..ddc54fef52299 100644 --- a/config/serverless.yml +++ b/config/serverless.yml @@ -100,6 +100,8 @@ xpack.index_management.enableIndexActions: false xpack.index_management.enableLegacyTemplates: false # Disable index stats information from Index Management UI xpack.index_management.enableIndexStats: false +# Enable size and doc count information via metering API from Index Management UI +xpack.index_management.enableSizeAndDocCount: true # Disable data stream stats information from Index Management UI xpack.index_management.enableDataStreamStats: false # Only limited index settings can be edited diff --git a/x-pack/plugins/index_management/public/application/app_context.tsx b/x-pack/plugins/index_management/public/application/app_context.tsx index 9cc0a426f47e3..3aef6ae464bd6 100644 --- a/x-pack/plugins/index_management/public/application/app_context.tsx +++ b/x-pack/plugins/index_management/public/application/app_context.tsx @@ -63,6 +63,7 @@ export interface AppDependencies { enableIndexActions: boolean; enableLegacyTemplates: boolean; enableIndexStats: boolean; + enableSizeAndDocCount: boolean; enableDataStreamStats: boolean; editableIndexSettings: 'all' | 'limited'; enableMappingsSourceFieldSection: boolean; diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts index 136174dfac165..dff4880fbc66a 100644 --- a/x-pack/plugins/index_management/public/plugin.ts +++ b/x-pack/plugins/index_management/public/plugin.ts @@ -41,6 +41,7 @@ export class IndexMgmtUIPlugin enableIndexActions: boolean; enableLegacyTemplates: boolean; enableIndexStats: boolean; + enableSizeAndDocCount: boolean; enableDataStreamStats: boolean; editableIndexSettings: 'all' | 'limited'; isIndexManagementUiEnabled: boolean; @@ -59,6 +60,7 @@ export class IndexMgmtUIPlugin enableIndexActions, enableLegacyTemplates, enableIndexStats, + enableSizeAndDocCount, enableDataStreamStats, editableIndexSettings, enableMappingsSourceFieldSection, @@ -70,6 +72,7 @@ export class IndexMgmtUIPlugin enableIndexActions: enableIndexActions ?? true, enableLegacyTemplates: enableLegacyTemplates ?? true, enableIndexStats: enableIndexStats ?? true, + enableSizeAndDocCount: enableSizeAndDocCount ?? true, enableDataStreamStats: enableDataStreamStats ?? true, editableIndexSettings: editableIndexSettings ?? 'all', enableMappingsSourceFieldSection: enableMappingsSourceFieldSection ?? true, diff --git a/x-pack/plugins/index_management/public/types.ts b/x-pack/plugins/index_management/public/types.ts index 9c9340b334ce2..35e6fc6b9dfd5 100644 --- a/x-pack/plugins/index_management/public/types.ts +++ b/x-pack/plugins/index_management/public/types.ts @@ -52,6 +52,7 @@ export interface ClientConfigType { enableIndexActions?: boolean; enableLegacyTemplates?: boolean; enableIndexStats?: boolean; + enableSizeAndDocCount?: boolean; enableDataStreamStats?: boolean; editableIndexSettings?: 'all' | 'limited'; enableMappingsSourceFieldSection?: boolean; diff --git a/x-pack/plugins/index_management/server/config.ts b/x-pack/plugins/index_management/server/config.ts index f2d1808a44286..3480e380281e5 100644 --- a/x-pack/plugins/index_management/server/config.ts +++ b/x-pack/plugins/index_management/server/config.ts @@ -38,6 +38,11 @@ const schemaLatest = schema.object( // deprecate as unused after semantic text is enabled everywhere enableSemanticText: schema.boolean({ defaultValue: true }), }), + enableSizeAndDocCount: offeringBasedSchema({ + // Size and document count information is enabled 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 }), + }), 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 diff --git a/x-pack/plugins/index_management/server/lib/fetch_indices.test.ts b/x-pack/plugins/index_management/server/lib/fetch_indices.test.ts index 9cc91a6e0a957..75b7ff6e85f2d 100644 --- a/x-pack/plugins/index_management/server/lib/fetch_indices.test.ts +++ b/x-pack/plugins/index_management/server/lib/fetch_indices.test.ts @@ -19,143 +19,188 @@ describe('[Index management API Routes] fetch indices lib function', () => { const getIndices = router.getMockESApiFn('indices.get'); const getIndicesStats = router.getMockESApiFn('indices.stats'); + const getMeteringStats = router.getMockESApiFnAsSecondaryAuthUser('transport.request'); const mockRequest: RequestMock = { method: 'get', path: addBasePath('/indices'), }; - beforeAll(() => { - registerIndicesRoutes({ - ...routeDependencies, - router, + describe('stateful', () => { + beforeAll(() => { + registerIndicesRoutes({ + ...routeDependencies, + config: { + ...routeDependencies.config, + isSizeAndDocCountEnabled: false, + isIndexStatsEnabled: true, + }, + router, + }); }); - }); - test('regular index', async () => { - getIndices.mockResolvedValue({ - regular_index: createTestIndexState(), - }); - getIndicesStats.mockResolvedValue({ - indices: { - regular_index: createTestIndexStats({ uuid: 'regular_index' }), - }, - }); + test('regular index', async () => { + getIndices.mockResolvedValue({ + regular_index: createTestIndexState(), + }); + getIndicesStats.mockResolvedValue({ + indices: { + regular_index: createTestIndexStats({ uuid: 'regular_index' }), + }, + }); - await expect(router.runRequest(mockRequest)).resolves.toEqual({ - body: [createTestIndexResponse({ name: 'regular_index', uuid: 'regular_index' })], - }); - }); - test('index with aliases', async () => { - getIndices.mockResolvedValue({ - index_with_aliases: createTestIndexState({ - aliases: { test_alias: {}, another_alias: {} }, - }), - }); - getIndicesStats.mockResolvedValue({ - indices: { - index_with_aliases: createTestIndexStats({ uuid: 'index_with_aliases' }), - }, - }); - - await expect(router.runRequest(mockRequest)).resolves.toEqual({ - body: [ - createTestIndexResponse({ - aliases: ['test_alias', 'another_alias'], - name: 'index_with_aliases', - uuid: 'index_with_aliases', + await expect(router.runRequest(mockRequest)).resolves.toEqual({ + body: [createTestIndexResponse({ name: 'regular_index', uuid: 'regular_index' })], + }); + }); + test('index with aliases', async () => { + getIndices.mockResolvedValue({ + index_with_aliases: createTestIndexState({ + aliases: { test_alias: {}, another_alias: {} }, }), - ], - }); - }); - test('frozen index', async () => { - getIndices.mockResolvedValue({ - frozen_index: createTestIndexState({ - settings: { index: { number_of_shards: 1, number_of_replicas: 1, frozen: 'true' } }, - }), - }); - getIndicesStats.mockResolvedValue({ - indices: { - frozen_index: createTestIndexStats({ uuid: 'frozen_index' }), - }, - }); + }); + getIndicesStats.mockResolvedValue({ + indices: { + index_with_aliases: createTestIndexStats({ uuid: 'index_with_aliases' }), + }, + }); - await expect(router.runRequest(mockRequest)).resolves.toEqual({ - body: [ - createTestIndexResponse({ - name: 'frozen_index', - uuid: 'frozen_index', - isFrozen: true, + await expect(router.runRequest(mockRequest)).resolves.toEqual({ + body: [ + createTestIndexResponse({ + aliases: ['test_alias', 'another_alias'], + name: 'index_with_aliases', + uuid: 'index_with_aliases', + }), + ], + }); + }); + test('frozen index', async () => { + getIndices.mockResolvedValue({ + frozen_index: createTestIndexState({ + settings: { index: { number_of_shards: 1, number_of_replicas: 1, frozen: 'true' } }, }), - ], - }); - }); - test('hidden index', async () => { - getIndices.mockResolvedValue({ - hidden_index: createTestIndexState({ - settings: { index: { number_of_shards: 1, number_of_replicas: 1, hidden: 'true' } }, - }), - }); - getIndicesStats.mockResolvedValue({ - indices: { - hidden_index: createTestIndexStats({ uuid: 'hidden_index' }), - }, - }); + }); + getIndicesStats.mockResolvedValue({ + indices: { + frozen_index: createTestIndexStats({ uuid: 'frozen_index' }), + }, + }); - await expect(router.runRequest(mockRequest)).resolves.toEqual({ - body: [ - createTestIndexResponse({ - name: 'hidden_index', - uuid: 'hidden_index', - hidden: true, + await expect(router.runRequest(mockRequest)).resolves.toEqual({ + body: [ + createTestIndexResponse({ + name: 'frozen_index', + uuid: 'frozen_index', + isFrozen: true, + }), + ], + }); + }); + test('hidden index', async () => { + getIndices.mockResolvedValue({ + hidden_index: createTestIndexState({ + settings: { index: { number_of_shards: 1, number_of_replicas: 1, hidden: 'true' } }, }), - ], - }); - }); - test('data stream index', async () => { - getIndices.mockResolvedValue({ - data_stream_index: createTestIndexState({ - data_stream: 'test_data_stream', - }), - }); - getIndicesStats.mockResolvedValue({ - indices: { - data_stream_index: createTestIndexStats({ uuid: 'data_stream_index' }), - }, - }); + }); + getIndicesStats.mockResolvedValue({ + indices: { + hidden_index: createTestIndexStats({ uuid: 'hidden_index' }), + }, + }); - await expect(router.runRequest(mockRequest)).resolves.toEqual({ - body: [ - createTestIndexResponse({ - name: 'data_stream_index', - uuid: 'data_stream_index', + await expect(router.runRequest(mockRequest)).resolves.toEqual({ + body: [ + createTestIndexResponse({ + name: 'hidden_index', + uuid: 'hidden_index', + hidden: true, + }), + ], + }); + }); + test('data stream index', async () => { + getIndices.mockResolvedValue({ + data_stream_index: createTestIndexState({ data_stream: 'test_data_stream', }), - ], + }); + getIndicesStats.mockResolvedValue({ + indices: { + data_stream_index: createTestIndexStats({ uuid: 'data_stream_index' }), + }, + }); + + await expect(router.runRequest(mockRequest)).resolves.toEqual({ + body: [ + createTestIndexResponse({ + name: 'data_stream_index', + uuid: 'data_stream_index', + data_stream: 'test_data_stream', + }), + ], + }); + }); + test('index missing in stats call', async () => { + getIndices.mockResolvedValue({ + index_missing_stats: createTestIndexState(), + }); + // simulates when an index has been deleted after get indices call + // deleted index won't be present in the indices stats call response + getIndicesStats.mockResolvedValue({ + indices: { + some_other_index: createTestIndexStats({ uuid: 'some_other_index' }), + }, + }); + await expect(router.runRequest(mockRequest)).resolves.toEqual({ + body: [ + createTestIndexResponse({ + name: 'index_missing_stats', + uuid: undefined, + health: undefined, + status: undefined, + documents: 0, + size: '0b', + primary_size: '0b', + }), + ], + }); }); }); - test('index missing in stats call', async () => { - getIndices.mockResolvedValue({ - index_missing_stats: createTestIndexState(), - }); - // simulates when an index has been deleted after get indices call - // deleted index won't be present in the indices stats call response - getIndicesStats.mockResolvedValue({ - indices: { - some_other_index: createTestIndexStats({ uuid: 'some_other_index' }), - }, + + describe('stateless', () => { + beforeAll(() => { + registerIndicesRoutes({ + ...routeDependencies, + config: { + ...routeDependencies.config, + isSizeAndDocCountEnabled: true, + isIndexStatsEnabled: false, + }, + router, + }); }); - await expect(router.runRequest(mockRequest)).resolves.toEqual({ - body: [ - createTestIndexResponse({ - name: 'index_missing_stats', - uuid: undefined, - health: undefined, - status: undefined, - documents: 0, - size: '0b', - primary_size: '0b', - }), - ], + + test('regular index', async () => { + getIndices.mockResolvedValue({ + regular_index: createTestIndexState(), + }); + getMeteringStats.mockResolvedValue({ + indices: [{ name: 'regular_index', num_docs: 100, size_in_bytes: 1000 }], + }); + + await expect(router.runRequest(mockRequest)).resolves.toEqual({ + body: [ + { + name: 'regular_index', + isFrozen: false, + aliases: 'none', + hidden: false, + data_stream: undefined, + documents: 100, + size: '1000b', + }, + ], + }); }); }); }); diff --git a/x-pack/plugins/index_management/server/lib/fetch_indices.ts b/x-pack/plugins/index_management/server/lib/fetch_indices.ts index b58578c8c1621..1df453d1042cd 100644 --- a/x-pack/plugins/index_management/server/lib/fetch_indices.ts +++ b/x-pack/plugins/index_management/server/lib/fetch_indices.ts @@ -11,6 +11,14 @@ import { IndexDataEnricher } from '../services'; import { Index } from '..'; import { RouteDependencies } from '../types'; +interface MeteringStatsResponse { + indices: Array<{ + name: string; + num_docs: number; + size_in_bytes: number; + }>; +} + async function fetchIndicesCall( client: IScopedClusterClient, config: RouteDependencies['config'], @@ -42,12 +50,18 @@ async function fetchIndicesCall( const indicesNames = Object.keys(indices); - // Return response without index stats, if isIndexStatsEnabled === false - if (config.isIndexStatsEnabled === false) { + if (config.isIndexStatsEnabled) { + const { indices: indicesStats } = await client.asCurrentUser.indices.stats({ + index: indexNamesString, + expand_wildcards: ['hidden', 'all'], + forbid_closed_indices: false, + metric: ['docs', 'store'], + }); + return indicesNames.map((indexName: string) => { const indexData = indices[indexName]; const aliases = Object.keys(indexData.aliases!); - return { + const baseResponse = { name: indexName, primary: indexData.settings?.index?.number_of_shards, replica: indexData.settings?.index?.number_of_replicas, @@ -56,20 +70,69 @@ async function fetchIndicesCall( 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; }); } - const { indices: indicesStats } = await client.asCurrentUser.indices.stats({ - index: indexNamesString, - expand_wildcards: ['hidden', 'all'], - forbid_closed_indices: false, - metric: ['docs', 'store'], - }); + // uses the _metering/stats API to get the number of documents and size of the index + // this API is only available in ES3 + if (config.isSizeAndDocCountEnabled) { + const { indices: indicesStats } = + await client.asSecondaryAuthUser.transport.request({ + method: 'GET', + path: `/_metering/stats/` + indexNamesString, + }); + return indicesNames.map((indexName: string) => { + const indexData = indices[indexName]; + const aliases = Object.keys(indexData.aliases!); + const baseResponse = { + name: indexName, + isFrozen: false, + aliases: aliases.length ? aliases : 'none', + hidden: indexData.settings?.index?.hidden === 'true', + data_stream: indexData.data_stream, + }; + + if (indicesStats) { + const indexStats = indicesStats.find((index) => index.name === indexName); + + return { + ...baseResponse, + documents: indexStats?.num_docs ?? 0, + size: new ByteSizeValue(indexStats?.size_in_bytes ?? 0).toString(), + }; + } + + return baseResponse; + }); + } + + // if neither index stats (Stateful only API) + // nor size and doc count are enabled (ES3 only API) + // return the base response return indicesNames.map((indexName: string) => { const indexData = indices[indexName]; const aliases = Object.keys(indexData.aliases!); - const baseResponse = { + return { name: indexName, primary: indexData.settings?.index?.number_of_shards, replica: indexData.settings?.index?.number_of_replicas, @@ -78,25 +141,6 @@ async function fetchIndicesCall( 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; }); } diff --git a/x-pack/plugins/index_management/server/plugin.ts b/x-pack/plugins/index_management/server/plugin.ts index bb2d563ee1b8c..51a6c391f6fa9 100644 --- a/x-pack/plugins/index_management/server/plugin.ts +++ b/x-pack/plugins/index_management/server/plugin.ts @@ -55,7 +55,8 @@ export class IndexMgmtServerPlugin implements Plugin security !== undefined && security.license.isEnabled(), isLegacyTemplatesEnabled: this.config.enableLegacyTemplates, - isIndexStatsEnabled: this.config.enableIndexStats, + isIndexStatsEnabled: this.config.enableIndexStats ?? true, + isSizeAndDocCountEnabled: this.config.enableSizeAndDocCount ?? false, isDataStreamStatsEnabled: this.config.enableDataStreamStats, enableMappingsSourceFieldSection: this.config.enableMappingsSourceFieldSection, enableTogglingDataRetention: this.config.enableTogglingDataRetention, diff --git a/x-pack/plugins/index_management/server/routes/api/component_templates/register_privileges_route.test.ts b/x-pack/plugins/index_management/server/routes/api/component_templates/register_privileges_route.test.ts index 0548af172c094..ae3e7f7342eb1 100644 --- a/x-pack/plugins/index_management/server/routes/api/component_templates/register_privileges_route.test.ts +++ b/x-pack/plugins/index_management/server/routes/api/component_templates/register_privileges_route.test.ts @@ -48,6 +48,7 @@ describe('GET privileges', () => { isSecurityEnabled: () => true, isLegacyTemplatesEnabled: true, isIndexStatsEnabled: true, + isSizeAndDocCountEnabled: false, isDataStreamStatsEnabled: true, enableMappingsSourceFieldSection: true, enableTogglingDataRetention: true, @@ -119,6 +120,7 @@ describe('GET privileges', () => { isSecurityEnabled: () => false, isLegacyTemplatesEnabled: true, isIndexStatsEnabled: true, + isSizeAndDocCountEnabled: false, isDataStreamStatsEnabled: true, enableMappingsSourceFieldSection: true, enableTogglingDataRetention: true, diff --git a/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_privileges_route.test.ts b/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_privileges_route.test.ts index 2fc1d3e38599e..fee0297895fae 100644 --- a/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_privileges_route.test.ts +++ b/x-pack/plugins/index_management/server/routes/api/enrich_policies/register_privileges_route.test.ts @@ -48,6 +48,7 @@ describe('GET privileges', () => { isSecurityEnabled: () => true, isLegacyTemplatesEnabled: true, isIndexStatsEnabled: true, + isSizeAndDocCountEnabled: false, isDataStreamStatsEnabled: true, enableMappingsSourceFieldSection: true, enableTogglingDataRetention: true, @@ -119,6 +120,7 @@ describe('GET privileges', () => { isSecurityEnabled: () => false, isLegacyTemplatesEnabled: true, isIndexStatsEnabled: true, + isSizeAndDocCountEnabled: false, isDataStreamStatsEnabled: true, enableMappingsSourceFieldSection: true, enableTogglingDataRetention: true, diff --git a/x-pack/plugins/index_management/server/test/helpers/route_dependencies.ts b/x-pack/plugins/index_management/server/test/helpers/route_dependencies.ts index b6a6ee4900b13..fedea2f924d66 100644 --- a/x-pack/plugins/index_management/server/test/helpers/route_dependencies.ts +++ b/x-pack/plugins/index_management/server/test/helpers/route_dependencies.ts @@ -14,6 +14,7 @@ export const routeDependencies: Omit = { isSecurityEnabled: jest.fn().mockReturnValue(true), isLegacyTemplatesEnabled: true, isIndexStatsEnabled: true, + isSizeAndDocCountEnabled: false, isDataStreamStatsEnabled: true, enableMappingsSourceFieldSection: true, enableTogglingDataRetention: true, diff --git a/x-pack/plugins/index_management/server/test/helpers/router_mock.ts b/x-pack/plugins/index_management/server/test/helpers/router_mock.ts index ca52e08709712..3c021220b1919 100644 --- a/x-pack/plugins/index_management/server/test/helpers/router_mock.ts +++ b/x-pack/plugins/index_management/server/test/helpers/router_mock.ts @@ -96,6 +96,10 @@ export class RouterMock implements IRouter { return get(this.contextMock.core.elasticsearch.client.asCurrentUser, path); } + getMockESApiFnAsSecondaryAuthUser(path: string): jest.Mock { + return get(this.contextMock.core.elasticsearch.client.asSecondaryAuthUser, path); + } + runRequest({ method, path, ...mockRequest }: RequestMock) { const handler = this.cacheHandlers[method][path]; diff --git a/x-pack/plugins/index_management/server/types.ts b/x-pack/plugins/index_management/server/types.ts index 99ad3aec5b84b..c5eb2ed9bc8cb 100644 --- a/x-pack/plugins/index_management/server/types.ts +++ b/x-pack/plugins/index_management/server/types.ts @@ -25,6 +25,7 @@ export interface RouteDependencies { isSecurityEnabled: () => boolean; isLegacyTemplatesEnabled: boolean; isIndexStatsEnabled: boolean; + isSizeAndDocCountEnabled: boolean; isDataStreamStatsEnabled: boolean; enableMappingsSourceFieldSection: boolean; enableTogglingDataRetention: boolean; diff --git a/x-pack/test_serverless/api_integration/test_suites/common/index_management/indices.ts b/x-pack/test_serverless/api_integration/test_suites/common/index_management/indices.ts index ec83887406772..72b3a4a2e1435 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/index_management/indices.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/index_management/indices.ts @@ -63,7 +63,14 @@ export default function ({ getService }: FtrProviderContext) { const sortedReceivedKeys = Object.keys(indexCreated).sort(); - expect(sortedReceivedKeys).to.eql(['aliases', 'hidden', 'isFrozen', 'name']); + expect(sortedReceivedKeys).to.eql([ + 'aliases', + 'documents', + 'hidden', + 'isFrozen', + 'name', + 'size', + ]); }); }); @@ -77,7 +84,14 @@ export default function ({ getService }: FtrProviderContext) { expect(index).to.be.ok(); - expect(Object.keys(index).sort()).to.eql(['aliases', 'hidden', 'isFrozen', 'name']); + expect(Object.keys(index).sort()).to.eql([ + 'aliases', + 'documents', + 'hidden', + 'isFrozen', + 'name', + 'size', + ]); }); it('throws 404 for a non-existent index', async () => { @@ -121,7 +135,14 @@ export default function ({ getService }: FtrProviderContext) { expect(index).to.be.ok(); - expect(Object.keys(index).sort()).to.eql(['aliases', 'hidden', 'isFrozen', 'name']); + expect(Object.keys(index).sort()).to.eql([ + 'aliases', + 'documents', + 'hidden', + 'isFrozen', + 'name', + 'size', + ]); }); it('fails to re-create the same index', async () => { @@ -147,7 +168,14 @@ export default function ({ getService }: FtrProviderContext) { (index: { name: string }) => index.name === 'reload-test-index' ); const sortedReceivedKeys = Object.keys(indexCreated).sort(); - expect(sortedReceivedKeys).to.eql(['aliases', 'hidden', 'isFrozen', 'name']); + expect(sortedReceivedKeys).to.eql([ + 'aliases', + 'documents', + 'hidden', + 'isFrozen', + 'name', + 'size', + ]); expect(body.length > 1).to.be(true); // to contrast it with the next test });