mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
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:
parent
172bf98942
commit
6627bd8b3a
685 changed files with 6851 additions and 7899 deletions
|
@ -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'>;
|
||||
```
|
||||
|
|
|
@ -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) });
|
||||
|
|
|
@ -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'
|
||||
>;
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -34,7 +34,6 @@ export type {
|
|||
export {
|
||||
esTestConfig,
|
||||
createTestEsCluster,
|
||||
convertToKibanaClient,
|
||||
createEsClientForTesting,
|
||||
createEsClientForFtrConfig,
|
||||
} from './es';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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 }
|
||||
>({
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
);
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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 });
|
||||
})
|
||||
|
|
|
@ -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 || {} });
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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: {} }],
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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({});
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -43,7 +43,7 @@ export const fetchIndices =
|
|||
},
|
||||
{ ignore: [404], maxRetries: 0 }
|
||||
)
|
||||
.then(({ body }) => {
|
||||
.then((body) => {
|
||||
return Either.right(body);
|
||||
})
|
||||
.catch(catchRetryableEsClientErrors);
|
||||
|
|
|
@ -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' });
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)])
|
||||
|
|
|
@ -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'`,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
|
|
@ -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!');
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -54,5 +54,5 @@ export async function termsEnumSuggestions(
|
|||
}
|
||||
);
|
||||
|
||||
return result.body.terms;
|
||||
return result.terms;
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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>);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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))
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 || {})),
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue