Elasticsearch client: no longer default to using meta: true (#124488)

* Use `Client` interface instead of `KibanaClient`

* get rid of getKibanaEsClient and convertToKibanaClient

* get rid of last KibanaClient usages

* update usages and types in @kbn/securitysolution-es-utils

* fix some violations

* add sugar method around client mock

* update SO repository calls

* adapt more core usages

* export mock types

* batch 1

* batch 2

* batch 3

* batch 4

* batch 5

* batch 6

* batch 7

* batch 8

* batch 9

* security - batch 1

* security - batch 2

* security - batch 3

* last batch of initial violations

* fix resolve_time_pattern

* update generated doc

* fix /internal/index-pattern-management/preview_scripted_field endpoint

* fix monitoring's getLegacyClusterShim

* fix /api/snapshot_restore/privileges route

* fix UptimeESClient

* fix transforms/_nodes endpoint

* lint

* unit test fix - batch 1

* unit test fix - batch 2

* unit test fix - batch 3

* integration test fix - batch 1

* lint

* adapt ML client

* unit test fix - batch 4

* fix uptime test helper

* fix /api/transform/transforms/{transformId}/_update route

* fix ES client FTR test

* fix uptime unit test

* fix type errors on last unit tests

* fix RollupSearchStrategy call

* fix /internal/security/fields/{query} route

* fix GET /api/index_lifecycle_management/policies route

* fix mlClient.getDataFrameAnalytics

* fix APMEventClient

* fix security solution getBootstrapIndexExists

* fix data_enhanced's getSearchStatus

* remove unused @ts-expect-error

* fix unit tests due to latest code changes

* fix more calls in security_solution routes

* fix more calls in ml routes

* fix POST /api/index_management/component_templates route

* fix unit tests due to latest changes

* fix rule_registry's ResourceInstaller.createOrUpdateIndexTemplate

* fix more fleet client calls

* fix UA's GET cloud_backup_status route

* fix createLifecycleExecutorApiTest

* fix hasFleetServers

* fix unit tests due to latest changes

* changes due to last merge

* fix ml modelProvider.getModelsPipelines

* fix security_solution LifecycleQuery.search

* fix new CoreUsageDataService usage

* fix security solution's StatsQuery.search

* improve ml FTR assertions

* fix security_solution's EventsQuery.search

* fix EsClient type as we're keeping transport

* NITs

* clean RepositoryEsClient type

* update generated doc

* review comments

* adapt mlClient.anomalySearch signature

* remove unnecessary .then((body) => body)

* nit

* add unit tests for the client mocking functions

* fix new upgrade assistant /remote_clusters endpoint
This commit is contained in:
Pierre Gayvallet 2022-02-12 09:19:44 +01:00 committed by GitHub
parent 172bf98942
commit 6627bd8b3a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
685 changed files with 6851 additions and 7899 deletions

View file

@ -9,9 +9,5 @@ Client used to query the elasticsearch cluster.
<b>Signature:</b>
```typescript
export declare type ElasticsearchClient = Omit<KibanaClient, 'connectionPool' | 'transport' | 'serializer' | 'extend' | 'child' | 'close' | 'diagnostic'> & {
transport: {
request<TResponse = unknown>(params: TransportRequestParams, options?: TransportRequestOptions): Promise<TransportResult<TResponse>>;
};
};
export declare type ElasticsearchClient = Omit<Client, 'connectionPool' | 'serializer' | 'extend' | 'child' | 'close' | 'diagnostic'>;
```

View file

@ -114,7 +114,7 @@ export class PrebootExamplePlugin implements PrebootPlugin {
try {
return response.ok({
body: (await scopedClient.asCurrentUser.security.authenticate()).body,
body: await scopedClient.asCurrentUser.security.authenticate(),
});
} catch (err) {
return response.customError({ statusCode: 500, body: getDetailedErrorMessage(err) });

View file

@ -9,7 +9,7 @@
// Copied from src/core/server/elasticsearch/client/types.ts
// as these types aren't part of any package yet. Once they are, remove this completely
import type { KibanaClient } from '@elastic/elasticsearch/lib/api/kibana';
import type { Client } from '@elastic/elasticsearch';
/**
* Client used to query the elasticsearch cluster.
@ -17,6 +17,6 @@ import type { KibanaClient } from '@elastic/elasticsearch/lib/api/kibana';
* @public
*/
export type ElasticsearchClient = Omit<
KibanaClient,
'connectionPool' | 'transport' | 'serializer' | 'extend' | 'child' | 'close' | 'diagnostic'
Client,
'connectionPool' | 'serializer' | 'extend' | 'child' | 'close' | 'diagnostic'
>;

View file

@ -22,7 +22,7 @@ export const getBootstrapIndexExists = async (
index: string
): Promise<boolean> => {
try {
const { body } = await esClient.indices.getAlias({
const body = await esClient.indices.getAlias({
index: `${index}-*`,
name: index,
});

View file

@ -8,16 +8,6 @@
import type { ElasticsearchClient } from '../elasticsearch_client';
interface AliasesResponse {
[indexName: string]: {
aliases: {
[aliasName: string]: {
is_write_index: boolean;
};
};
};
}
interface IndexAlias {
alias: string;
index: string;
@ -39,7 +29,7 @@ export const getIndexAliases = async ({
esClient: ElasticsearchClient;
alias: string;
}): Promise<IndexAlias[]> => {
const response = await esClient.indices.getAlias<AliasesResponse>(
const response = await esClient.indices.getAlias(
{
name: alias,
},

View file

@ -23,7 +23,7 @@ export const getIndexCount = async ({
esClient: ElasticsearchClient;
index: string;
}): Promise<number> => {
const response = await esClient.count<{ count: number }>(
const response = await esClient.count(
{
index,
},

View file

@ -1,35 +0,0 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { KibanaClient } from '@elastic/elasticsearch/lib/api/kibana';
import type {
Client,
TransportRequestParams,
TransportRequestOptions,
TransportResult,
} from '@elastic/elasticsearch';
import { Transport } from '@elastic/elasticsearch';
// remove once https://github.com/elastic/kibana/issues/116095 is addressed
class KibanaTransport extends Transport {
request(params: TransportRequestParams, options?: TransportRequestOptions) {
const opts: TransportRequestOptions = options || {};
// Enforce the client to return TransportResult.
// It's required for bwc with responses in 7.x version.
if (opts?.meta === undefined) {
opts.meta = true;
}
return super.request(params, opts) as Promise<TransportResult<any, any>>;
}
}
export function convertToKibanaClient(esClient: Client): KibanaClient {
// @ts-expect-error @elastic/elasticsearch fix discrepancy between clients
return esClient.child({
Transport: KibanaTransport,
});
}

View file

@ -9,6 +9,5 @@
export { createTestEsCluster } from './test_es_cluster';
export type { CreateTestEsClusterOptions, EsTestCluster, ICluster } from './test_es_cluster';
export { esTestConfig } from './es_test_config';
export { convertToKibanaClient } from './client_to_kibana_client';
export { createEsClientForTesting, createEsClientForFtrConfig } from './es_client_for_testing';
export type { EsClientForTestingOptions } from './es_client_for_testing';

View file

@ -12,11 +12,9 @@ import del from 'del';
// @ts-expect-error in js
import { Cluster } from '@kbn/es';
import { Client, HttpConnection } from '@elastic/elasticsearch';
import type { KibanaClient } from '@elastic/elasticsearch/lib/api/kibana';
import type { ToolingLog } from '@kbn/dev-utils';
import { CI_PARALLEL_PROCESS_PREFIX } from '../ci_parallel_process_prefix';
import { esTestConfig } from './es_test_config';
import { convertToKibanaClient } from './client_to_kibana_client';
import { KIBANA_ROOT } from '../';
@ -53,7 +51,6 @@ export interface ICluster {
stop: () => Promise<void>;
cleanup: () => Promise<void>;
getClient: () => Client;
getKibanaEsClient: () => KibanaClient;
getHostUrls: () => string[];
}
@ -289,13 +286,6 @@ export function createTestEsCluster<
});
}
/**
* Returns an ES Client to the configured cluster
*/
getKibanaEsClient(): KibanaClient {
return convertToKibanaClient(this.getClient());
}
getUrl() {
if (this.nodes.length > 1) {
throw new Error(

View file

@ -34,7 +34,6 @@ export type {
export {
esTestConfig,
createTestEsCluster,
convertToKibanaClient,
createEsClientForTesting,
createEsClientForFtrConfig,
} from './es';

View file

@ -9,6 +9,7 @@
import { Action } from 'history';
import Boom from '@hapi/boom';
import { ByteSizeValue } from '@kbn/config-schema';
import type { Client } from '@elastic/elasticsearch';
import { ConfigPath } from '@kbn/config';
import { DetailedPeerCertificate } from 'tls';
import type { DocLinks } from '@kbn/doc-links';
@ -24,7 +25,6 @@ import { History as History_2 } from 'history';
import { Href } from 'history';
import { IconType } from '@elastic/eui';
import { IncomingHttpHeaders } from 'http';
import type { KibanaClient } from '@elastic/elasticsearch/lib/api/kibana';
import { Location as Location_2 } from 'history';
import { LocationDescriptorObject } from 'history';
import { Logger } from '@kbn/logging';
@ -44,9 +44,6 @@ import * as Rx from 'rxjs';
import { SchemaTypeError } from '@kbn/config-schema';
import type { ThemeVersion } from '@kbn/ui-shared-deps-npm';
import { TransitionPromptHook } from 'history';
import type { TransportRequestOptions } from '@elastic/elasticsearch';
import type { TransportRequestParams } from '@elastic/elasticsearch';
import type { TransportResult } from '@elastic/elasticsearch';
import { Type } from '@kbn/config-schema';
import { TypeOf } from '@kbn/config-schema';
import { UiCounterMetricType } from '@kbn/analytics';

View file

@ -207,47 +207,37 @@ describe('CoreUsageDataService', () => {
});
service.setup({ http, metrics, savedObjectsStartPromise, changedDeprecatedConfigPath$ });
const elasticsearch = elasticsearchServiceMock.createStart();
elasticsearch.client.asInternalUser.cat.indices.mockResolvedValueOnce({
body: [
{
name: '.kibana_task_manager_1',
'docs.count': '10',
'docs.deleted': '10',
'store.size': '1000',
'pri.store.size': '2000',
},
],
} as any);
elasticsearch.client.asInternalUser.count.mockResolvedValueOnce({
body: {
count: '15',
elasticsearch.client.asInternalUser.cat.indices.mockResponseOnce([
{
name: '.kibana_task_manager_1',
'docs.count': '10',
'docs.deleted': '10',
'store.size': '1000',
'pri.store.size': '2000',
},
] as any);
elasticsearch.client.asInternalUser.count.mockResponseOnce({
count: '15',
} as any);
elasticsearch.client.asInternalUser.cat.indices.mockResolvedValueOnce({
body: [
{
name: '.kibana_1',
'docs.count': '20',
'docs.deleted': '20',
'store.size': '2000',
'pri.store.size': '4000',
},
],
} as any);
elasticsearch.client.asInternalUser.count.mockResolvedValueOnce({
body: {
count: '10',
elasticsearch.client.asInternalUser.cat.indices.mockResponseOnce([
{
name: '.kibana_1',
'docs.count': '20',
'docs.deleted': '20',
'store.size': '2000',
'pri.store.size': '4000',
},
] as any);
elasticsearch.client.asInternalUser.count.mockResponseOnce({
count: '10',
} as any);
elasticsearch.client.asInternalUser.search.mockResolvedValueOnce({
body: {
hits: { total: { value: 6 } },
aggregations: {
aliases: {
buckets: {
active: { doc_count: 1 },
disabled: { doc_count: 2 },
},
elasticsearch.client.asInternalUser.search.mockResponseOnce({
hits: { total: { value: 6 } },
aggregations: {
aliases: {
buckets: {
active: { doc_count: 1 },
disabled: { doc_count: 2 },
},
},
},

View file

@ -136,12 +136,12 @@ export class CoreUsageDataService
// to map back from the index to the alias. So we have to make an API
// call for every alias. The document count is the lucene document count.
const catIndicesResults = await elasticsearch.client.asInternalUser.cat
.indices<any[]>({
.indices({
index,
format: 'JSON',
bytes: 'b',
})
.then(({ body }) => {
.then((body) => {
const stats = body[0];
return {
@ -160,7 +160,7 @@ export class CoreUsageDataService
.count({
index,
})
.then(({ body }) => {
.then((body) => {
return {
savedObjectsDocsCount: body.count ? body.count : 0,
};
@ -182,7 +182,7 @@ export class CoreUsageDataService
private async getSavedObjectAliasUsageData(elasticsearch: ElasticsearchServiceStart) {
// Note: this agg can be changed to use `savedObjectsRepository.find` in the future after `filters` is supported.
// See src/core/server/saved_objects/service/lib/aggregations/aggs_types/bucket_aggs.ts for supported aggregations.
const { body: resp } = await elasticsearch.client.asInternalUser.search<
const resp = await elasticsearch.client.asInternalUser.search<
unknown,
{ aliases: UsageDataAggs }
>({

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import type { KibanaClient } from '@elastic/elasticsearch/lib/api/kibana';
import type { Client } from '@elastic/elasticsearch';
import { Logger } from '../../logging';
import { IAuthHeadersStorage, Headers, isKibanaRequest, isRealRequest } from '../../http';
import { ensureRawRequest, filterHeaders } from '../../http/router';
@ -60,12 +60,12 @@ export interface ICustomClusterClient extends IClusterClient {
export class ClusterClient implements ICustomClusterClient {
private readonly config: ElasticsearchClientConfig;
private readonly authHeaders?: IAuthHeadersStorage;
private readonly rootScopedClient: KibanaClient;
private readonly rootScopedClient: Client;
private readonly getUnauthorizedErrorHandler: () => UnauthorizedErrorHandler | undefined;
private readonly getExecutionContext: () => string | undefined;
private isClosed = false;
public readonly asInternalUser: KibanaClient;
public readonly asInternalUser: Client;
constructor({
config,

View file

@ -7,7 +7,6 @@
*/
import { Client, HttpConnection } from '@elastic/elasticsearch';
import type { KibanaClient } from '@elastic/elasticsearch/lib/api/kibana';
import { Logger } from '../../logging';
import { parseClientOptions, ElasticsearchClientConfig } from './client_config';
import { instrumentEsQueryAndDeprecationLogger } from './log_query_and_deprecation';
@ -28,7 +27,7 @@ export const configureClient = (
scoped?: boolean;
getExecutionContext?: () => string | undefined;
}
): KibanaClient => {
): Client => {
const clientOptions = parseClientOptions(config, scoped);
const KibanaTransport = createTransport({ getExecutionContext });
@ -40,5 +39,5 @@ export const configureClient = (
instrumentEsQueryAndDeprecationLogger({ logger, client, type });
return client as KibanaClient;
return client;
};

View file

@ -116,7 +116,7 @@ describe('createTransport', () => {
});
describe('`meta` option', () => {
it('adds `meta: true` to the options when not provided', async () => {
it('does not adds `meta: true` to the options when not provided', async () => {
const transportClass = createTransportClass();
const transport = new transportClass(baseConstructorParams);
const requestOptions = { method: 'GET', path: '/' };
@ -126,7 +126,7 @@ describe('createTransport', () => {
expect(transportRequestMock).toHaveBeenCalledTimes(1);
expect(transportRequestMock).toHaveBeenCalledWith(
expect.any(Object),
expect.objectContaining({
expect.not.objectContaining({
meta: true,
})
);

View file

@ -46,11 +46,6 @@ export const createTransport = ({
// rewrites headers['x-opaque-id'] if it presents
opts.opaqueId = opaqueId;
}
// Enforce the client to return TransportResult.
// It's required for bwc with responses in 7.x version.
if (opts.meta === undefined) {
opts.meta = true;
}
// add stored headers to the options
opts.headers = {

View file

@ -47,9 +47,156 @@ describe('Mocked client', () => {
it('`child` should be mocked and return a mocked Client', () => {
expectMocked(client.child);
const child = client.child();
const child = client.child({});
expect(child).not.toBe(client);
expectMocked(child.search);
});
describe('mockResponse', () => {
beforeEach(() => {
client.ping.mockReset();
client.ping.mockResponse(true, { statusCode: 217, headers: { foo: 'bar' } });
});
it('returns the body when `meta` is false', async () => {
const response = await client.ping({}, { meta: false });
expect(response).toBe(true);
});
it('returns the response when `meta` is true', async () => {
const response = await client.ping({}, { meta: true });
expect(response).toEqual({
body: true,
statusCode: 217,
headers: { foo: 'bar' },
warnings: [],
meta: {},
});
});
it('returns the body when `meta` is not provided', async () => {
const response = await client.ping({}, {});
expect(response).toBe(true);
});
it('mocks the response multiple times', async () => {
expect(await client.ping({}, {})).toBe(true);
expect(await client.ping({}, {})).toBe(true);
expect(await client.ping({}, {})).toBe(true);
});
});
describe('mockResponseOnce', () => {
beforeEach(() => {
client.ping.mockReset();
client.ping.mockResponseOnce(true, { statusCode: 217, headers: { foo: 'bar' } });
});
it('returns the body when `meta` is false', async () => {
const response = await client.ping({}, { meta: false });
expect(response).toBe(true);
});
it('returns the response when `meta` is true', async () => {
const response = await client.ping({}, { meta: true });
expect(response).toEqual({
body: true,
statusCode: 217,
headers: { foo: 'bar' },
warnings: [],
meta: {},
});
});
it('returns the body when `meta` is not provided', async () => {
const response = await client.ping({}, {});
expect(response).toBe(true);
});
it('mocks the response only once', async () => {
expect(await client.ping({}, {})).toBe(true);
expect(await client.ping({}, {})).toBe(undefined);
});
it('can be chained', async () => {
client.ping.mockReset();
client.ping.mockResponseOnce(true);
client.ping.mockResponseOnce(false);
client.ping.mockResponseOnce(true);
expect(await client.ping({}, {})).toBe(true);
expect(await client.ping({}, {})).toBe(false);
expect(await client.ping({}, {})).toBe(true);
});
});
describe('mockResponseImplementation', () => {
beforeEach(() => {
client.ping.mockReset();
client.ping.mockResponseImplementation(() => ({
body: true,
statusCode: 217,
headers: { foo: 'bar' },
}));
});
it('returns the body when `meta` is false', async () => {
const response = await client.ping({}, { meta: false });
expect(response).toBe(true);
});
it('returns the response when `meta` is true', async () => {
const response = await client.ping({}, { meta: true });
expect(response).toEqual({
body: true,
statusCode: 217,
headers: { foo: 'bar' },
warnings: [],
meta: {},
});
});
it('returns the body when `meta` is not provided', async () => {
const response = await client.ping({}, {});
expect(response).toBe(true);
});
it('mocks the response multiple times', async () => {
expect(await client.ping({}, {})).toBe(true);
expect(await client.ping({}, {})).toBe(true);
expect(await client.ping({}, {})).toBe(true);
});
});
describe('mockResponseImplementationOnce', () => {
beforeEach(() => {
client.ping.mockReset();
client.ping.mockResponseImplementationOnce(() => ({
body: true,
statusCode: 217,
headers: { foo: 'bar' },
}));
});
it('returns the body when `meta` is false', async () => {
const response = await client.ping({}, { meta: false });
expect(response).toBe(true);
});
it('returns the response when `meta` is true', async () => {
const response = await client.ping({}, { meta: true });
expect(response).toEqual({
body: true,
statusCode: 217,
headers: { foo: 'bar' },
warnings: [],
meta: {},
});
});
it('returns the body when `meta` is not provided', async () => {
const response = await client.ping({}, {});
expect(response).toBe(true);
});
it('mocks the response only once', async () => {
expect(await client.ping({}, {})).toBe(true);
expect(await client.ping({}, {})).toBe(undefined);
});
it('can be chained', async () => {
client.ping.mockReset();
client.ping.mockResponseImplementationOnce(() => ({ body: true }));
client.ping.mockResponseImplementationOnce(() => ({ body: false }));
client.ping.mockResponseImplementationOnce(() => ({ body: true }));
expect(await client.ping({}, {})).toBe(true);
expect(await client.ping({}, {})).toBe(false);
expect(await client.ping({}, {})).toBe(true);
});
});
});

View file

@ -6,9 +6,8 @@
* Side Public License, v 1.
*/
import type { KibanaClient } from '@elastic/elasticsearch/lib/api/kibana';
import type { TransportResult } from '@elastic/elasticsearch';
import type { DeeplyMockedKeys } from '@kbn/utility-types/jest';
import type { Client } from '@elastic/elasticsearch';
import type { TransportResult, TransportRequestOptions } from '@elastic/elasticsearch';
import type { PublicKeys } from '@kbn/utility-types';
import { ElasticsearchClient } from './types';
import { ICustomClusterClient } from './cluster_client';
@ -21,11 +20,110 @@ const omittedProps = [
'transport',
'serializer',
'helpers',
] as Array<PublicKeys<KibanaClient>>;
] as Array<PublicKeys<Client>>;
export type DeeplyMockedApi<T> = {
[P in keyof T]: T[P] extends (...args: any[]) => any
? ClientApiMockInstance<ReturnType<T[P]>, Parameters<T[P]>>
: DeeplyMockedApi<T[P]>;
} & T;
export interface ClientApiMockInstance<T, Y extends any[]> extends jest.MockInstance<T, Y> {
/**
* Helper API around `mockReturnValue` returning either the body or the whole TransportResult
* depending on the `meta` parameter used during the call
*/
mockResponse(value: Awaited<T>, opts?: Partial<Omit<TransportResult<T>, 'body'>>): this;
/**
* Helper API around `mockReturnValueOnce` returning either the body or the whole TransportResult
* depending on the `meta` parameter used during the call
*/
mockResponseOnce(value: Awaited<T>, opts?: Partial<Omit<TransportResult<T>, 'body'>>): this;
/**
* Helper API around `mockImplementation` returning either the body or the whole TransportResult
* depending on the `meta` parameter used during the call
*/
mockResponseImplementation(handler: (...args: Y) => Partial<TransportResult<Awaited<T>>>): this;
/**
* Helper API around `mockImplementationOnce` returning either the body or the whole TransportResult
* depending on the `meta` parameter used during the call
*/
mockResponseImplementationOnce(
handler: (...args: Y) => Partial<TransportResult<Awaited<T>>>
): this;
}
const createMockedApi = <
T = unknown,
Y extends [any, TransportRequestOptions] = [any, TransportRequestOptions]
>(): ClientApiMockInstance<T, Y> => {
const mock: ClientApiMockInstance<T, Y> = jest.fn() as any;
mock.mockResponse = (value: T, opts?: Partial<Omit<TransportResult<T>, 'body'>>) => {
mock.mockImplementation((args: unknown, options?: TransportRequestOptions) => {
const meta = options?.meta ?? false;
if (meta) {
return Promise.resolve(createApiResponse({ ...opts, body: value })) as any;
} else {
return Promise.resolve(value) as Promise<T>;
}
});
return mock;
};
mock.mockResponseOnce = (value: T, opts?: Partial<Omit<TransportResult<T>, 'body'>>) => {
mock.mockImplementationOnce((args: unknown, options?: TransportRequestOptions) => {
const meta = options?.meta ?? false;
if (meta) {
return Promise.resolve(createApiResponse({ ...opts, body: value })) as any;
} else {
return Promise.resolve(value) as Promise<T>;
}
});
return mock;
};
mock.mockResponseImplementation = (
handler: (...args: Y) => Partial<TransportResult<Awaited<T>>>
) => {
mock.mockImplementation((args: unknown, options?: TransportRequestOptions) => {
const meta = options?.meta ?? false;
// @ts-expect-error couldn't do better while keeping compatibility this jest.MockInstance
const response = handler(args, options);
if (meta) {
return Promise.resolve(createApiResponse(response)) as any;
} else {
return Promise.resolve(response.body ?? {}) as Promise<T>;
}
});
return mock;
};
mock.mockResponseImplementationOnce = (
handler: (...args: Y) => Partial<TransportResult<Awaited<T>>>
) => {
mock.mockImplementationOnce((args: unknown, options?: TransportRequestOptions) => {
const meta = options?.meta ?? false;
// @ts-expect-error couldn't do better while keeping compatibility this jest.MockInstance
const response = handler(args, options);
if (meta) {
return Promise.resolve(createApiResponse(response)) as any;
} else {
return Promise.resolve(response.body ?? {}) as Promise<T>;
}
});
return mock;
};
return mock;
};
// use jest.requireActual() to prevent weird errors when people mock @elastic/elasticsearch
const { Client: UnmockedClient } = jest.requireActual('@elastic/elasticsearch');
const createInternalClientMock = (res?: Promise<unknown>): DeeplyMockedKeys<KibanaClient> => {
const createInternalClientMock = (res?: Promise<unknown>): DeeplyMockedApi<Client> => {
// we mimic 'reflection' on a concrete instance of the client to generate the mocked functions.
const client = new UnmockedClient({
node: 'http://127.0.0.1',
@ -50,14 +148,16 @@ const createInternalClientMock = (res?: Promise<unknown>): DeeplyMockedKeys<Kiba
.filter(([key]) => !omitted.includes(key))
.forEach(([key, descriptor]) => {
if (typeof descriptor.value === 'function') {
obj[key] = jest.fn(() => res ?? createSuccessTransportRequestPromise({}));
const mock = createMockedApi();
mock.mockImplementation(() => res ?? createSuccessTransportRequestPromise({}));
obj[key] = mock;
} else if (typeof obj[key] === 'object' && obj[key] != null) {
mockify(obj[key], omitted);
}
});
};
mockify(client, omittedProps);
mockify(client, omittedProps as string[]);
client.close = jest.fn().mockReturnValue(Promise.resolve());
client.child = jest.fn().mockImplementation(() => createInternalClientMock());
@ -81,10 +181,10 @@ const createInternalClientMock = (res?: Promise<unknown>): DeeplyMockedKeys<Kiba
request: jest.fn(),
};
return client as DeeplyMockedKeys<KibanaClient>;
return client as DeeplyMockedApi<Client>;
};
export type ElasticsearchClientMock = DeeplyMockedKeys<ElasticsearchClient>;
export type ElasticsearchClientMock = DeeplyMockedApi<ElasticsearchClient>;
const createClientMock = (res?: Promise<unknown>): ElasticsearchClientMock =>
createInternalClientMock(res) as unknown as ElasticsearchClientMock;
@ -138,13 +238,12 @@ const createSuccessTransportRequestPromise = <T>(
body: T,
{ statusCode = 200 }: { statusCode?: number } = {},
headers: Record<string, string | string[]> = { [PRODUCT_RESPONSE_HEADER]: 'Elasticsearch' }
): Promise<TransportResult<T>> => {
): Promise<TransportResult<T> & T> => {
const response = createApiResponse({ body, statusCode, headers });
return Promise.resolve(response) as Promise<TransportResult<T>>;
return Promise.resolve(response) as Promise<TransportResult<T> & T>;
};
const createErrorTransportRequestPromise = (err: any): Promise<TransportResult<never>> => {
const createErrorTransportRequestPromise = (err: any): Promise<never> => {
return Promise.reject(err);
};

View file

@ -23,37 +23,27 @@ describe('retryCallCluster', () => {
});
it('returns response from ES API call in case of success', async () => {
const successReturn = elasticsearchClientMock.createSuccessTransportRequestPromise({
...dummyBody,
});
client.asyncSearch.get.mockReturnValue(successReturn);
client.asyncSearch.get.mockResponseOnce(dummyBody);
const result = await retryCallCluster(() => client.asyncSearch.get({} as any));
expect(result.body).toEqual(dummyBody);
expect(result).toEqual(dummyBody);
});
it('retries ES API calls that rejects with `NoLivingConnectionsError`', async () => {
const successReturn = elasticsearchClientMock.createSuccessTransportRequestPromise({
...dummyBody,
});
client.asyncSearch.get
.mockImplementationOnce(() =>
createErrorReturn(new errors.NoLivingConnectionsError('no living connections', {} as any))
)
.mockImplementationOnce(() => successReturn);
.mockResponseOnce(dummyBody);
const result = await retryCallCluster(() => client.asyncSearch.get({} as any));
expect(result.body).toEqual(dummyBody);
expect(result).toEqual(dummyBody);
});
it('rejects when ES API calls reject with other errors', async () => {
client.ping
.mockImplementationOnce(() => createErrorReturn(new Error('unknown error')))
.mockImplementationOnce(() =>
elasticsearchClientMock.createSuccessTransportRequestPromise({ ...dummyBody })
);
.mockResponseOnce(dummyBody);
await expect(retryCallCluster(() => client.ping())).rejects.toMatchInlineSnapshot(
`[Error: unknown error]`
@ -69,9 +59,7 @@ describe('retryCallCluster', () => {
createErrorReturn(new errors.NoLivingConnectionsError('no living connections', {} as any))
)
.mockImplementationOnce(() => createErrorReturn(new Error('unknown error')))
.mockImplementationOnce(() =>
elasticsearchClientMock.createSuccessTransportRequestPromise({ ...dummyBody })
);
.mockResponseOnce(dummyBody);
await expect(retryCallCluster(() => client.ping())).rejects.toMatchInlineSnapshot(
`[Error: unknown error]`
@ -92,9 +80,7 @@ describe('migrationRetryCallCluster', () => {
client.ping
.mockImplementationOnce(() => createErrorReturn(error))
.mockImplementationOnce(() => createErrorReturn(error))
.mockImplementationOnce(() =>
elasticsearchClientMock.createSuccessTransportRequestPromise({ ...dummyBody })
);
.mockResponseOnce(dummyBody);
};
it('retries ES API calls that rejects with `NoLivingConnectionsError`', async () => {
@ -103,21 +89,21 @@ describe('migrationRetryCallCluster', () => {
);
const result = await migrationRetryCallCluster(() => client.ping(), logger, 1);
expect(result.body).toEqual(dummyBody);
expect(result).toEqual(dummyBody);
});
it('retries ES API calls that rejects with `ConnectionError`', async () => {
mockClientPingWithErrorBeforeSuccess(new errors.ConnectionError('connection error', {} as any));
const result = await migrationRetryCallCluster(() => client.ping(), logger, 1);
expect(result.body).toEqual(dummyBody);
expect(result).toEqual(dummyBody);
});
it('retries ES API calls that rejects with `TimeoutError`', async () => {
mockClientPingWithErrorBeforeSuccess(new errors.TimeoutError('timeout error', {} as any));
const result = await migrationRetryCallCluster(() => client.ping(), logger, 1);
expect(result.body).toEqual(dummyBody);
expect(result).toEqual(dummyBody);
});
it('retries ES API calls that rejects with 503 `ResponseError`', async () => {
@ -128,7 +114,7 @@ describe('migrationRetryCallCluster', () => {
);
const result = await migrationRetryCallCluster(() => client.ping(), logger, 1);
expect(result.body).toEqual(dummyBody);
expect(result).toEqual(dummyBody);
});
it('retries ES API calls that rejects 401 `ResponseError`', async () => {
@ -139,7 +125,7 @@ describe('migrationRetryCallCluster', () => {
);
const result = await migrationRetryCallCluster(() => client.ping(), logger, 1);
expect(result.body).toEqual(dummyBody);
expect(result).toEqual(dummyBody);
});
it('retries ES API calls that rejects with 403 `ResponseError`', async () => {
@ -150,7 +136,7 @@ describe('migrationRetryCallCluster', () => {
);
const result = await migrationRetryCallCluster(() => client.ping(), logger, 1);
expect(result.body).toEqual(dummyBody);
expect(result).toEqual(dummyBody);
});
it('retries ES API calls that rejects with 408 `ResponseError`', async () => {
@ -161,7 +147,7 @@ describe('migrationRetryCallCluster', () => {
);
const result = await migrationRetryCallCluster(() => client.ping(), logger, 1);
expect(result.body).toEqual(dummyBody);
expect(result).toEqual(dummyBody);
});
it('retries ES API calls that rejects with 410 `ResponseError`', async () => {
@ -172,7 +158,7 @@ describe('migrationRetryCallCluster', () => {
);
const result = await migrationRetryCallCluster(() => client.ping(), logger, 1);
expect(result.body).toEqual(dummyBody);
expect(result).toEqual(dummyBody);
});
it('retries ES API calls that rejects with `snapshot_in_progress_exception` `ResponseError`', async () => {
@ -188,7 +174,7 @@ describe('migrationRetryCallCluster', () => {
);
const result = await migrationRetryCallCluster(() => client.ping(), logger, 1);
expect(result.body).toEqual(dummyBody);
expect(result).toEqual(dummyBody);
});
it('logs only once for each unique error message', async () => {

View file

@ -6,12 +6,7 @@
* Side Public License, v 1.
*/
import type { KibanaClient } from '@elastic/elasticsearch/lib/api/kibana';
import type {
TransportResult,
TransportRequestOptions,
TransportRequestParams,
} from '@elastic/elasticsearch';
import type { Client } from '@elastic/elasticsearch';
/**
* Client used to query the elasticsearch cluster.
@ -19,16 +14,9 @@ import type {
* @public
*/
export type ElasticsearchClient = Omit<
KibanaClient,
'connectionPool' | 'transport' | 'serializer' | 'extend' | 'child' | 'close' | 'diagnostic'
> & {
transport: {
request<TResponse = unknown>(
params: TransportRequestParams,
options?: TransportRequestOptions
): Promise<TransportResult<TResponse>>;
};
};
Client,
'connectionPool' | 'serializer' | 'extend' | 'child' | 'close' | 'diagnostic'
>;
/**
* All response typings are maintained until elasticsearch-js provides them out of the box

View file

@ -39,16 +39,19 @@ describe('elasticsearch clients', () => {
it('does not return deprecation warning when x-elastic-product-origin header is set', async () => {
// Header should be automatically set by Core
const resp1 =
await kibanaServer.coreStart.elasticsearch.client.asInternalUser.indices.getSettings({
index: '.kibana',
});
await kibanaServer.coreStart.elasticsearch.client.asInternalUser.indices.getSettings(
{
index: '.kibana',
},
{ meta: true }
);
expect(resp1.headers).not.toHaveProperty('warning');
// Also test setting it explicitly
const resp2 =
await kibanaServer.coreStart.elasticsearch.client.asInternalUser.indices.getSettings(
{ index: '.kibana' },
{ headers: { 'x-elastic-product-origin': 'kibana' } }
{ headers: { 'x-elastic-product-origin': 'kibana' }, meta: true }
);
expect(resp2.headers).not.toHaveProperty('warning');
});

View file

@ -18,9 +18,7 @@ describe('isInlineScriptingEnabled', () => {
});
const mockSettingsValue = (settings: estypes.ClusterGetSettingsResponse) => {
client.cluster.getSettings.mockReturnValue(
elasticsearchServiceMock.createSuccessTransportRequestPromise(settings)
);
client.cluster.getSettings.mockResolvedValue(settings);
};
it('returns `true` if all settings are empty', async () => {

View file

@ -15,7 +15,7 @@ export const isInlineScriptingEnabled = async ({
}: {
client: ElasticsearchClient;
}): Promise<boolean> => {
const { body: settings } = await client.cluster.getSettings({
const settings = await client.cluster.getSettings({
include_defaults: true,
flat_settings: true,
});

View file

@ -18,10 +18,6 @@ const mockLogger = mockLoggerFactory.get('mock logger');
const KIBANA_VERSION = '5.1.0';
const createEsSuccess = elasticsearchClientMock.createSuccessTransportRequestPromise;
const createEsErrorReturn = (err: any) =>
elasticsearchClientMock.createErrorTransportRequestPromise(err);
function createNodes(...versions: string[]): NodesInfo {
const nodes = {} as any;
versions
@ -140,10 +136,10 @@ describe('pollEsNodesVersion', () => {
const nodeInfosSuccessOnce = (infos: NodesInfo) => {
// @ts-expect-error not full interface
internalClient.nodes.info.mockImplementationOnce(() => createEsSuccess(infos));
internalClient.nodes.info.mockResponseOnce(infos);
};
const nodeInfosErrorOnce = (error: any) => {
internalClient.nodes.info.mockImplementationOnce(() => createEsErrorReturn(new Error(error)));
internalClient.nodes.info.mockImplementationOnce(() => Promise.reject(new Error(error)));
};
it('returns isCompatible=false and keeps polling when a poll request throws', (done) => {
@ -317,13 +313,9 @@ describe('pollEsNodesVersion', () => {
expect.assertions(1);
// @ts-expect-error we need to return an incompatible type to use the testScheduler here
internalClient.nodes.info.mockReturnValueOnce([
{ body: createNodes('5.1.0', '5.2.0', '5.0.0') },
]);
internalClient.nodes.info.mockReturnValueOnce([createNodes('5.1.0', '5.2.0', '5.0.0')]);
// @ts-expect-error we need to return an incompatible type to use the testScheduler here
internalClient.nodes.info.mockReturnValueOnce([
{ body: createNodes('5.1.1', '5.2.0', '5.0.0') },
]);
internalClient.nodes.info.mockReturnValueOnce([createNodes('5.1.1', '5.2.0', '5.0.0')]);
getTestScheduler().run(({ expectObservable }) => {
const expected = 'a 99ms (b|)';
@ -359,11 +351,11 @@ describe('pollEsNodesVersion', () => {
internalClient.nodes.info.mockReturnValueOnce(
// @ts-expect-error we need to return an incompatible type to use the testScheduler here
of({ body: createNodes('5.1.0', '5.2.0', '5.0.0') }).pipe(delay(100))
of(createNodes('5.1.0', '5.2.0', '5.0.0')).pipe(delay(100))
);
internalClient.nodes.info.mockReturnValueOnce(
// @ts-expect-error we need to return an incompatible type to use the testScheduler here
of({ body: createNodes('5.1.1', '5.2.0', '5.0.0') }).pipe(delay(100))
of(createNodes('5.1.1', '5.2.0', '5.0.0')).pipe(delay(100))
);
const esNodesCompatibility$ = pollEsNodesVersion({

View file

@ -120,6 +120,7 @@ export function mapNodesVersionCompatibility(
kibanaVersion,
};
}
// Returns true if NodesVersionCompatibility nodesInfoRequestError is the same
function compareNodesInfoErrorMessages(
prev: NodesVersionCompatibility,
@ -127,6 +128,7 @@ function compareNodesInfoErrorMessages(
): boolean {
return prev.nodesInfoRequestError?.message === curr.nodesInfoRequestError?.message;
}
// Returns true if two NodesVersionCompatibility entries match
function compareNodes(prev: NodesVersionCompatibility, curr: NodesVersionCompatibility) {
const nodesEqual = (n: NodeInfo, m: NodeInfo) => n.ip === m.ip && n.version === m.version;
@ -152,11 +154,10 @@ export const pollEsNodesVersion = ({
return timer(0, healthCheckInterval).pipe(
exhaustMap(() => {
return from(
internalClient.nodes.info<NodesInfo>({
internalClient.nodes.info({
filter_path: ['nodes.*.version', 'nodes.*.http.publish_address', 'nodes.*.ip'],
})
).pipe(
map(({ body }) => body),
catchError((nodesInfoRequestError) => {
return of({ nodes: {}, nodesInfoRequestError });
})

View file

@ -58,7 +58,10 @@ describe('trace', () => {
const router = createRouter('');
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
const { headers } = await context.core.elasticsearch.client.asInternalUser.ping();
const { headers } = await context.core.elasticsearch.client.asInternalUser.ping(
{},
{ meta: true }
);
return res.ok({ body: headers || {} });
});
@ -80,7 +83,10 @@ describe('trace', () => {
const router = createRouter('');
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(
{},
{ meta: true }
);
return res.ok({ body: headers || {} });
});
@ -102,7 +108,10 @@ describe('trace', () => {
const router = createRouter('');
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
const { headers } = await context.core.elasticsearch.client.asInternalUser.ping();
const { headers } = await context.core.elasticsearch.client.asInternalUser.ping(
{},
{ meta: true }
);
return res.ok({ body: headers || {} });
});
@ -120,7 +129,10 @@ describe('trace', () => {
const router = createRouter('');
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(
{},
{ meta: true }
);
return res.ok({ body: headers || {} });
});
@ -142,6 +154,7 @@ describe('trace', () => {
{},
{
opaqueId: 'new-opaque-id',
meta: true,
}
);
return res.ok({ body: headers || {} });
@ -183,7 +196,10 @@ describe('trace', () => {
const router = createRouter('');
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(
{},
{ meta: true }
);
return res.ok({ body: headers || {} });
});
@ -206,7 +222,10 @@ describe('trace', () => {
const router = createRouter('');
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
executionContext.set(parentContext);
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(
{},
{ meta: true }
);
return res.ok({
body: {
context: executionContext.get()?.toJSON(),
@ -319,7 +338,10 @@ describe('trace', () => {
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
executionContext.set(parentContext);
await delay(id-- * 100);
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(
{},
{ meta: true }
);
return res.ok({ body: headers || {} });
});
@ -429,7 +451,10 @@ describe('trace', () => {
const router = createRouter('');
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(
{},
{ meta: true }
);
return res.ok({ body: headers || {} });
});
@ -450,7 +475,10 @@ describe('trace', () => {
const router = createRouter('');
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
const { headers } = await context.core.elasticsearch.client.asInternalUser.ping();
const { headers } = await context.core.elasticsearch.client.asInternalUser.ping(
{},
{ meta: true }
);
return res.ok({ body: headers || {} });
});
@ -471,7 +499,10 @@ describe('trace', () => {
const router = createRouter('');
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(
{},
{ meta: true }
);
return res.ok({ body: headers || {} });
});
@ -494,7 +525,10 @@ describe('trace', () => {
const router = createRouter('');
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
executionContext.set(parentContext);
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(
{},
{ meta: true }
);
return res.ok({ body: headers || {} });
});
@ -523,7 +557,10 @@ describe('trace', () => {
};
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
executionContext.set(ctx);
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping(
{},
{ meta: true }
);
return res.ok({ body: headers || {} });
});
@ -591,7 +628,7 @@ describe('trace', () => {
};
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
const { headers } = await executionContext.withContext(newContext, () =>
context.core.elasticsearch.client.asCurrentUser.ping()
context.core.elasticsearch.client.asCurrentUser.ping({}, { meta: true })
);
return res.ok({ body: headers || {} });
});

View file

@ -297,7 +297,9 @@ describe('http service', () => {
const router = createRouter('/new-platform');
router.get({ path: '/', validate: false }, async (context, req, res) => {
try {
const result = await elasticsearch.client.asScoped(req).asInternalUser.ping();
const result = await elasticsearch.client
.asScoped(req)
.asInternalUser.ping({}, { meta: true });
return res.ok({
body: result,
});

View file

@ -63,6 +63,8 @@ export { deprecationsServiceMock } from './deprecations/deprecations_service.moc
export { executionContextServiceMock } from './execution_context/execution_context_service.mock';
export { docLinksServiceMock } from './doc_links/doc_links_service.mock';
export type { ElasticsearchClientMock } from './elasticsearch/client/mocks';
type MockedPluginInitializerConfig<T> = jest.Mocked<PluginInitializerContext<T>['config']>;
export function pluginInitializerContextConfigMock<T>(config: T) {

View file

@ -48,9 +48,7 @@ describe('unknown saved object types deprecation', () => {
describe('getUnknownTypesDeprecations', () => {
beforeEach(() => {
esClient.asInternalUser.search.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise(createSearchResponse(0))
);
esClient.asInternalUser.search.mockResponse(createSearchResponse(0));
});
it('calls `esClient.asInternalUser.search` with the correct parameters', async () => {
@ -76,9 +74,7 @@ describe('unknown saved object types deprecation', () => {
});
it('returns no deprecation if no unknown type docs are found', async () => {
esClient.asInternalUser.search.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise(createSearchResponse(0))
);
esClient.asInternalUser.search.mockResponse(createSearchResponse(0));
const deprecations = await getUnknownTypesDeprecations({
esClient,
@ -91,9 +87,7 @@ describe('unknown saved object types deprecation', () => {
});
it('returns a deprecation if any unknown type docs are found', async () => {
esClient.asInternalUser.search.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise(createSearchResponse(1))
);
esClient.asInternalUser.search.mockResponse(createSearchResponse(1));
const deprecations = await getUnknownTypesDeprecations({
esClient,

View file

@ -74,7 +74,7 @@ const getUnknownSavedObjects = async ({
});
const query = getUnknownTypesQuery(knownTypes);
const { body } = await esClient.asInternalUser.search<SavedObjectsRawDocSource>({
const body = await esClient.asInternalUser.search<SavedObjectsRawDocSource>({
index: targetIndices,
body: {
size: 10000,

View file

@ -21,7 +21,7 @@ describe('bulkOverwriteTransformedDocuments', () => {
it('resolves with `right:bulk_index_succeeded` if no error is encountered', async () => {
const client = elasticsearchClientMock.createInternalClient(
elasticsearchClientMock.createSuccessTransportRequestPromise({
Promise.resolve({
items: [
{
index: {
@ -52,7 +52,7 @@ describe('bulkOverwriteTransformedDocuments', () => {
it('resolves with `right:bulk_index_succeeded` if version conflict errors are encountered', async () => {
const client = elasticsearchClientMock.createInternalClient(
elasticsearchClientMock.createSuccessTransportRequestPromise({
Promise.resolve({
items: [
{
index: {
@ -113,7 +113,7 @@ describe('bulkOverwriteTransformedDocuments', () => {
it('resolves with `left:target_index_had_write_block` if all errors are write block exceptions', async () => {
const client = elasticsearchClientMock.createInternalClient(
elasticsearchClientMock.createSuccessTransportRequestPromise({
Promise.resolve({
items: [
{
index: {
@ -158,7 +158,7 @@ describe('bulkOverwriteTransformedDocuments', () => {
});
const client = elasticsearchClientMock.createInternalClient(
elasticsearchClientMock.createSuccessTransportRequestPromise({
Promise.resolve({
items: [
{
index: {

View file

@ -93,7 +93,7 @@ export const bulkOverwriteTransformedDocuments =
.then((res) => {
// Filter out version_conflict_engine_exception since these just mean
// that another instance already updated these documents
const errors: estypes.ErrorCause[] = (res.body.items ?? [])
const errors: estypes.ErrorCause[] = (res.items ?? [])
.filter((item) => item.index?.error)
.map((item) => item.index!.error!)
.filter(({ type }) => type !== 'version_conflict_engine_exception');

View file

@ -45,7 +45,11 @@ export const calculateExcludeFilters =
Object.entries(excludeFromUpgradeFilterHooks).map(([soType, hook]) =>
withTimeout({
promise: Promise.resolve(
hook({ readonlyEsClient: { search: client.search.bind(client) } })
hook({
readonlyEsClient: {
search: client.search.bind(client) as ElasticsearchClient['search'],
},
})
),
timeoutMs: hookTimeoutMs,
})

View file

@ -53,7 +53,7 @@ describe('checkForUnknownDocs', () => {
it('calls `client.search` with the correct parameters', async () => {
const client = elasticsearchClientMock.createInternalClient(
elasticsearchClientMock.createSuccessTransportRequestPromise({ hits: { hits: [] } })
Promise.resolve({ hits: { hits: [] } })
);
const task = checkForUnknownDocs({
@ -85,7 +85,7 @@ describe('checkForUnknownDocs', () => {
it('resolves with `Either.right` when no unknown docs are found', async () => {
const client = elasticsearchClientMock.createInternalClient(
elasticsearchClientMock.createSuccessTransportRequestPromise({ hits: { hits: [] } })
Promise.resolve({ hits: { hits: [] } })
);
const task = checkForUnknownDocs({
@ -103,7 +103,7 @@ describe('checkForUnknownDocs', () => {
it('resolves with `Either.left` when unknown docs are found', async () => {
const client = elasticsearchClientMock.createInternalClient(
elasticsearchClientMock.createSuccessTransportRequestPromise({
Promise.resolve({
hits: {
hits: [
{ _id: '12', _source: { type: 'foo' } },
@ -134,7 +134,7 @@ describe('checkForUnknownDocs', () => {
it('uses `unknown` as the type when the document does not contain a type field', async () => {
const client = elasticsearchClientMock.createInternalClient(
elasticsearchClientMock.createSuccessTransportRequestPromise({
Promise.resolve({
hits: {
hits: [{ _id: '12', _source: {} }],
},

View file

@ -56,8 +56,8 @@ export const checkForUnknownDocs =
query,
},
})
.then((response) => {
const { hits } = response.body.hits;
.then((body) => {
const { hits } = body.hits;
if (hits.length) {
return Either.left({
type: 'unknown_docs_found' as const,

View file

@ -83,7 +83,7 @@ export const cloneIndex = ({
},
{ maxRetries: 0 /** handle retry ourselves for now */ }
)
.then((res) => {
.then((response) => {
/**
* - acknowledged=false, we timed out before the cluster state was
* updated with the newly created index, but it probably will be
@ -93,8 +93,8 @@ export const cloneIndex = ({
* - acknowledged=true, shards_acknowledged=true, cloning complete
*/
return Either.right({
acknowledged: res.body.acknowledged,
shardsAcknowledged: res.body.shards_acknowledged,
acknowledged: response.acknowledged,
shardsAcknowledged: response.shards_acknowledged,
});
})
.catch((error: EsErrors.ResponseError) => {

View file

@ -31,7 +31,7 @@ export const closePit =
body: { id: pitId },
})
.then((response) => {
if (!response.body.succeeded) {
if (!response.succeeded) {
throw new Error(`Failed to close PointInTime with id: ${pitId}`);
}
return Either.right({});

View file

@ -102,8 +102,8 @@ export const createIndex = ({
* - acknowledged=true, shards_acknowledged=true, index creation complete
*/
return Either.right({
acknowledged: Boolean(res.body.acknowledged),
shardsAcknowledged: res.body.shards_acknowledged,
acknowledged: Boolean(res.acknowledged),
shardsAcknowledged: res.shards_acknowledged,
});
})
.catch((error) => {

View file

@ -43,7 +43,7 @@ export const fetchIndices =
},
{ ignore: [404], maxRetries: 0 }
)
.then(({ body }) => {
.then((body) => {
return Either.right(body);
})
.catch(catchRetryableEsClientErrors);

View file

@ -63,7 +63,7 @@ describe('migration actions', () => {
beforeAll(async () => {
esServer = await startES();
client = esServer.es.getKibanaEsClient();
client = esServer.es.getClient();
// Create test fixture data:
await createIndex({
@ -277,7 +277,7 @@ describe('migration actions', () => {
})();
const redStatusResponse = await client.cluster.health({ index: 'red_then_yellow_index' });
expect(redStatusResponse.body.status).toBe('red');
expect(redStatusResponse.status).toBe('red');
client.indices.putSettings({
index: 'red_then_yellow_index',
@ -291,7 +291,7 @@ describe('migration actions', () => {
// Assert that the promise didn't resolve before the index became yellow
const yellowStatusResponse = await client.cluster.health({ index: 'red_then_yellow_index' });
expect(yellowStatusResponse.body.status).toBe('yellow');
expect(yellowStatusResponse.status).toBe('yellow');
});
});
@ -924,7 +924,7 @@ describe('migration actions', () => {
},
});
await expect(searchResponse.body.hits.hits.length).toBeGreaterThan(0);
await expect(searchResponse.hits.hits.length).toBeGreaterThan(0);
});
it('rejects if index does not exist', async () => {
const openPitTask = openPit({ client, index: 'no_such_index' });

View file

@ -65,7 +65,7 @@ describe('Elasticsearch Errors', () => {
);
// @ts-expect-error @elastic/elasticsearch doesn't declare error on IndexResponse
expect(isWriteBlockException(res.body.error!)).toEqual(true);
expect(isWriteBlockException(res.error!)).toEqual(true);
});
it('correctly identify errors from create operations', async () => {
@ -81,7 +81,7 @@ describe('Elasticsearch Errors', () => {
);
// @ts-expect-error @elastic/elasticsearch doesn't declare error on IndexResponse
expect(isWriteBlockException(res.body.error!)).toEqual(true);
expect(isWriteBlockException(res.error!)).toEqual(true);
});
it('correctly identify errors from bulk index operations', async () => {
@ -100,7 +100,7 @@ describe('Elasticsearch Errors', () => {
],
});
const cause = res.body.items[0].index!.error! as estypes.ErrorCause;
const cause = res.items[0].index!.error! as estypes.ErrorCause;
expect(isWriteBlockException(cause)).toEqual(true);
});
@ -122,7 +122,7 @@ describe('Elasticsearch Errors', () => {
],
});
const cause = res.body.items[0].create!.error! as estypes.ErrorCause;
const cause = res.items[0].create!.error! as estypes.ErrorCause;
expect(isWriteBlockException(cause)).toEqual(true);
});

View file

@ -40,6 +40,6 @@ export const openPit =
index,
keep_alive: pitKeepAlive,
})
.then((response) => Either.right({ pitId: response.body.id }))
.then((response) => Either.right({ pitId: response.id }))
.catch(catchRetryableEsClientErrors);
};

View file

@ -14,6 +14,7 @@ import {
RetryableEsClientError,
} from './catch_retryable_es_client_errors';
import { BATCH_SIZE } from './constants';
export interface UpdateByQueryResponse {
taskId: string;
}
@ -52,7 +53,7 @@ export const pickupUpdatedMappings =
// Create a task and return task id instead of blocking until complete
wait_for_completion: false,
})
.then(({ body: { task: taskId } }) => {
.then(({ task: taskId }) => {
return Either.right({ taskId: String(taskId!) });
})
.catch(catchRetryableEsClientErrors);

View file

@ -68,12 +68,12 @@ export const readWithPit =
query,
},
})
.then((response) => {
.then((body) => {
const totalHits =
typeof response.body.hits.total === 'number'
? response.body.hits.total // This format is to be removed in 8.0
: response.body.hits.total?.value;
const hits = response.body.hits.hits;
typeof body.hits.total === 'number'
? body.hits.total // This format is to be removed in 8.0
: body.hits.total?.value;
const hits = body.hits.hits;
if (hits.length > 0) {
return Either.right({

View file

@ -21,6 +21,7 @@ import { BATCH_SIZE } from './constants';
export interface ReindexResponse {
taskId: string;
}
/** @internal */
export interface ReindexParams {
client: ElasticsearchClient;
@ -34,6 +35,7 @@ export interface ReindexParams {
*/
unusedTypesQuery: estypes.QueryDslQueryContainer;
}
/**
* Reindex documents from the `sourceIndex` into the `targetIndex`. Returns a
* task ID which can be tracked for progress.
@ -85,7 +87,7 @@ export const reindex =
// Create a task and return task id instead of blocking until complete
wait_for_completion: false,
})
.then(({ body: { task: taskId } }) => {
.then(({ task: taskId }) => {
return Either.right({ taskId: String(taskId) });
})
.catch(catchRetryableEsClientErrors);

View file

@ -19,6 +19,7 @@ export interface RemoveWriteBlockParams {
client: ElasticsearchClient;
index: string;
}
/**
* Removes a write block from an index
*/
@ -32,10 +33,7 @@ export const removeWriteBlock =
> =>
() => {
return client.indices
.putSettings<{
acknowledged: boolean;
shards_acknowledged: boolean;
}>(
.putSettings(
{
index,
// Don't change any existing settings
@ -49,7 +47,7 @@ export const removeWriteBlock =
{ maxRetries: 0 /** handle retry ourselves for now */ }
)
.then((res) => {
return res.body.acknowledged === true
return res.acknowledged === true
? Either.right('remove_write_block_succeeded' as const)
: Either.left({
type: 'retryable_es_client_error' as const,

View file

@ -73,7 +73,7 @@ export const searchForOutdatedDocuments =
],
})
.then((res) =>
Either.right({ outdatedDocuments: (res.body.hits?.hits as SavedObjectsRawDoc[]) ?? [] })
Either.right({ outdatedDocuments: (res.hits?.hits as SavedObjectsRawDoc[]) ?? [] })
)
.catch(catchRetryableEsClientErrors);
};

View file

@ -21,6 +21,7 @@ export interface SetWriteBlockParams {
client: ElasticsearchClient;
index: string;
}
/**
* Sets a write block in place for the given index. If the response includes
* `acknowledged: true` all in-progress writes have drained and no further
@ -41,10 +42,7 @@ export const setWriteBlock =
() => {
return (
client.indices
.addBlock<{
acknowledged: boolean;
shards_acknowledged: boolean;
}>(
.addBlock(
{
index,
block: 'write',
@ -52,8 +50,8 @@ export const setWriteBlock =
{ maxRetries: 0 /** handle retry ourselves for now */ }
)
// not typed yet
.then((res: any) => {
return res.body.acknowledged === true
.then((res) => {
return res.acknowledged === true
? Either.right('set_write_block_succeeded' as const)
: Either.left({
type: 'retryable_es_client_error' as const,

View file

@ -51,7 +51,7 @@ export const updateAndPickupMappings = ({
timeout: DEFAULT_TIMEOUT,
body: mappings,
})
.then((res) => {
.then(() => {
// Ignore `acknowledged: false`. When the coordinating node accepts
// the new cluster state update but not all nodes have applied the
// update within the timeout `acknowledged` will be false. However,

View file

@ -33,13 +33,13 @@ export const verifyReindex =
() => {
const count = (index: string) =>
client
.count<{ count: number }>({
.count({
index,
// Return an error when targeting missing or closed indices
allow_no_indices: false,
})
.then((res) => {
return res.body.count;
return res.count;
});
return Promise.all([count(sourceIndex), count(targetIndex)])

View file

@ -51,7 +51,7 @@ export const waitForIndexStatusYellow =
{ ignore: [408] }
)
.then((res) => {
if (res.body.timed_out === true) {
if (res.timed_out === true) {
return Either.left({
type: 'retryable_es_client_error' as const,
message: `Timeout waiting for the status of the [${index}] index to become 'yellow'`,

View file

@ -82,8 +82,7 @@ export const waitForTask =
wait_for_completion: true,
timeout,
})
.then((res) => {
const body = res.body;
.then((body) => {
const failures = body.response?.failures ?? [];
return Either.right({
completed: body.completed,

View file

@ -122,6 +122,6 @@ describe('migration from 7.7.2-xpack with 100k objects', () => {
// Use a >= comparison since once Kibana has started it might create new
// documents like telemetry tasks
expect(migratedIndexResponse.body.count).toBeGreaterThanOrEqual(oldIndexResponse.body.count);
expect(migratedIndexResponse.count).toBeGreaterThanOrEqual(oldIndexResponse.count);
});
});

View file

@ -55,7 +55,7 @@ describe('migration from 7.13 to 7.14+ with many failed action_tasks', () => {
kibanaIndexName = '.kibana',
taskManagerIndexName = '.kibana_task_manager'
): Promise<{ tasksCount: number; actionTaskParamsCount: number }> => {
const esClient: ElasticsearchClient = esServer.es.getKibanaEsClient();
const esClient: ElasticsearchClient = esServer.es.getClient();
const actionTaskParamsResponse = await esClient.count({
index: kibanaIndexName,
@ -75,8 +75,8 @@ describe('migration from 7.13 to 7.14+ with many failed action_tasks', () => {
});
return {
actionTaskParamsCount: actionTaskParamsResponse.body.count,
tasksCount: tasksResponse.body.count,
actionTaskParamsCount: actionTaskParamsResponse.count,
tasksCount: tasksResponse.count,
};
};

View file

@ -31,7 +31,7 @@ function sortByTypeAndId(a: { type: string; id: string }, b: { type: string; id:
}
async function fetchDocuments(esClient: ElasticsearchClient, index: string) {
const { body } = await esClient.search<any>({
const body = await esClient.search<any>({
index,
body: {
query: {
@ -95,7 +95,7 @@ describe('migration v2', () => {
// wait a bit for the count to settle.
await new Promise((resolve) => setTimeout(resolve, 5000));
const esClient: ElasticsearchClient = esServer.es.getKibanaEsClient();
const esClient: ElasticsearchClient = esServer.es.getClient();
// assert that the docs from the original index have been migrated rather than comparing a doc count after startup
const originalDocs = await fetchDocuments(esClient, '.kibana_7.14.0_001');

View file

@ -133,7 +133,7 @@ describe('migration v2', () => {
const pitId = logRecordWithPit.right.pitId;
expect(pitId).toBeTruthy();
const client = esServer.es.getKibanaEsClient();
const client = esServer.es.getClient();
await expect(
client.search({
body: {

View file

@ -35,7 +35,7 @@ function sortByTypeAndId(a: { type: string; id: string }, b: { type: string; id:
}
async function fetchDocuments(esClient: ElasticsearchClient, index: string) {
const { body } = await esClient.search<any>({
const body = await esClient.search<any>({
index,
body: {
query: {
@ -175,7 +175,7 @@ describe('migrating from 7.3.0-xpack which used v1 migrations', () => {
});
it('creates the new index and the correct aliases', async () => {
const { body } = await esClient.indices.get(
const body = await esClient.indices.get(
{
index: migratedIndex,
},
@ -203,7 +203,7 @@ describe('migrating from 7.3.0-xpack which used v1 migrations', () => {
},
size: 10000,
});
const allDocuments = res.body.hits.hits as SavedObjectsRawDoc[];
const allDocuments = res.hits.hits as SavedObjectsRawDoc[];
allDocuments.forEach((doc) => {
assertMigrationVersion(doc, expectedVersions);
});

View file

@ -35,7 +35,7 @@ function sortByTypeAndId(a: { type: string; id: string }, b: { type: string; id:
}
async function fetchDocuments(esClient: ElasticsearchClient, index: string) {
const { body } = await esClient.search<any>({
const body = await esClient.search<any>({
index,
body: {
query: {
@ -179,7 +179,7 @@ describe('migrating from the same Kibana version that used v1 migrations', () =>
});
it('creates the new index and the correct aliases', async () => {
const { body } = await esClient.indices.get(
const body = await esClient.indices.get(
{
index: migratedIndex,
},
@ -206,7 +206,7 @@ describe('migrating from the same Kibana version that used v1 migrations', () =>
},
size: 10000,
});
const allDocuments = res.body.hits.hits as SavedObjectsRawDoc[];
const allDocuments = res.hits.hits as SavedObjectsRawDoc[];
allDocuments.forEach((doc) => {
assertMigrationVersion(doc, expectedVersions);
});

View file

@ -29,7 +29,7 @@ function extractSortNumberFromId(id: string): number {
}
async function fetchDocs(esClient: ElasticsearchClient, index: string, type: string) {
const { body } = await esClient.search<any>({
const body = await esClient.search<any>({
index,
size: 10000,
body: {
@ -180,7 +180,7 @@ describe('migration v2', () => {
});
await root.start();
const esClient = esServer.es.getKibanaEsClient();
const esClient = esServer.es.getClient();
const migratedFooDocs = await fetchDocs(esClient, migratedIndex, 'foo');
expect(migratedFooDocs.length).toBe(2500);

View file

@ -30,7 +30,7 @@ function extractSortNumberFromId(id: string): number {
}
async function fetchDocs(esClient: ElasticsearchClient, index: string) {
const { body } = await esClient.search<any>({
const body = await esClient.search<any>({
index,
size: 10000,
body: {
@ -183,7 +183,7 @@ describe('migration v2', () => {
await startWithDelay([rootA, rootB, rootC], 0);
const esClient = esServer.es.getKibanaEsClient();
const esClient = esServer.es.getClient();
const migratedDocs = await fetchDocs(esClient, migratedIndex);
expect(migratedDocs.length).toBe(5000);
@ -202,7 +202,7 @@ describe('migration v2', () => {
await startWithDelay([rootA, rootB, rootC], 1);
const esClient = esServer.es.getKibanaEsClient();
const esClient = esServer.es.getClient();
const migratedDocs = await fetchDocs(esClient, migratedIndex);
expect(migratedDocs.length).toBe(5000);
@ -221,7 +221,7 @@ describe('migration v2', () => {
await startWithDelay([rootA, rootB, rootC], 5);
const esClient = esServer.es.getKibanaEsClient();
const esClient = esServer.es.getClient();
const migratedDocs = await fetchDocs(esClient, migratedIndex);
expect(migratedDocs.length).toBe(5000);
@ -240,7 +240,7 @@ describe('migration v2', () => {
await startWithDelay([rootA, rootB, rootC], 20);
const esClient = esServer.es.getKibanaEsClient();
const esClient = esServer.es.getClient();
const migratedDocs = await fetchDocs(esClient, migratedIndex);
expect(migratedDocs.length).toBe(5000);

View file

@ -122,7 +122,7 @@ function createRoot() {
}
async function fetchDocs(esClient: ElasticsearchClient, index: string) {
const { body } = await esClient.search<any>({
const body = await esClient.search<any>({
index,
body: {
query: {

View file

@ -28,7 +28,7 @@ function sortByTypeAndId(a: { type: string; id: string }, b: { type: string; id:
}
async function fetchDocs(esClient: ElasticsearchClient, index: string) {
const { body } = await esClient.search<any>({
const body = await esClient.search<any>({
index,
body: {
query: {

View file

@ -97,15 +97,9 @@ describe('KibanaMigrator', () => {
it('throws if prepareMigrations is not called first', async () => {
const options = mockOptions();
options.client.cat.templates.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise([], { statusCode: 404 })
);
options.client.indices.get.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise({}, { statusCode: 404 })
);
options.client.indices.getAlias.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise({}, { statusCode: 404 })
);
options.client.cat.templates.mockResponse([], { statusCode: 404 });
options.client.indices.get.mockResponse({}, { statusCode: 404 });
options.client.indices.getAlias.mockResponse({}, { statusCode: 404 });
const migrator = new KibanaMigrator(options);
@ -117,12 +111,8 @@ describe('KibanaMigrator', () => {
it('only runs migrations once if called multiple times', async () => {
const options = mockOptions();
options.client.indices.get.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise({}, { statusCode: 404 })
);
options.client.indices.getAlias.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise({}, { statusCode: 404 })
);
options.client.indices.get.mockResponse({}, { statusCode: 404 });
options.client.indices.getAlias.mockResponse({}, { statusCode: 404 });
const migrator = new KibanaMigrator(options);
@ -158,20 +148,18 @@ describe('KibanaMigrator', () => {
});
it('rejects when the migration state machine terminates in a FATAL state', () => {
const options = mockV2MigrationOptions();
options.client.indices.get.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise(
{
'.my-index_8.2.4_001': {
aliases: {
'.my-index': {},
'.my-index_8.2.4': {},
},
mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } },
settings: {},
options.client.indices.get.mockResponse(
{
'.my-index_8.2.4_001': {
aliases: {
'.my-index': {},
'.my-index_8.2.4': {},
},
mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } },
settings: {},
},
{ statusCode: 200 }
)
},
{ statusCode: 200 }
);
const migrator = new KibanaMigrator(options);
@ -183,14 +171,11 @@ describe('KibanaMigrator', () => {
it('rejects when an unexpected exception occurs in an action', async () => {
const options = mockV2MigrationOptions();
options.client.tasks.get.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise({
completed: true,
error: { type: 'elasticsearch_exception', reason: 'task failed with an error' },
failures: [],
task: { description: 'task description' } as any,
})
);
options.client.tasks.get.mockResponse({
completed: true,
error: { type: 'elasticsearch_exception', reason: 'task failed with an error' },
task: { description: 'task description' } as any,
});
const migrator = new KibanaMigrator(options);
migrator.prepareMigrations();
@ -213,56 +198,38 @@ type MockedOptions = KibanaMigratorOptions & {
const mockV2MigrationOptions = () => {
const options = mockOptions();
options.client.indices.get.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise(
{
'.my-index': {
aliases: { '.kibana': {} },
mappings: { properties: {} },
settings: {},
},
options.client.indices.get.mockResponse(
{
'.my-index': {
aliases: { '.kibana': {} },
mappings: { properties: {} },
settings: {},
},
{ statusCode: 200 }
)
);
options.client.indices.addBlock.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise({
acknowledged: true,
shards_acknowledged: true,
indices: [],
})
);
options.client.reindex.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise({
taskId: 'reindex_task_id',
} as estypes.ReindexResponse)
);
options.client.tasks.get.mockReturnValue(
elasticsearchClientMock.createSuccessTransportRequestPromise({
completed: true,
error: undefined,
failures: [],
task: { description: 'task description' } as any,
} as estypes.TasksGetResponse)
},
{ statusCode: 200 }
);
options.client.indices.addBlock.mockResponse({
acknowledged: true,
shards_acknowledged: true,
indices: [],
});
options.client.reindex.mockResponse({
taskId: 'reindex_task_id',
} as estypes.ReindexResponse);
options.client.tasks.get.mockResponse({
completed: true,
error: undefined,
failures: [],
task: { description: 'task description' } as any,
} as estypes.TasksGetResponse);
options.client.search = jest
.fn()
.mockImplementation(() =>
elasticsearchClientMock.createSuccessTransportRequestPromise({ hits: { hits: [] } })
);
options.client.search.mockResponse({ hits: { hits: [] } } as any);
options.client.openPointInTime = jest
.fn()
.mockImplementation(() =>
elasticsearchClientMock.createSuccessTransportRequestPromise({ id: 'pit_id' })
);
options.client.openPointInTime.mockResponse({ id: 'pit_id' });
options.client.closePointInTime = jest
.fn()
.mockImplementation(() =>
elasticsearchClientMock.createSuccessTransportRequestPromise({ succeeded: true })
);
options.client.closePointInTime.mockResponse({
succeeded: true,
} as estypes.ClosePointInTimeResponse);
return options;
};

View file

@ -11,9 +11,6 @@ import {
mockRawDocExistsInNamespace,
} from './collect_multi_namespace_references.test.mock';
import type { DeeplyMockedKeys } from '@kbn/utility-types/jest';
import type { ElasticsearchClient } from '../../../elasticsearch';
import { elasticsearchClientMock } from '../../../elasticsearch/client/mocks';
import { typeRegistryMock } from '../../saved_objects_type_registry.mock';
import { SavedObjectsSerializer } from '../../serialization';
@ -43,7 +40,7 @@ beforeEach(() => {
});
describe('collectMultiNamespaceReferences', () => {
let client: DeeplyMockedKeys<ElasticsearchClient>;
let client: ReturnType<typeof elasticsearchClientMock.createElasticsearchClient>;
/** Sets up the type registry, saved objects client, etc. and return the full parameters object to be passed to `collectMultiNamespaceReferences` */
function setup(
@ -88,30 +85,28 @@ describe('collectMultiNamespaceReferences', () => {
references?: Array<{ type: string; id: string }>;
}>
) {
client.mget.mockReturnValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise({
docs: results.map((x) => {
const references =
x.references?.map(({ type, id }) => ({ type, id, name: 'ref-name' })) ?? [];
return x.found
? {
_id: 'doesnt-matter',
_index: 'doesnt-matter',
_source: {
namespaces: SPACES,
references,
},
...VERSION_PROPS,
found: true,
}
: {
_id: 'doesnt-matter',
_index: 'doesnt-matter',
found: false,
};
}),
})
);
client.mget.mockResponseOnce({
docs: results.map((x) => {
const references =
x.references?.map(({ type, id }) => ({ type, id, name: 'ref-name' })) ?? [];
return x.found
? {
_id: 'doesnt-matter',
_index: 'doesnt-matter',
_source: {
namespaces: SPACES,
references,
},
...VERSION_PROPS,
found: true,
}
: {
_id: 'doesnt-matter',
_index: 'doesnt-matter',
found: false,
};
}),
});
}
/** Asserts that mget is called for the given objects */

View file

@ -207,7 +207,7 @@ async function getObjectsAndReferences({
}
const bulkGetResponse = await client.mget(
{ body: { docs: makeBulkGetDocs(bulkGetObjects) } },
{ ignore: [404] }
{ ignore: [404], meta: true }
);
// exit early if we can't verify a 404 response is from Elasticsearch
if (

View file

@ -12,10 +12,7 @@ import {
mockIsNotFoundFromUnsupportedServer,
} from './internal_bulk_resolve.test.mock';
import type { DeeplyMockedKeys } from '@kbn/utility-types/jest';
import type { ElasticsearchClient } from 'src/core/server/elasticsearch';
import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks';
import { LEGACY_URL_ALIAS_TYPE } from '../../object_types';
import { typeRegistryMock } from '../../saved_objects_type_registry.mock';
import { SavedObjectsSerializer } from '../../serialization';
@ -40,7 +37,7 @@ beforeEach(() => {
});
describe('internalBulkResolve', () => {
let client: DeeplyMockedKeys<ElasticsearchClient>;
let client: ReturnType<typeof elasticsearchClientMock.createElasticsearchClient>;
let serializer: SavedObjectsSerializer;
let incrementCounterInternal: jest.Mock<any, any>;
@ -69,52 +66,48 @@ describe('internalBulkResolve', () => {
function mockBulkResults(
...results: Array<{ found: boolean; targetId?: string; disabled?: boolean }>
) {
client.bulk.mockReturnValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise({
items: results.map(({ found, targetId, disabled }) => ({
update: {
_index: 'doesnt-matter',
status: 0,
get: {
found,
_source: {
...((targetId || disabled) && {
[LEGACY_URL_ALIAS_TYPE]: { targetId, disabled },
}),
},
...VERSION_PROPS,
client.bulk.mockResponseOnce({
items: results.map(({ found, targetId, disabled }) => ({
update: {
_index: 'doesnt-matter',
status: 0,
get: {
found,
_source: {
...((targetId || disabled) && {
[LEGACY_URL_ALIAS_TYPE]: { targetId, disabled },
}),
},
...VERSION_PROPS,
},
})),
errors: false,
took: 0,
})
);
},
})),
errors: false,
took: 0,
});
}
/** Mocks the elasticsearch client so it returns the expected results for an mget operation*/
function mockMgetResults(...results: Array<{ found: boolean }>) {
client.mget.mockReturnValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise({
docs: results.map((x) => {
return x.found
? {
_id: 'doesnt-matter',
_index: 'doesnt-matter',
_source: {
foo: 'bar',
},
...VERSION_PROPS,
found: true,
}
: {
_id: 'doesnt-matter',
_index: 'doesnt-matter',
found: false,
};
}),
})
);
client.mget.mockResponseOnce({
docs: results.map((x) => {
return x.found
? {
_id: 'doesnt-matter',
_index: 'doesnt-matter',
_source: {
foo: 'bar',
},
...VERSION_PROPS,
found: true,
}
: {
_id: 'doesnt-matter',
_index: 'doesnt-matter',
found: false,
};
}),
});
}
/** Asserts that bulk is called for the given aliases */
@ -158,16 +151,20 @@ describe('internalBulkResolve', () => {
const error = SavedObjectsErrorHelpers.createUnsupportedTypeError(UNSUPPORTED_TYPE);
return { type: UNSUPPORTED_TYPE, id, error };
}
function expectNotFoundError(id: string) {
const error = SavedObjectsErrorHelpers.createGenericNotFoundError(OBJ_TYPE, id);
return { type: OBJ_TYPE, id, error };
}
function expectExactMatchResult(id: string) {
return { saved_object: `mock-obj-for-${id}`, outcome: 'exactMatch' };
}
function expectAliasMatchResult(id: string) {
return { saved_object: `mock-obj-for-${id}`, outcome: 'aliasMatch', alias_target_id: id };
}
// eslint-disable-next-line @typescript-eslint/naming-convention
function expectConflictResult(id: string, alias_target_id: string) {
return { saved_object: `mock-obj-for-${id}`, outcome: 'conflict', alias_target_id };

View file

@ -138,7 +138,7 @@ export async function internalBulkResolve<T>(
const bulkGetResponse = docsToBulkGet.length
? await client.mget<SavedObjectsRawDocSource>(
{ body: { docs: docsToBulkGet } },
{ ignore: [404] }
{ ignore: [404], meta: true }
)
: undefined;
// exit early if a 404 isn't from elasticsearch
@ -293,7 +293,7 @@ async function fetchAndUpdateAliases(
require_alias: true,
body: bulkUpdateDocs,
});
return bulkUpdateResponse.body.items.map((item) => {
return bulkUpdateResponse.items.map((item) => {
// Map the bulk update response to the `_source` fields that were returned for each document
return item.update?.get;
});

View file

@ -54,9 +54,7 @@ describe('deleteLegacyUrlAliases', () => {
body: { error: { type: 'es_type', reason: 'es_reason' } },
})
);
params.client.updateByQuery.mockResolvedValueOnce(
elasticsearchClientMock.createErrorTransportRequestPromise(esError)
);
params.client.updateByQuery.mockResolvedValueOnce(Promise.reject(esError));
mockGetEsErrorMessage.mockClear();
mockGetEsErrorMessage.mockReturnValue('Oh no!');

View file

@ -273,7 +273,7 @@ async function bulkGetObjectsAndAliases(
const bulkGetResponse = docsToBulkGet.length
? await client.mget<SavedObjectsRawDocSource>(
{ body: { docs: docsToBulkGet } },
{ ignore: [404] }
{ ignore: [404], meta: true }
)
: undefined;

View file

@ -473,9 +473,7 @@ describe('SavedObjectsRepository', () => {
options?: SavedObjectsCreateOptions
) => {
const response = getMockBulkCreateResponse(objects, options?.namespace);
client.bulk.mockResolvedValue(
elasticsearchClientMock.createSuccessTransportRequestPromise(response)
);
client.bulk.mockResponse(response);
return await savedObjectsRepository.bulkCreate(objects, options);
};
@ -838,9 +836,7 @@ describe('SavedObjectsRepository', () => {
} else {
response = getMockBulkCreateResponse([obj1, obj2]);
}
client.bulk.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(response)
);
client.bulk.mockResponseOnce(response);
const objects = [obj1, obj, obj2];
const result = await savedObjectsRepository.bulkCreate(objects);
@ -941,9 +937,7 @@ describe('SavedObjectsRepository', () => {
},
]);
const bulkResponse = getMockBulkCreateResponse([o1, o5]);
client.bulk.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(bulkResponse)
);
client.bulk.mockResponseOnce(bulkResponse);
const options = { overwrite: true };
const result = await savedObjectsRepository.bulkCreate(objects, options);
@ -984,9 +978,7 @@ describe('SavedObjectsRepository', () => {
it(`returns errors for any bulk objects with invalid schemas`, async () => {
const response = getMockBulkCreateResponse([obj3]);
client.bulk.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(response)
);
client.bulk.mockResponseOnce(response);
const result = await savedObjectsRepository.bulkCreate([
obj3,
@ -1089,9 +1081,7 @@ describe('SavedObjectsRepository', () => {
};
const objects = [obj1, obj, obj2];
const response = getMockBulkCreateResponse([obj1, obj2]);
client.bulk.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(response)
);
client.bulk.mockResponseOnce(response);
const result = await savedObjectsRepository.bulkCreate(objects);
expect(client.bulk).toHaveBeenCalledTimes(1);
expect(result).toEqual({
@ -1107,9 +1097,7 @@ describe('SavedObjectsRepository', () => {
// of the document when it actually does not, forcing to cast to any as BulkResponse
// does not contains _source
const response = getMockBulkCreateResponse([obj1, obj2], namespace) as any;
client.bulk.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(response)
);
client.bulk.mockResponseOnce(response);
// Bulk create one object with id unspecified, and one with id specified
const result = await savedObjectsRepository.bulkCreate([{ ...obj1, id: undefined }, obj2], {
@ -1182,9 +1170,7 @@ describe('SavedObjectsRepository', () => {
);
const bulkGetSuccess = async (objects: SavedObject[], options?: SavedObjectsBaseOptions) => {
const response = getMockMgetResponse(objects, options?.namespace);
client.mget.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(response)
);
client.mget.mockResponseOnce(response);
const result = await bulkGet(objects, options);
expect(client.mget).toHaveBeenCalledTimes(1);
return result;
@ -1551,14 +1537,10 @@ describe('SavedObjectsRepository', () => {
const multiNamespaceObjects = objects.filter(({ type }) => registry.isMultiNamespace(type));
if (multiNamespaceObjects?.length) {
const response = getMockMgetResponse(multiNamespaceObjects, options?.namespace);
client.mget.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(response)
);
client.mget.mockResponseOnce(response);
}
const response = getMockBulkUpdateResponse(objects, options, includeOriginId);
client.bulk.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(response)
);
client.bulk.mockResponseOnce(response);
const result = await savedObjectsRepository.bulkUpdate(objects, options);
expect(client.mget).toHaveBeenCalledTimes(multiNamespaceObjects?.length ? 1 : 0);
return result;
@ -1825,9 +1807,7 @@ describe('SavedObjectsRepository', () => {
mockGetBulkOperationError.mockReturnValueOnce(undefined);
mockGetBulkOperationError.mockReturnValueOnce(expectedErrorResult.error as Payload);
}
client.bulk.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(mockResponse)
);
client.bulk.mockResponseOnce(mockResponse);
const result = await savedObjectsRepository.bulkUpdate(objects);
expect(client.bulk).toHaveBeenCalled();
@ -1848,16 +1828,10 @@ describe('SavedObjectsRepository', () => {
mgetResponse: estypes.MgetResponse,
mgetOptions?: { statusCode?: number }
) => {
client.mget.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(mgetResponse, {
statusCode: mgetOptions?.statusCode,
})
);
client.mget.mockResponseOnce(mgetResponse, { statusCode: mgetOptions?.statusCode });
const bulkResponse = getMockBulkUpdateResponse([obj1, obj2], { namespace });
client.bulk.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(bulkResponse)
);
client.bulk.mockResponseOnce(bulkResponse);
const result = await savedObjectsRepository.bulkUpdate([obj1, _obj, obj2], options);
expect(client.bulk).toHaveBeenCalled();
@ -1966,9 +1940,7 @@ describe('SavedObjectsRepository', () => {
};
const objects = [obj1, obj, obj2];
const mockResponse = getMockBulkUpdateResponse(objects);
client.bulk.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(mockResponse)
);
client.bulk.mockResponseOnce(mockResponse);
const result = await savedObjectsRepository.bulkUpdate(objects);
expect(client.bulk).toHaveBeenCalledTimes(1);
@ -2150,12 +2122,14 @@ describe('SavedObjectsRepository', () => {
mockPreflightCheckForCreate.mockImplementation(({ objects }) => {
return Promise.resolve(objects.map(({ type, id }) => ({ type, id }))); // respond with no errors by default
});
client.create.mockImplementation((params) =>
elasticsearchClientMock.createSuccessTransportRequestPromise({
_id: params.id,
...mockVersionProps,
} as estypes.CreateResponse)
);
client.create.mockResponseImplementation((params) => {
return {
body: {
_id: params.id,
...mockVersionProps,
} as estypes.CreateResponse,
};
});
});
const type = 'index-pattern';
@ -2721,15 +2695,11 @@ describe('SavedObjectsRepository', () => {
if (registry.isMultiNamespace(type)) {
const mockGetResponse =
mockGetResponseValue ?? getMockGetResponse({ type, id }, options?.namespace);
client.get.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(mockGetResponse)
);
client.get.mockResponseOnce(mockGetResponse);
}
client.delete.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise({
result: 'deleted',
} as estypes.DeleteResponse)
);
client.delete.mockResponseOnce({
result: 'deleted',
} as estypes.DeleteResponse);
const result = await savedObjectsRepository.delete(type, id, options);
expect(client.get).toHaveBeenCalledTimes(registry.isMultiNamespace(type) ? 1 : 0);
return result;
@ -3023,9 +2993,7 @@ describe('SavedObjectsRepository', () => {
namespace: string,
options?: SavedObjectsDeleteByNamespaceOptions
) => {
client.updateByQuery.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(mockUpdateResults)
);
client.updateByQuery.mockResponseOnce(mockUpdateResults);
const result = await savedObjectsRepository.deleteByNamespace(namespace, options);
expect(mockGetSearchDsl).toHaveBeenCalledTimes(1);
expect(client.updateByQuery).toHaveBeenCalledTimes(1);
@ -3097,11 +3065,9 @@ describe('SavedObjectsRepository', () => {
const updatedCount = 42;
const removeReferencesToSuccess = async (options = defaultOptions) => {
client.updateByQuery.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise({
updated: updatedCount,
})
);
client.updateByQuery.mockResponseOnce({
updated: updatedCount,
});
return await savedObjectsRepository.removeReferencesTo(type, id, options);
};
@ -3226,15 +3192,13 @@ describe('SavedObjectsRepository', () => {
describe('errors', () => {
it(`throws when ES returns failures`, async () => {
client.updateByQuery.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise({
updated: 7,
failures: [
{ id: 'failure' } as estypes.BulkIndexByScrollFailure,
{ id: 'another-failure' } as estypes.BulkIndexByScrollFailure,
],
})
);
client.updateByQuery.mockResponseOnce({
updated: 7,
failures: [
{ id: 'failure' } as estypes.BulkIndexByScrollFailure,
{ id: 'another-failure' } as estypes.BulkIndexByScrollFailure,
],
});
await expect(
savedObjectsRepository.removeReferencesTo(type, id, defaultOptions)
@ -3322,11 +3286,7 @@ describe('SavedObjectsRepository', () => {
const namespace = 'foo-namespace';
const findSuccess = async (options: SavedObjectsFindOptions, namespace?: string) => {
client.search.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(
generateSearchResults(namespace)
)
);
client.search.mockResponseOnce(generateSearchResults(namespace));
const result = await savedObjectsRepository.find(options);
expect(mockGetSearchDsl).toHaveBeenCalledTimes(1);
expect(client.search).toHaveBeenCalledTimes(1);
@ -3818,9 +3778,7 @@ describe('SavedObjectsRepository', () => {
},
options?.namespace
);
client.get.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(response)
);
client.get.mockResponseOnce(response);
const result = await savedObjectsRepository.get(type, id, options);
expect(client.get).toHaveBeenCalledTimes(1);
return result;
@ -4034,31 +3992,32 @@ describe('SavedObjectsRepository', () => {
if (isMultiNamespace) {
const response =
mockGetResponseValue ?? getMockGetResponse({ type, id }, options?.namespace);
client.get.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(response)
);
client.get.mockResponseOnce(response);
}
client.update.mockImplementation((params) =>
elasticsearchClientMock.createSuccessTransportRequestPromise({
_id: params.id,
...mockVersionProps,
_index: '.kibana',
get: {
found: true,
_source: {
type,
...mockTimestampFields,
[type]: {
...fields.reduce((acc, field) => {
acc[typeof field === 'string' ? field : field.fieldName] = 8468;
return acc;
}, {} as Record<string, number>),
defaultIndex: 'logstash-*',
client.update.mockResponseImplementation((params) => {
return {
body: {
_id: params.id,
...mockVersionProps,
_index: '.kibana',
get: {
found: true,
_source: {
type,
...mockTimestampFields,
[type]: {
...fields.reduce((acc, field) => {
acc[typeof field === 'string' ? field : field.fieldName] = 8468;
return acc;
}, {} as Record<string, number>),
defaultIndex: 'logstash-*',
},
},
},
},
} as estypes.UpdateResponse)
);
} as estypes.UpdateResponse,
};
});
const result = await savedObjectsRepository.incrementCounter(type, id, fields, options);
expect(client.get).toHaveBeenCalledTimes(isMultiNamespace ? 1 : 0);
@ -4347,26 +4306,28 @@ describe('SavedObjectsRepository', () => {
describe('returns', () => {
it(`formats the ES response`, async () => {
client.update.mockImplementation((params) =>
elasticsearchClientMock.createSuccessTransportRequestPromise({
_id: params.id,
...mockVersionProps,
_index: '.kibana',
get: {
found: true,
_source: {
type: 'config',
...mockTimestampFields,
config: {
buildNum: 8468,
apiCallsCount: 100,
defaultIndex: 'logstash-*',
client.update.mockResponseImplementation((params) => {
return {
body: {
_id: params.id,
...mockVersionProps,
_index: '.kibana',
get: {
found: true,
_source: {
type: 'config',
...mockTimestampFields,
config: {
buildNum: 8468,
apiCallsCount: 100,
defaultIndex: 'logstash-*',
},
originId,
},
originId,
},
},
} as estypes.UpdateResponse)
);
} as estypes.UpdateResponse,
};
});
const response = await savedObjectsRepository.incrementCounter(
'config',
@ -4452,26 +4413,24 @@ describe('SavedObjectsRepository', () => {
options?: SavedObjectsUpdateOptions,
includeOriginId?: boolean
) => {
client.update.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(
{
_id: `${type}:${id}`,
...mockVersionProps,
result: 'updated',
// don't need the rest of the source for test purposes, just the namespace and namespaces attributes
get: {
_source: {
namespaces: [options?.namespace ?? 'default'],
namespace: options?.namespace,
client.update.mockResponseOnce(
{
_id: `${type}:${id}`,
...mockVersionProps,
result: 'updated',
// don't need the rest of the source for test purposes, just the namespace and namespaces attributes
get: {
_source: {
namespaces: [options?.namespace ?? 'default'],
namespace: options?.namespace,
// "includeOriginId" is not an option for the operation; however, if the existing saved object contains an originId attribute, the
// operation will return it in the result. This flag is just used for test purposes to modify the mock cluster call response.
...(includeOriginId && { originId }),
},
// "includeOriginId" is not an option for the operation; however, if the existing saved object contains an originId attribute, the
// operation will return it in the result. This flag is just used for test purposes to modify the mock cluster call response.
...(includeOriginId && { originId }),
},
} as estypes.UpdateResponse,
{ statusCode: 200 }
)
},
} as estypes.UpdateResponse,
{ statusCode: 200 }
);
};
@ -4489,12 +4448,7 @@ describe('SavedObjectsRepository', () => {
if (registry.isMultiNamespace(type)) {
const mockGetResponse =
mockGetResponseValue ?? getMockGetResponse({ type, id }, options?.namespace);
client.get.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(
{ ...mockGetResponse },
{ statusCode: 200 }
)
);
client.get.mockResponseOnce(mockGetResponse, { statusCode: 200 });
}
mockUpdateResponse(type, id, options, includeOriginId);
const result = await savedObjectsRepository.update(type, id, attributes, options);
@ -4896,9 +4850,7 @@ describe('SavedObjectsRepository', () => {
const generateResults = (id?: string) => ({ id: id || 'id' });
const successResponse = async (type: string, options?: SavedObjectsOpenPointInTimeOptions) => {
client.openPointInTime.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(generateResults())
);
client.openPointInTime.mockResponseOnce(generateResults());
const result = await savedObjectsRepository.openPointInTimeForType(type, options);
expect(client.openPointInTime).toHaveBeenCalledTimes(1);
return result;
@ -4987,9 +4939,7 @@ describe('SavedObjectsRepository', () => {
describe('#closePointInTime', () => {
const generateResults = () => ({ succeeded: true, num_freed: 3 });
const successResponse = async (id: string) => {
client.closePointInTime.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(generateResults())
);
client.closePointInTime.mockResponseOnce(generateResults());
const result = await savedObjectsRepository.closePointInTime(id);
expect(client.closePointInTime).toHaveBeenCalledTimes(1);
return result;
@ -5017,9 +4967,7 @@ describe('SavedObjectsRepository', () => {
describe('returns', () => {
it(`returns response body from ES`, async () => {
const results = generateResults();
client.closePointInTime.mockResolvedValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(results)
);
client.closePointInTime.mockResponseOnce(results);
const response = await savedObjectsRepository.closePointInTime('abc123');
expect(response).toEqual(results);
});

View file

@ -392,8 +392,8 @@ export class SavedObjectsRepository {
const { body, statusCode, headers } =
id && overwrite
? await this.client.index(requestParams)
: await this.client.create(requestParams);
? await this.client.index(requestParams, { meta: true })
: await this.client.create(requestParams, { meta: true });
// throw if we can't verify a 404 response is from Elasticsearch
if (isNotFoundFromUnsupportedServer({ statusCode, headers })) {
@ -602,7 +602,7 @@ export class SavedObjectsRepository {
}
const { requestedId, rawMigratedDoc, esRequestIndex } = expectedResult.value;
const rawResponse = Object.values(bulkResponse?.body.items[esRequestIndex] ?? {})[0] as any;
const rawResponse = Object.values(bulkResponse?.items[esRequestIndex] ?? {})[0] as any;
const error = getBulkOperationError(rawMigratedDoc._source.type, requestedId, rawResponse);
if (error) {
@ -672,7 +672,7 @@ export class SavedObjectsRepository {
docs: bulkGetDocs,
},
},
{ ignore: [404] }
{ ignore: [404], meta: true }
)
: undefined;
// throw if we can't verify a 404 response is from Elasticsearch
@ -764,7 +764,7 @@ export class SavedObjectsRepository {
...getExpectedVersionProperties(undefined, preflightResult?.rawDocSource),
refresh,
},
{ ignore: [404] }
{ ignore: [404], meta: true }
);
if (isNotFoundFromUnsupportedServer({ statusCode, headers })) {
@ -865,7 +865,7 @@ export class SavedObjectsRepository {
}),
},
},
{ ignore: [404] }
{ ignore: [404], meta: true }
);
// throw if we can't verify a 404 response is from Elasticsearch
if (isNotFoundFromUnsupportedServer({ statusCode, headers })) {
@ -1019,6 +1019,7 @@ export class SavedObjectsRepository {
esOptions,
{
ignore: [404],
meta: true,
}
);
if (statusCode === 404) {
@ -1128,7 +1129,7 @@ export class SavedObjectsRepository {
docs: bulkGetDocs,
},
},
{ ignore: [404] }
{ ignore: [404], meta: true }
)
: undefined;
// fail fast if we can't verify a 404 is from Elasticsearch
@ -1237,7 +1238,7 @@ export class SavedObjectsRepository {
id: this._serializer.generateRawId(namespace, type, id),
index: this.getIndexForType(type),
},
{ ignore: [404] }
{ ignore: [404], meta: true }
);
const indexNotFound = statusCode === 404;
// check if we have the elasticsearch header when index is not found and, if we do, ensure it is from Elasticsearch
@ -1368,8 +1369,8 @@ export class SavedObjectsRepository {
...(Array.isArray(references) && { references }),
};
const { body } = await this.client
.update<SavedObjectsRawDocSource>({
const body = await this.client
.update<unknown, unknown, SavedObjectsRawDocSource>({
id: this._serializer.generateRawId(namespace, type, id),
index: this.getIndexForType(type),
...getExpectedVersionProperties(version, preflightResult?.rawDocSource),
@ -1556,6 +1557,7 @@ export class SavedObjectsRepository {
},
{
ignore: [404],
meta: true,
}
)
: undefined;
@ -1655,7 +1657,7 @@ export class SavedObjectsRepository {
}
const { type, id, namespaces, documentToSave, esRequestIndex } = expectedResult.value;
const response = bulkUpdateResponse?.body.items[esRequestIndex] ?? {};
const response = bulkUpdateResponse?.items[esRequestIndex] ?? {};
const rawResponse = Object.values(response)[0] as any;
const error = getBulkOperationError(type, id, rawResponse);
@ -1734,7 +1736,7 @@ export class SavedObjectsRepository {
}),
},
},
{ ignore: [404] }
{ ignore: [404], meta: true }
);
// fail fast if we can't verify a 404 is from Elasticsearch
if (isNotFoundFromUnsupportedServer({ statusCode, headers })) {
@ -1923,7 +1925,7 @@ export class SavedObjectsRepository {
const raw = this._serializer.savedObjectToRaw(migrated as SavedObjectSanitizedDoc);
const { body } = await this.client.update<SavedObjectsRawDocSource>({
const body = await this.client.update<unknown, unknown, SavedObjectsRawDocSource>({
id: raw._id,
index: this.getIndexForType(type),
refresh,
@ -2028,6 +2030,7 @@ export class SavedObjectsRepository {
const { body, statusCode, headers } = await this.client.openPointInTime(esOptions, {
ignore: [404],
meta: true,
});
if (statusCode === 404) {
@ -2088,11 +2091,9 @@ export class SavedObjectsRepository {
id: string,
options?: SavedObjectsClosePointInTimeOptions
): Promise<SavedObjectsClosePointInTimeResponse> {
const { body } = await this.client.closePointInTime<SavedObjectsClosePointInTimeResponse>({
return await this.client.closePointInTime({
body: { id },
});
return body;
}
/**
@ -2213,6 +2214,7 @@ export class SavedObjectsRepository {
},
{
ignore: [404],
meta: true,
}
);

View file

@ -44,7 +44,7 @@ describe('RepositoryEsClient', () => {
it('transform elasticsearch errors into saved objects errors', async () => {
expect.assertions(1);
client.bulk = jest.fn().mockRejectedValue(new Error('reason'));
client.bulk.mockRejectedValue(new Error('reason'));
try {
await repositoryClient.bulk({ body: [] });
} catch (e) {

View file

@ -28,7 +28,7 @@ const methods = [
type MethodName = typeof methods[number];
export type RepositoryEsClient = Pick<ElasticsearchClient, MethodName>;
export type RepositoryEsClient = Pick<ElasticsearchClient, MethodName | 'transport'>;
export function createRepositoryEsClient(client: ElasticsearchClient): RepositoryEsClient {
return methods.reduce((acc: RepositoryEsClient, key: MethodName) => {

View file

@ -13,8 +13,6 @@ import {
mockDeleteLegacyUrlAliases,
} from './update_objects_spaces.test.mock';
import type { DeeplyMockedKeys } from '@kbn/utility-types/jest';
import type { ElasticsearchClient } from 'src/core/server/elasticsearch';
import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks';
import { loggerMock } from '../../../logging/logger.mock';
@ -66,7 +64,7 @@ afterAll(() => {
});
describe('#updateObjectsSpaces', () => {
let client: DeeplyMockedKeys<ElasticsearchClient>;
let client: ReturnType<typeof elasticsearchClientMock.createElasticsearchClient>;
/** Sets up the type registry, saved objects client, etc. and return the full parameters object to be passed to `updateObjectsSpaces` */
function setup({ objects = [], spacesToAdd = [], spacesToRemove = [], options }: SetupParams) {
@ -93,8 +91,29 @@ describe('#updateObjectsSpaces', () => {
/** Mocks the saved objects client so it returns the expected results */
function mockMgetResults(...results: Array<{ found: boolean }>) {
client.mget.mockReturnValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise({
client.mget.mockResponseOnce({
docs: results.map((x) =>
x.found
? {
_id: 'doesnt-matter',
_index: 'doesnt-matter',
_source: { namespaces: [EXISTING_SPACE] },
...VERSION_PROPS,
found: true,
}
: {
_id: 'doesnt-matter',
_index: 'doesnt-matter',
found: false,
}
),
});
}
/** Mocks the saved objects client so as to test unsupported server responding with 404 */
function mockMgetResultsNotFound(...results: Array<{ found: boolean }>) {
client.mget.mockResponseOnce(
{
docs: results.map((x) =>
x.found
? {
@ -110,33 +129,8 @@ describe('#updateObjectsSpaces', () => {
found: false,
}
),
})
);
}
/** Mocks the saved objects client so as to test unsupported server responding with 404 */
function mockMgetResultsNotFound(...results: Array<{ found: boolean }>) {
client.mget.mockReturnValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise(
{
docs: results.map((x) =>
x.found
? {
_id: 'doesnt-matter',
_index: 'doesnt-matter',
_source: { namespaces: [EXISTING_SPACE] },
...VERSION_PROPS,
found: true,
}
: {
_id: 'doesnt-matter',
_index: 'doesnt-matter',
found: false,
}
),
},
{ statusCode: 404 },
{}
)
},
{ statusCode: 404, headers: {} }
);
}
@ -155,13 +149,11 @@ describe('#updateObjectsSpaces', () => {
mockGetBulkOperationError.mockReturnValueOnce(undefined);
}
});
client.bulk.mockReturnValueOnce(
elasticsearchClientMock.createSuccessTransportRequestPromise({
items: results.map(() => ({})), // as long as the result does not contain an error field, it is treated as a success
errors: false,
took: 0,
})
);
client.bulk.mockResponseOnce({
items: results.map(() => ({})), // as long as the result does not contain an error field, it is treated as a success
errors: false,
took: 0,
});
}
/** Asserts that mget is called for the given objects */

View file

@ -124,6 +124,7 @@ const MAX_CONCURRENT_ALIAS_DELETIONS = 10;
function isMgetError(doc?: estypes.MgetResponseItem<unknown>): doc is estypes.MgetMultiGetError {
return Boolean(doc && 'error' in doc);
}
/**
* Gets all references and transitive references of the given objects. Ignores any object and/or reference that is not a multi-namespace
* type.
@ -204,7 +205,7 @@ export async function updateObjectsSpaces({
const bulkGetResponse = bulkGetDocs.length
? await client.mget<SavedObjectsRawDocSource>(
{ body: { docs: bulkGetDocs } },
{ ignore: [404] }
{ ignore: [404], meta: true }
)
: undefined;
// fail fast if we can't verify a 404 response is from Elasticsearch
@ -338,7 +339,7 @@ export async function updateObjectsSpaces({
const { type, id, updatedSpaces, esRequestIndex } = expectedResult.value;
if (esRequestIndex !== undefined) {
const response = bulkOperationResponse?.body.items[esRequestIndex] ?? {};
const response = bulkOperationResponse?.items[esRequestIndex] ?? {};
const rawResponse = Object.values(response)[0] as any;
const error = getBulkOperationError(type, id, rawResponse);
if (error) {

View file

@ -10,6 +10,7 @@ import { AddConfigDeprecation } from '@kbn/config';
import Boom from '@hapi/boom';
import { ByteSizeValue } from '@kbn/config-schema';
import { CliArgs } from '@kbn/config';
import type { Client } from '@elastic/elasticsearch';
import type { ClientOptions } from '@elastic/elasticsearch/lib/client';
import { ConditionalType } from '@kbn/config-schema';
import { ConfigDeprecation } from '@kbn/config';
@ -31,7 +32,6 @@ import { EnvironmentMode } from '@kbn/config';
import { errors } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { IncomingHttpHeaders } from 'http';
import type { KibanaClient } from '@elastic/elasticsearch/lib/api/kibana';
import { Logger } from '@kbn/logging';
import { LoggerFactory } from '@kbn/logging';
import { LogLevel as LogLevel_2 } from '@kbn/logging';
@ -53,9 +53,6 @@ import { ResponseToolkit } from '@hapi/hapi';
import { SchemaTypeError } from '@kbn/config-schema';
import { ShallowPromise } from '@kbn/utility-types';
import { Stream } from 'stream';
import type { TransportRequestOptions } from '@elastic/elasticsearch';
import type { TransportRequestParams } from '@elastic/elasticsearch';
import type { TransportResult } from '@elastic/elasticsearch';
import { Type } from '@kbn/config-schema';
import { TypeOf } from '@kbn/config-schema';
import { UiCounterMetricType } from '@kbn/analytics';
@ -888,11 +885,7 @@ export { EcsEventOutcome }
export { EcsEventType }
// @public
export type ElasticsearchClient = Omit<KibanaClient, 'connectionPool' | 'transport' | 'serializer' | 'extend' | 'child' | 'close' | 'diagnostic'> & {
transport: {
request<TResponse = unknown>(params: TransportRequestParams, options?: TransportRequestOptions): Promise<TransportResult<TResponse>>;
};
};
export type ElasticsearchClient = Omit<Client, 'connectionPool' | 'serializer' | 'extend' | 'child' | 'close' | 'diagnostic'>;
// @public
export type ElasticsearchClientConfig = Pick<ElasticsearchConfig, 'customHeaders' | 'compression' | 'sniffOnStart' | 'sniffOnConnectionFault' | 'requestHeadersWhitelist' | 'sniffInterval' | 'hosts' | 'username' | 'password' | 'serviceAccountToken'> & {
@ -3167,7 +3160,7 @@ export const validBodyOutput: readonly ["data", "stream"];
// Warnings were encountered during analysis:
//
// src/core/server/elasticsearch/client/types.ts:93:7 - (ae-forgotten-export) The symbol "Explanation" needs to be exported by the entry point index.d.ts
// src/core/server/elasticsearch/client/types.ts:81:7 - (ae-forgotten-export) The symbol "Explanation" needs to be exported by the entry point index.d.ts
// src/core/server/http/router/response.ts:302:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:375:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:377:3 - (ae-forgotten-export) The symbol "SavedObjectsConfigType" needs to be exported by the entry point index.d.ts

View file

@ -8,7 +8,7 @@
import type supertest from 'supertest';
import type { SavedObjectsClientContract, IUiSettingsClient } from 'src/core/server';
import type { KibanaClient } from '@elastic/elasticsearch/lib/api/kibana';
import type { Client } from '@elastic/elasticsearch';
import {
createTestServers,
@ -26,7 +26,7 @@ let kbn: TestKibanaUtils;
interface AllServices {
savedObjectsClient: SavedObjectsClientContract;
esClient: KibanaClient;
esClient: Client;
uiSettings: IUiSettingsClient;
supertest: (method: HttpMethod, path: string) => supertest.Test;
}
@ -55,7 +55,7 @@ export function getServices() {
return services;
}
const esClient = esServer.es.getKibanaEsClient();
const esClient = esServer.es.getClient();
const savedObjectsClient = kbn.coreStart.savedObjects.getScopedClient(
httpServerMock.createKibanaRequest()

View file

@ -10,7 +10,6 @@ import { coreMock } from '../../../../core/server/mocks';
import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server';
import { ConfigSchema } from '../../config';
import type { DeeplyMockedKeys } from '@kbn/utility-types/jest';
import type { TransportResult } from '@elastic/elasticsearch';
import { termsAggSuggestions } from './terms_agg';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { duration } from 'moment';
@ -25,14 +24,12 @@ const configMock = {
// @ts-expect-error not full interface
const mockResponse = {
body: {
aggregations: {
suggestions: {
buckets: [{ key: 'whoa' }, { key: 'amazing' }],
},
aggregations: {
suggestions: {
buckets: [{ key: 'whoa' }, { key: 'amazing' }],
},
},
} as TransportResult<estypes.SearchResponse<any>>;
} as estypes.SearchResponse<any>;
jest.mock('../data_views');

View file

@ -45,8 +45,8 @@ export async function termsAggSuggestions(
);
const buckets =
get(result.body, 'aggregations.suggestions.buckets') ||
get(result.body, 'aggregations.nestedSuggestions.suggestions.buckets');
get(result, 'aggregations.suggestions.buckets') ||
get(result, 'aggregations.nestedSuggestions.suggestions.buckets');
return map(buckets ?? [], 'key');
}

View file

@ -11,7 +11,6 @@ import { coreMock } from '../../../../core/server/mocks';
import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server';
import { ConfigSchema } from '../../config';
import type { DeeplyMockedKeys } from '@kbn/utility-types/jest';
import type { TransportResult } from '@elastic/elasticsearch';
import { TermsEnumResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
let savedObjectsClientMock: jest.Mocked<SavedObjectsClientContract>;
@ -19,9 +18,7 @@ let esClientMock: DeeplyMockedKeys<ElasticsearchClient>;
const configMock = {
autocomplete: { valueSuggestions: { tiers: ['data_hot', 'data_warm', 'data_content'] } },
} as ConfigSchema;
const mockResponse = {
body: { terms: ['whoa', 'amazing'] },
};
const mockResponse = { terms: ['whoa', 'amazing'] };
jest.mock('../data_views');
@ -30,9 +27,7 @@ describe('_terms_enum suggestions', () => {
const requestHandlerContext = coreMock.createRequestHandlerContext();
savedObjectsClientMock = requestHandlerContext.savedObjects.client;
esClientMock = requestHandlerContext.elasticsearch.client.asCurrentUser;
esClientMock.termsEnum.mockResolvedValue(
mockResponse as unknown as TransportResult<TermsEnumResponse>
);
esClientMock.termsEnum.mockResolvedValue(mockResponse as unknown as TermsEnumResponse);
});
it('calls the _terms_enum API with the field, query, filters, and config tiers', async () => {
@ -73,7 +68,7 @@ describe('_terms_enum suggestions', () => {
"index": "index",
}
`);
expect(result).toEqual(mockResponse.body.terms);
expect(result).toEqual(mockResponse.terms);
});
it('calls the _terms_enum API and fallback to fieldName when field is null', async () => {
@ -113,6 +108,6 @@ describe('_terms_enum suggestions', () => {
"index": "index",
}
`);
expect(result).toEqual(mockResponse.body.terms);
expect(result).toEqual(mockResponse.terms);
});
});

View file

@ -54,5 +54,5 @@ export async function termsEnumSuggestions(
}
);
return result.body.terms;
return result.terms;
}

View file

@ -30,45 +30,41 @@ function setupMockCallCluster(
function mockedEsGetMethod() {
if (optCount === null) {
return Promise.resolve({
body: {
_index: '.kibana_1',
_id: 'kql-telemetry:kql-telemetry',
found: false,
},
_index: '.kibana_1',
_id: 'kql-telemetry:kql-telemetry',
found: false,
});
} else {
return Promise.resolve({
body: {
_source: {
'kql-telemetry': { ...optCount },
type: 'kql-telemetry',
updated_at: '2018-10-05T20:20:56.258Z',
},
_source: {
'kql-telemetry': { ...optCount },
type: 'kql-telemetry',
updated_at: '2018-10-05T20:20:56.258Z',
},
});
}
}
function mockedEsSearchMethod() {
if (language === 'missingConfigDoc') {
return Promise.resolve({ body: { hits: { hits: [] } } });
return Promise.resolve({ hits: { hits: [] } });
} else {
return Promise.resolve({
body: {
hits: {
hits: [
{
_source: {
config: {
'search:queryLanguage': language,
},
hits: {
hits: [
{
_source: {
config: {
'search:queryLanguage': language,
},
},
],
},
},
],
},
});
}
}
const esClientMock = {
get: jest.fn().mockImplementation(mockedEsGetMethod),
search: jest.fn().mockImplementation(mockedEsSearchMethod),

View file

@ -20,7 +20,7 @@ export interface Usage {
export function fetchProvider(index: string) {
return async ({ esClient }: CollectorFetchContext): Promise<Usage> => {
const [{ body: response }, { body: config }] = await Promise.all([
const [response, config] = await Promise.all([
esClient.get(
{
index,

View file

@ -15,7 +15,7 @@ interface SearchTelemetry {
export function fetchProvider(kibanaIndex: string) {
return async ({ esClient }: CollectorFetchContext): Promise<ReportedUsage> => {
const { body: esResponse } = await esClient.search<SearchTelemetry>(
const esResponse = await esClient.search<SearchTelemetry>(
{
index: kibanaIndex,
body: {

View file

@ -53,11 +53,15 @@ export const eqlSearchStrategyProvider = (
...request.params,
};
const response = id
? await client.get({ ...params, id }, { ...request.options, signal: options.abortSignal })
? await client.get(
{ ...params, id },
{ ...request.options, signal: options.abortSignal, meta: true }
)
: // @ts-expect-error optional key cannot be used since search doesn't expect undefined
await client.search(params as EqlSearchStrategyRequest['params'], {
...request.options,
abortController: { signal: options.abortSignal },
meta: true,
});
return toEqlKibanaSearchResponse(response as TransportResult<EqlSearchResponse>);

View file

@ -5,7 +5,8 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { TransportResult } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { elasticsearchServiceMock } from '../../../../../../core/server/mocks';
import { pluginInitializerContextConfigMock } from '../../../../../../core/server/mocks';
import { esSearchStrategyProvider } from './es_search_strategy';
@ -23,31 +24,28 @@ describe('ES search strategy', () => {
skipped: 2,
successful: 7,
},
} as const;
let mockedApiCaller: Promise<TransportResult<any>>;
let mockApiCaller: jest.Mock<() => TransportResult<any>>;
} as estypes.SearchResponse;
const mockLogger: any = {
debug: () => {},
};
let esClient: ReturnType<typeof elasticsearchServiceMock.createElasticsearchClient>;
function getMockedDeps(err?: Record<string, any>) {
mockApiCaller = jest.fn().mockImplementation(() => {
if (err) {
mockedApiCaller = elasticsearchServiceMock.createErrorTransportRequestPromise(err);
} else {
mockedApiCaller = elasticsearchServiceMock.createSuccessTransportRequestPromise(
successBody,
{ statusCode: 200 }
);
}
return mockedApiCaller;
});
esClient = elasticsearchServiceMock.createElasticsearchClient();
if (err) {
esClient.search.mockImplementation(() => Promise.reject(err));
} else {
esClient.search.mockResponse(successBody, { statusCode: 200 });
}
return {
uiSettingsClient: {
get: () => {},
},
esClient: { asCurrentUser: { search: mockApiCaller } },
esClient: { asCurrentUser: esClient },
} as unknown as SearchStrategyDependencies;
}
@ -65,8 +63,8 @@ describe('ES search strategy', () => {
await esSearchStrategyProvider(mockConfig$, mockLogger)
.search({ params }, {}, getMockedDeps())
.subscribe(() => {
expect(mockApiCaller).toBeCalled();
expect(mockApiCaller.mock.calls[0][0]).toEqual({
expect(esClient.search).toBeCalled();
expect(esClient.search.mock.calls[0][0]).toEqual({
...params,
ignore_unavailable: true,
track_total_hits: true,
@ -81,8 +79,8 @@ describe('ES search strategy', () => {
await esSearchStrategyProvider(mockConfig$, mockLogger)
.search({ params }, {}, getMockedDeps())
.subscribe(() => {
expect(mockApiCaller).toBeCalled();
expect(mockApiCaller.mock.calls[0][0]).toEqual({
expect(esClient.search).toBeCalled();
expect(esClient.search.mock.calls[0][0]).toEqual({
...params,
track_total_hits: true,
});
@ -117,8 +115,8 @@ describe('ES search strategy', () => {
.search({ params }, { abortSignal: abortController.signal }, getMockedDeps())
.toPromise();
expect(mockApiCaller).toBeCalled();
expect(mockApiCaller.mock.calls[0][0]).toEqual({
expect(esClient.search).toBeCalled();
expect(esClient.search.mock.calls[0][0]).toEqual({
...params,
track_total_hits: true,
});
@ -139,7 +137,7 @@ describe('ES search strategy', () => {
.search({ params }, {}, getMockedDeps(errResponse))
.toPromise();
} catch (e) {
expect(mockApiCaller).toBeCalled();
expect(esClient.search).toBeCalled();
expect(e).toBeInstanceOf(KbnServerError);
expect(e.statusCode).toBe(404);
expect(e.message).toBe(errResponse.message);
@ -157,7 +155,7 @@ describe('ES search strategy', () => {
.search({ params }, {}, getMockedDeps(errResponse))
.toPromise();
} catch (e) {
expect(mockApiCaller).toBeCalled();
expect(esClient.search).toBeCalled();
expect(e).toBeInstanceOf(KbnServerError);
expect(e.statusCode).toBe(500);
expect(e.message).toBe(errResponse.message);
@ -175,7 +173,7 @@ describe('ES search strategy', () => {
.search({ params }, {}, getMockedDeps(errResponse))
.toPromise();
} catch (e) {
expect(mockApiCaller).toBeCalled();
expect(esClient.search).toBeCalled();
expect(e).toBeInstanceOf(KbnServerError);
expect(e.statusCode).toBe(500);
expect(e.message).toBe(errResponse.message);
@ -192,7 +190,7 @@ describe('ES search strategy', () => {
.search({ indexType: 'banana', params }, {}, getMockedDeps())
.toPromise();
} catch (e) {
expect(mockApiCaller).not.toBeCalled();
expect(esClient.search).not.toBeCalled();
expect(e).toBeInstanceOf(KbnServerError);
expect(e.message).toBe('Unsupported index pattern type banana');
expect(e.statusCode).toBe(400);

View file

@ -46,7 +46,7 @@ export const esSearchStrategyProvider = (
...(terminateAfter ? { terminate_after: terminateAfter } : {}),
...requestParams,
};
const { body } = await esClient.asCurrentUser.search(params, {
const body = await esClient.asCurrentUser.search(params, {
signal: abortSignal,
});
const response = shimHitsTotal(body, options);

View file

@ -68,9 +68,13 @@ export const enhancedEsSearchStrategyProvider = (
...request.params,
};
const { body, headers } = id
? await client.asyncSearch.get({ ...params, id }, { signal: options.abortSignal })
? await client.asyncSearch.get(
{ ...params, id },
{ signal: options.abortSignal, meta: true }
)
: await client.asyncSearch.submit(params, {
signal: options.abortSignal,
meta: true,
});
const response = shimHitsTotal(body.response, options);
@ -124,6 +128,7 @@ export const enhancedEsSearchStrategyProvider = (
},
{
signal: options?.abortSignal,
meta: true,
}
);

View file

@ -70,7 +70,7 @@ export const registerFieldPreviewRoute = ({ router }: RouteDependencies): void =
body,
});
const fieldValue = response.body.hits.hits[0]?.fields?.my_runtime_field ?? '';
const fieldValue = response.hits.hits[0]?.fields?.my_runtime_field ?? '';
return res.ok({ body: { values: fieldValue } });
} catch (error: any) {

View file

@ -28,23 +28,27 @@ export function registerPreviewScriptedFieldRoute(router: IRouter): void {
const { index, name, script, query, additionalFields } = request.body;
try {
const response = await client.search({
index,
body: {
_source: additionalFields && additionalFields.length > 0 ? additionalFields : undefined,
size: 10,
timeout: '30s',
query: query ?? { match_all: {} },
script_fields: {
[name]: {
script: {
lang: 'painless',
source: script,
const response = await client.search(
{
index,
body: {
_source:
additionalFields && additionalFields.length > 0 ? additionalFields : undefined,
size: 10,
timeout: '30s',
query: query ?? { match_all: {} },
script_fields: {
[name]: {
script: {
lang: 'painless',
source: script,
},
},
},
},
},
});
{ meta: true }
);
return res.ok({ body: response });
} catch (err) {

View file

@ -31,7 +31,7 @@ export function registerResolveIndexRoute(router: IRouter): void {
},
},
async (context, req, res) => {
const { body } = await context.core.elasticsearch.client.asCurrentUser.indices.resolveIndex({
const body = await context.core.elasticsearch.client.asCurrentUser.indices.resolveIndex({
name: req.params.query,
expand_wildcards: req.query.expand_wildcards || 'open',
});

View file

@ -5,46 +5,46 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { IndexPatternsFetcher } from '.';
import { ElasticsearchClient } from 'kibana/server';
import { elasticsearchServiceMock } from '../../../../core/server/mocks';
import * as indexNotFoundException from './index_not_found_exception.json';
describe('Index Pattern Fetcher - server', () => {
let indexPatterns: IndexPatternsFetcher;
let esClient: ElasticsearchClient;
let esClient: ReturnType<typeof elasticsearchServiceMock.createElasticsearchClient>;
const emptyResponse = {
body: {
indices: [],
},
indices: [],
};
const response = {
body: {
indices: ['b'],
fields: [{ name: 'foo' }, { name: 'bar' }, { name: 'baz' }],
},
indices: ['b'],
fields: [{ name: 'foo' }, { name: 'bar' }, { name: 'baz' }],
};
const patternList = ['a', 'b', 'c'];
beforeEach(() => {
jest.clearAllMocks();
esClient = {
fieldCaps: jest.fn().mockResolvedValueOnce(emptyResponse).mockResolvedValue(response),
} as unknown as ElasticsearchClient;
esClient = elasticsearchServiceMock.createElasticsearchClient();
indexPatterns = new IndexPatternsFetcher(esClient);
});
it('Removes pattern without matching indices', async () => {
esClient.fieldCaps
.mockResponseOnce(emptyResponse as unknown as estypes.FieldCapsResponse)
.mockResponse(response as unknown as estypes.FieldCapsResponse);
// first field caps request returns empty
const result = await indexPatterns.validatePatternListActive(patternList);
expect(result).toEqual(['b', 'c']);
});
it('Keeps matching and negating patterns', async () => {
esClient.fieldCaps
.mockResponseOnce(emptyResponse as unknown as estypes.FieldCapsResponse)
.mockResponse(response as unknown as estypes.FieldCapsResponse);
// first field caps request returns empty
const result = await indexPatterns.validatePatternListActive(['-a', 'b', 'c']);
expect(result).toEqual(['-a', 'c']);
});
it('Returns all patterns when all match indices', async () => {
esClient = {
fieldCaps: jest.fn().mockResolvedValue(response),
} as unknown as ElasticsearchClient;
esClient.fieldCaps.mockResponse(response as unknown as estypes.FieldCapsResponse);
indexPatterns = new IndexPatternsFetcher(esClient);
const result = await indexPatterns.validatePatternListActive(patternList);
expect(result).toEqual(patternList);
@ -52,6 +52,7 @@ describe('Index Pattern Fetcher - server', () => {
it('Removes pattern when error is thrown', async () => {
class ServerError extends Error {
public body?: Record<string, any>;
constructor(
message: string,
public readonly statusCode: number,
@ -61,34 +62,29 @@ describe('Index Pattern Fetcher - server', () => {
this.body = errBody;
}
}
esClient = {
fieldCaps: jest
.fn()
.mockResolvedValueOnce(response)
.mockRejectedValue(
esClient.fieldCaps
.mockResponseOnce(response as unknown as estypes.FieldCapsResponse)
.mockImplementationOnce(() => {
return Promise.reject(
new ServerError('index_not_found_exception', 404, indexNotFoundException)
),
} as unknown as ElasticsearchClient;
);
});
indexPatterns = new IndexPatternsFetcher(esClient);
const result = await indexPatterns.validatePatternListActive(patternList);
expect(result).toEqual([patternList[0]]);
});
it('When allowNoIndices is false, run validatePatternListActive', async () => {
const fieldCapsMock = jest.fn();
esClient = {
fieldCaps: fieldCapsMock.mockResolvedValue(response),
} as unknown as ElasticsearchClient;
esClient.fieldCaps.mockResponse(response as unknown as estypes.FieldCapsResponse);
indexPatterns = new IndexPatternsFetcher(esClient);
await indexPatterns.getFieldsForWildcard({ pattern: patternList });
expect(fieldCapsMock.mock.calls).toHaveLength(4);
expect(esClient.fieldCaps).toHaveBeenCalledTimes(4);
});
it('When allowNoIndices is true, do not run validatePatternListActive', async () => {
const fieldCapsMock = jest.fn();
esClient = {
fieldCaps: fieldCapsMock.mockResolvedValue(response),
} as unknown as ElasticsearchClient;
esClient.fieldCaps.mockResponse(response as unknown as estypes.FieldCapsResponse);
indexPatterns = new IndexPatternsFetcher(esClient, true);
await indexPatterns.getFieldsForWildcard({ pattern: patternList });
expect(fieldCapsMock.mock.calls).toHaveLength(1);
expect(esClient.fieldCaps).toHaveBeenCalledTimes(1);
});
});

View file

@ -37,10 +37,12 @@ interface FieldSubType {
export class IndexPatternsFetcher {
private elasticsearchClient: ElasticsearchClient;
private allowNoIndices: boolean;
constructor(elasticsearchClient: ElasticsearchClient, allowNoIndices: boolean = false) {
this.elasticsearchClient = elasticsearchClient;
this.allowNoIndices = allowNoIndices;
}
/**
* Get a list of field objects for an index pattern that may contain wildcards
*
@ -80,11 +82,9 @@ export class IndexPatternsFetcher {
if (type === 'rollup' && rollupIndex) {
const rollupFields: FieldDescriptor[] = [];
const rollupIndexCapabilities = getCapabilitiesForRollupIndices(
(
await this.elasticsearchClient.rollup.getRollupIndexCaps({
index: rollupIndex,
})
).body
await this.elasticsearchClient.rollup.getRollupIndexCaps({
index: rollupIndex,
})
)[rollupIndex].aggs;
const fieldCapsResponseObj = keyBy(fieldCapsResponse, 'name');
// Keep meta fields
@ -150,7 +150,7 @@ export class IndexPatternsFetcher {
ignore_unavailable: true,
allow_no_indices: false,
});
return searchResponse.body.indices.length > 0;
return searchResponse.indices.length > 0;
})
.map((p) => p.catch(() => false))
);

View file

@ -68,13 +68,16 @@ export async function callFieldCapsApi(params: FieldCapsApiParams) {
},
} = params;
try {
return await callCluster.fieldCaps({
index: indices,
fields: '*',
ignore_unavailable: true,
index_filter: filter,
...fieldCapsOptions,
});
return await callCluster.fieldCaps(
{
index: indices,
fields: '*',
ignore_unavailable: true,
index_filter: filter,
...fieldCapsOptions,
},
{ meta: true }
);
} catch (error) {
throw convertEsError(indices, error);
}

View file

@ -64,15 +64,13 @@ describe('server/index_patterns/service/lib/resolve_time_pattern', () => {
describe('read response', () => {
it('returns all aliases names in result.all, ordered by time desc', async () => {
sandbox.stub(callIndexAliasApiNS, 'callIndexAliasApi').returns({
body: {
'logs-2016.2': {},
'logs-Saturday-2017.1': {},
'logs-2016.1': {},
'logs-Sunday-2017.1': {},
'logs-2015': {},
'logs-2016.3': {},
'logs-Friday-2017.1': {},
},
'logs-2016.2': {},
'logs-Saturday-2017.1': {},
'logs-2016.1': {},
'logs-Sunday-2017.1': {},
'logs-2015': {},
'logs-2016.3': {},
'logs-Friday-2017.1': {},
});
const resp = await resolveTimePattern(noop, TIME_PATTERN);
@ -90,15 +88,13 @@ describe('server/index_patterns/service/lib/resolve_time_pattern', () => {
it('returns all indices matching the time pattern in matches, ordered by time desc', async () => {
sandbox.stub(callIndexAliasApiNS, 'callIndexAliasApi').returns({
body: {
'logs-2016.2': {},
'logs-Saturday-2017.1': {},
'logs-2016.1': {},
'logs-Sunday-2017.1': {},
'logs-2015': {},
'logs-2016.3': {},
'logs-Friday-2017.1': {},
},
'logs-2016.2': {},
'logs-Saturday-2017.1': {},
'logs-2016.1': {},
'logs-Sunday-2017.1': {},
'logs-2015': {},
'logs-2016.3': {},
'logs-Friday-2017.1': {},
});
const resp = await resolveTimePattern(noop, TIME_PATTERN);

View file

@ -28,7 +28,7 @@ import { callIndexAliasApi } from './es_api';
export async function resolveTimePattern(callCluster: ElasticsearchClient, timePattern: string) {
const aliases = await callIndexAliasApi(callCluster, timePatternToWildcard(timePattern));
const allIndexDetails = chain(aliases.body)
const allIndexDetails = chain(aliases)
.reduce(
(acc: string[], index: any, indexName: string) =>
acc.concat(indexName, Object.keys(index.aliases || {})),

View file

@ -69,13 +69,11 @@ describe('hasUserIndexPattern', () => {
});
it('calls indices.resolveIndex for the index patterns', async () => {
esClient.indices.resolveIndex.mockReturnValue(
elasticsearchServiceMock.createSuccessTransportRequestPromise({
indices: [],
data_streams: [],
aliases: [],
})
);
esClient.indices.resolveIndex.mockResponse({
indices: [],
data_streams: [],
aliases: [],
});
await hasUserIndexPattern({ esClient, soClient });
expect(esClient.indices.resolveIndex).toHaveBeenCalledWith({
name: 'logs-*,metrics-*',
@ -83,91 +81,79 @@ describe('hasUserIndexPattern', () => {
});
it('returns false if no logs or metrics data_streams exist', async () => {
esClient.indices.resolveIndex.mockReturnValue(
elasticsearchServiceMock.createSuccessTransportRequestPromise({
indices: [],
data_streams: [],
aliases: [],
})
);
esClient.indices.resolveIndex.mockResponse({
indices: [],
data_streams: [],
aliases: [],
});
expect(await hasUserIndexPattern({ esClient, soClient })).toEqual(false);
});
it('returns true if any index exists', async () => {
esClient.indices.resolveIndex.mockReturnValue(
elasticsearchServiceMock.createSuccessTransportRequestPromise({
indices: [{ name: 'logs', attributes: [] }],
data_streams: [],
aliases: [],
})
);
esClient.indices.resolveIndex.mockResponse({
indices: [{ name: 'logs', attributes: [] }],
data_streams: [],
aliases: [],
});
expect(await hasUserIndexPattern({ esClient, soClient })).toEqual(true);
});
it('returns false if only metrics-elastic_agent data stream exists', async () => {
esClient.indices.resolveIndex.mockReturnValue(
elasticsearchServiceMock.createSuccessTransportRequestPromise({
indices: [],
data_streams: [
{
name: 'metrics-elastic_agent',
timestamp_field: '@timestamp',
backing_indices: ['.ds-metrics-elastic_agent'],
},
],
aliases: [],
})
);
esClient.indices.resolveIndex.mockResponse({
indices: [],
data_streams: [
{
name: 'metrics-elastic_agent',
timestamp_field: '@timestamp',
backing_indices: ['.ds-metrics-elastic_agent'],
},
],
aliases: [],
});
expect(await hasUserIndexPattern({ esClient, soClient })).toEqual(false);
});
it('returns false if only logs-elastic_agent data stream exists', async () => {
esClient.indices.resolveIndex.mockReturnValue(
elasticsearchServiceMock.createSuccessTransportRequestPromise({
indices: [],
data_streams: [
{
name: 'logs-elastic_agent',
timestamp_field: '@timestamp',
backing_indices: ['.ds-logs-elastic_agent'],
},
],
aliases: [],
})
);
esClient.indices.resolveIndex.mockResponse({
indices: [],
data_streams: [
{
name: 'logs-elastic_agent',
timestamp_field: '@timestamp',
backing_indices: ['.ds-logs-elastic_agent'],
},
],
aliases: [],
});
expect(await hasUserIndexPattern({ esClient, soClient })).toEqual(false);
});
it('returns false if only metrics-endpoint.metadata_current_default index exists', async () => {
esClient.indices.resolveIndex.mockReturnValue(
elasticsearchServiceMock.createSuccessTransportRequestPromise({
indices: [
{
name: 'metrics-endpoint.metadata_current_default',
attributes: ['open'],
},
],
aliases: [],
data_streams: [],
})
);
esClient.indices.resolveIndex.mockResponse({
indices: [
{
name: 'metrics-endpoint.metadata_current_default',
attributes: ['open'],
},
],
aliases: [],
data_streams: [],
});
expect(await hasUserIndexPattern({ esClient, soClient })).toEqual(false);
});
it('returns true if any other data stream exists', async () => {
esClient.indices.resolveIndex.mockReturnValue(
elasticsearchServiceMock.createSuccessTransportRequestPromise({
indices: [],
data_streams: [
{
name: 'other',
timestamp_field: '@timestamp',
backing_indices: ['.ds-other'],
},
],
aliases: [],
})
);
esClient.indices.resolveIndex.mockResponse({
indices: [],
data_streams: [
{
name: 'other',
timestamp_field: '@timestamp',
backing_indices: ['.ds-other'],
},
],
aliases: [],
});
expect(await hasUserIndexPattern({ esClient, soClient })).toEqual(true);
});
});

View file

@ -44,13 +44,13 @@ export const hasUserIndexPattern = async ({ esClient, soClient }: Deps): Promise
name: `${FLEET_ASSETS_TO_IGNORE.LOGS_INDEX_PATTERN},${FLEET_ASSETS_TO_IGNORE.METRICS_INDEX_PATTERN}`,
});
const hasAnyNonDefaultFleetIndices = resolveResponse.body.indices.some(
const hasAnyNonDefaultFleetIndices = resolveResponse.indices.some(
(ds) => ds.name !== FLEET_ASSETS_TO_IGNORE.METRICS_ENDPOINT_INDEX_TO_IGNORE
);
if (hasAnyNonDefaultFleetIndices) return true;
const hasAnyNonDefaultFleetDataStreams = resolveResponse.body.data_streams.some(
const hasAnyNonDefaultFleetDataStreams = resolveResponse.data_streams.some(
(ds) =>
ds.name !== FLEET_ASSETS_TO_IGNORE.METRICS_DATA_STREAM_TO_IGNORE &&
ds.name !== FLEET_ASSETS_TO_IGNORE.LOGS_DATA_STREAM_TO_IGNORE

View file

@ -25,7 +25,7 @@ export const registerHitsStatusRoute = (router: IRouter) => {
const client = context.core.elasticsearch.client;
try {
const { body } = await client.asCurrentUser.search({
const body = await client.asCurrentUser.search({
index,
size: 1,
body: {

Some files were not shown because too many files have changed in this diff Show more