From 68346eceffe778bcfe10fe2449c10db5a5f5eb7d Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Thu, 21 Nov 2019 14:24:37 +0100 Subject: [PATCH 1/6] update testing infrastructure for ui_settings (#51266) * update testing infrastructure for ui_settings * remove type mute --- .../ui_settings/create_objects_client_stub.ts | 50 ---- ...reate_or_upgrade_saved_config.test.mock.ts | 23 ++ .../create_or_upgrade_saved_config.test.ts | 73 ++--- .../get_upgradeable_config.test.mock.ts} | 11 +- .../create_or_upgrade.test.ts | 95 +++---- .../is_config_version_upgradeable.test.ts | 4 +- .../integration_tests/doc_exists.ts | 45 ++- .../integration_tests/doc_missing.ts | 29 +- .../doc_missing_and_index_read_only.ts | 31 +-- .../integration_tests/lib/index.ts | 2 - .../ui_settings/ui_settings_client.test.ts | 260 +++++++++--------- 11 files changed, 294 insertions(+), 329 deletions(-) delete mode 100644 src/core/server/ui_settings/create_objects_client_stub.ts create mode 100644 src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.mock.ts rename src/core/server/ui_settings/{integration_tests/lib/assert.ts => create_or_upgrade_saved_config/get_upgradeable_config.test.mock.ts} (81%) diff --git a/src/core/server/ui_settings/create_objects_client_stub.ts b/src/core/server/ui_settings/create_objects_client_stub.ts deleted file mode 100644 index 1e4a5e6fb58ec6..00000000000000 --- a/src/core/server/ui_settings/create_objects_client_stub.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; -import { SavedObjectsClient } from '../saved_objects'; - -export const savedObjectsClientErrors = SavedObjectsClient.errors; - -export interface SavedObjectsClientStub { - update: sinon.SinonStub; - get: sinon.SinonStub; - create: sinon.SinonStub; - bulkCreate: sinon.SinonStub; - bulkGet: sinon.SinonStub; - bulkUpdate: sinon.SinonStub; - delete: sinon.SinonStub; - find: sinon.SinonStub; - errors: typeof savedObjectsClientErrors; -} - -export function createObjectsClientStub(esDocSource = {}): SavedObjectsClientStub { - const savedObjectsClient = { - update: sinon.stub(), - get: sinon.stub().returns({ attributes: esDocSource }), - create: sinon.stub(), - errors: savedObjectsClientErrors, - bulkCreate: sinon.stub(), - bulkGet: sinon.stub(), - bulkUpdate: sinon.stub(), - delete: sinon.stub(), - find: sinon.stub(), - }; - return savedObjectsClient; -} diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.mock.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.mock.ts new file mode 100644 index 00000000000000..0b62aecc1d13fe --- /dev/null +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.mock.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const createOrUpgradeSavedConfigMock = jest.fn(); +jest.doMock('./create_or_upgrade_saved_config', () => ({ + createOrUpgradeSavedConfig: createOrUpgradeSavedConfigMock, +})); diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts index 65b8792532acf5..eab96cc80c8839 100644 --- a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts @@ -17,19 +17,18 @@ * under the License. */ -import sinon from 'sinon'; import Chance from 'chance'; import { SavedObjectsErrorHelpers } from '../../saved_objects'; - +import { savedObjectsClientMock } from '../../saved_objects/service/saved_objects_client.mock'; import { loggingServiceMock } from '../../logging/logging_service.mock'; -import * as getUpgradeableConfigNS from './get_upgradeable_config'; +import { getUpgradeableConfigMock } from './get_upgradeable_config.test.mock'; + import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config'; const chance = new Chance(); describe('uiSettings/createOrUpgradeSavedConfig', function() { - const sandbox = sinon.createSandbox(); - afterEach(() => sandbox.restore()); + afterEach(() => jest.resetAllMocks()); const version = '4.0.1'; const prevVersion = '4.0.0'; @@ -37,14 +36,16 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { function setup() { const logger = loggingServiceMock.create(); - const getUpgradeableConfig = sandbox.stub(getUpgradeableConfigNS, 'getUpgradeableConfig'); - const savedObjectsClient = { - create: sinon.stub().callsFake(async (type, attributes, options = {}) => ({ - type, - id: options.id, - version: 'foo', - })), - } as any; // mute until we have savedObjects mocks + const getUpgradeableConfig = getUpgradeableConfigMock; + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.create.mockImplementation( + async (type, attributes, options = {}) => + ({ + type, + id: options.id, + version: 'foo', + } as any) + ); async function run(options = {}) { const resp = await createOrUpgradeSavedConfig({ @@ -56,8 +57,8 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { ...options, }); - sinon.assert.calledOnce(getUpgradeableConfig); - sinon.assert.alwaysCalledWith(getUpgradeableConfig, { savedObjectsClient, version }); + expect(getUpgradeableConfigMock).toHaveBeenCalledTimes(1); + expect(getUpgradeableConfig).toHaveBeenCalledWith({ savedObjectsClient, version }); return resp; } @@ -78,9 +79,8 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { await run(); - sinon.assert.calledOnce(savedObjectsClient.create); - sinon.assert.calledWithExactly( - savedObjectsClient.create, + expect(savedObjectsClient.create).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.create).toHaveBeenCalledWith( 'config', { buildNum, @@ -103,7 +103,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { [chance.word()]: chance.sentence(), }; - getUpgradeableConfig.resolves({ + getUpgradeableConfig.mockResolvedValue({ id: prevVersion, attributes: savedAttributes, type: '', @@ -112,10 +112,9 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { await run(); - sinon.assert.calledOnce(getUpgradeableConfig); - sinon.assert.calledOnce(savedObjectsClient.create); - sinon.assert.calledWithExactly( - savedObjectsClient.create, + expect(getUpgradeableConfig).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.create).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.create).toHaveBeenCalledWith( 'config', { ...savedAttributes, @@ -130,7 +129,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { it('should log a message for upgrades', async () => { const { getUpgradeableConfig, logger, run } = setup(); - getUpgradeableConfig.resolves({ + getUpgradeableConfig.mockResolvedValue({ id: prevVersion, attributes: { buildNum: buildNum - 100 }, type: '', @@ -154,16 +153,14 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { it('does not log when upgrade fails', async () => { const { getUpgradeableConfig, logger, run, savedObjectsClient } = setup(); - getUpgradeableConfig.resolves({ + getUpgradeableConfig.mockResolvedValue({ id: prevVersion, attributes: { buildNum: buildNum - 100 }, type: '', references: [], }); - savedObjectsClient.create.callsFake(async () => { - throw new Error('foo'); - }); + savedObjectsClient.create.mockRejectedValue(new Error('foo')); try { await run(); @@ -181,9 +178,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { it('throws write errors', async () => { const { run, savedObjectsClient } = setup(); const error = new Error('foo'); - savedObjectsClient.create.callsFake(async () => { - throw error; - }); + savedObjectsClient.create.mockRejectedValue(error); await expect(run({ handleWriteErrors: false })).rejects.toThrowError(error); }); @@ -192,7 +187,9 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { it('returns undefined for ConflictError', async () => { const { run, savedObjectsClient } = setup(); const error = new Error('foo'); - savedObjectsClient.create.throws(SavedObjectsErrorHelpers.decorateConflictError(error)); + savedObjectsClient.create.mockRejectedValue( + SavedObjectsErrorHelpers.decorateConflictError(error) + ); expect(await run({ handleWriteErrors: true })).toBe(undefined); }); @@ -200,7 +197,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { it('returns config attributes for NotAuthorizedError', async () => { const { run, savedObjectsClient } = setup(); const error = new Error('foo'); - savedObjectsClient.create.throws( + savedObjectsClient.create.mockRejectedValue( SavedObjectsErrorHelpers.decorateNotAuthorizedError(error) ); @@ -212,7 +209,9 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { it('returns config attributes for ForbiddenError', async () => { const { run, savedObjectsClient } = setup(); const error = new Error('foo'); - savedObjectsClient.create.throws(SavedObjectsErrorHelpers.decorateForbiddenError(error)); + savedObjectsClient.create.mockRejectedValue( + SavedObjectsErrorHelpers.decorateForbiddenError(error) + ); expect(await run({ handleWriteErrors: true })).toEqual({ buildNum, @@ -222,7 +221,9 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { it('throws error for other SavedObjects exceptions', async () => { const { run, savedObjectsClient } = setup(); const error = new Error('foo'); - savedObjectsClient.create.throws(SavedObjectsErrorHelpers.decorateGeneralError(error)); + savedObjectsClient.create.mockRejectedValue( + SavedObjectsErrorHelpers.decorateGeneralError(error) + ); await expect(run({ handleWriteErrors: true })).rejects.toThrowError(error); }); @@ -230,7 +231,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { it('throws error for all other exceptions', async () => { const { run, savedObjectsClient } = setup(); const error = new Error('foo'); - savedObjectsClient.create.throws(error); + savedObjectsClient.create.mockRejectedValue(error); await expect(run({ handleWriteErrors: true })).rejects.toThrowError(error); }); diff --git a/src/core/server/ui_settings/integration_tests/lib/assert.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/get_upgradeable_config.test.mock.ts similarity index 81% rename from src/core/server/ui_settings/integration_tests/lib/assert.ts rename to src/core/server/ui_settings/create_or_upgrade_saved_config/get_upgradeable_config.test.mock.ts index 62533b7ae734da..f849b6bf5cdfa4 100644 --- a/src/core/server/ui_settings/integration_tests/lib/assert.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/get_upgradeable_config.test.mock.ts @@ -17,10 +17,7 @@ * under the License. */ -import sinon from 'sinon'; - -export function assertSinonMatch(value: any, match: any) { - const stub = sinon.stub(); - stub(value); - sinon.assert.calledWithExactly(stub, match); -} +export const getUpgradeableConfigMock = jest.fn(); +jest.doMock('./get_upgradeable_config', () => ({ + getUpgradeableConfig: getUpgradeableConfigMock, +})); diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts index 9d52a339ccf91b..f7dbf992e87286 100644 --- a/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/integration_tests/create_or_upgrade.test.ts @@ -17,7 +17,6 @@ * under the License. */ -import expect from '@kbn/expect'; import { SavedObjectsClientContract } from 'src/core/server'; import { @@ -97,16 +96,14 @@ describe('createOrUpgradeSavedConfig()', () => { }); const config540 = await savedObjectsClient.get('config', '5.4.0'); - expect(config540) - .to.have.property('attributes') - .eql({ - // should have the new build number - buildNum: 54099, + expect(config540.attributes).toEqual({ + // should have the new build number + buildNum: 54099, - // 5.4.0-SNAPSHOT and @@version were ignored so we only have the - // attributes from 5.4.0-rc1, even though the other build nums are greater - '5.4.0-rc1': true, - }); + // 5.4.0-SNAPSHOT and @@version were ignored so we only have the + // attributes from 5.4.0-rc1, even though the other build nums are greater + '5.4.0-rc1': true, + }); // add the 5.4.0 flag to the 5.4.0 savedConfig await savedObjectsClient.update('config', '5.4.0', { @@ -124,16 +121,14 @@ describe('createOrUpgradeSavedConfig()', () => { }); const config541 = await savedObjectsClient.get('config', '5.4.1'); - expect(config541) - .to.have.property('attributes') - .eql({ - // should have the new build number - buildNum: 54199, + expect(config541.attributes).toEqual({ + // should have the new build number + buildNum: 54199, - // should also include properties from 5.4.0 and 5.4.0-rc1 - '5.4.0': true, - '5.4.0-rc1': true, - }); + // should also include properties from 5.4.0 and 5.4.0-rc1 + '5.4.0': true, + '5.4.0-rc1': true, + }); // add the 5.4.1 flag to the 5.4.1 savedConfig await savedObjectsClient.update('config', '5.4.1', { @@ -151,17 +146,15 @@ describe('createOrUpgradeSavedConfig()', () => { }); const config700rc1 = await savedObjectsClient.get('config', '7.0.0-rc1'); - expect(config700rc1) - .to.have.property('attributes') - .eql({ - // should have the new build number - buildNum: 70010, - - // should also include properties from 5.4.1, 5.4.0 and 5.4.0-rc1 - '5.4.1': true, - '5.4.0': true, - '5.4.0-rc1': true, - }); + expect(config700rc1.attributes).toEqual({ + // should have the new build number + buildNum: 70010, + + // should also include properties from 5.4.1, 5.4.0 and 5.4.0-rc1 + '5.4.1': true, + '5.4.0': true, + '5.4.0-rc1': true, + }); // tag the 7.0.0-rc1 doc await savedObjectsClient.update('config', '7.0.0-rc1', { @@ -179,18 +172,16 @@ describe('createOrUpgradeSavedConfig()', () => { }); const config700 = await savedObjectsClient.get('config', '7.0.0'); - expect(config700) - .to.have.property('attributes') - .eql({ - // should have the new build number - buildNum: 70099, - - // should also include properties from ancestors, including 7.0.0-rc1 - '7.0.0-rc1': true, - '5.4.1': true, - '5.4.0': true, - '5.4.0-rc1': true, - }); + expect(config700.attributes).toEqual({ + // should have the new build number + buildNum: 70099, + + // should also include properties from ancestors, including 7.0.0-rc1 + '7.0.0-rc1': true, + '5.4.1': true, + '5.4.0': true, + '5.4.0-rc1': true, + }); // tag the 7.0.0 doc await savedObjectsClient.update('config', '7.0.0', { @@ -208,16 +199,14 @@ describe('createOrUpgradeSavedConfig()', () => { }); const config623rc1 = await savedObjectsClient.get('config', '6.2.3-rc1'); - expect(config623rc1) - .to.have.property('attributes') - .eql({ - // should have the new build number - buildNum: 62310, - - // should also include properties from ancestors, but not 7.0.0-rc1 or 7.0.0 - '5.4.1': true, - '5.4.0': true, - '5.4.0-rc1': true, - }); + expect(config623rc1.attributes).toEqual({ + // should have the new build number + buildNum: 62310, + + // should also include properties from ancestors, but not 7.0.0-rc1 or 7.0.0 + '5.4.1': true, + '5.4.0': true, + '5.4.0-rc1': true, + }); }); }); diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts index 073a6961fdec46..feb63817fe073a 100644 --- a/src/core/server/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts @@ -17,14 +17,12 @@ * under the License. */ -import expect from '@kbn/expect'; - import { isConfigVersionUpgradeable } from './is_config_version_upgradeable'; describe('savedObjects/health_check/isConfigVersionUpgradeable', function() { function isUpgradeableTest(savedVersion: string, kibanaVersion: string, expected: boolean) { it(`should return ${expected} for config version ${savedVersion} and kibana version ${kibanaVersion}`, () => { - expect(isConfigVersionUpgradeable(savedVersion, kibanaVersion)).to.be(expected); + expect(isConfigVersionUpgradeable(savedVersion, kibanaVersion)).toBe(expected); }); } diff --git a/src/core/server/ui_settings/integration_tests/doc_exists.ts b/src/core/server/ui_settings/integration_tests/doc_exists.ts index 7783fd99769634..031464c7fdaff4 100644 --- a/src/core/server/ui_settings/integration_tests/doc_exists.ts +++ b/src/core/server/ui_settings/integration_tests/doc_exists.ts @@ -17,10 +17,7 @@ * under the License. */ -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -import { getServices, chance, assertSinonMatch } from './lib'; +import { getServices, chance } from './lib'; export function docExistsSuite() { async function setup(options: any = {}) { @@ -58,11 +55,11 @@ export function docExistsSuite() { url: '/api/kibana/settings', }); - expect(statusCode).to.be(200); - assertSinonMatch(result, { + expect(statusCode).toBe(200); + expect(result).toMatchObject({ settings: { buildNum: { - userValue: sinon.match.number, + userValue: expect.any(Number), }, defaultIndex: { userValue: defaultIndex, @@ -89,11 +86,12 @@ export function docExistsSuite() { }, }); - expect(statusCode).to.be(200); - assertSinonMatch(result, { + expect(statusCode).toBe(200); + + expect(result).toMatchObject({ settings: { buildNum: { - userValue: sinon.match.number, + userValue: expect.any(Number), }, defaultIndex: { userValue: defaultIndex, @@ -117,8 +115,8 @@ export function docExistsSuite() { }, }); - expect(statusCode).to.be(400); - assertSinonMatch(result, { + expect(statusCode).toBe(400); + expect(result).toEqual({ error: 'Bad Request', message: 'Unable to update "foo" because it is overridden', statusCode: 400, @@ -141,11 +139,12 @@ export function docExistsSuite() { }, }); - expect(statusCode).to.be(200); - assertSinonMatch(result, { + expect(statusCode).toBe(200); + + expect(result).toMatchObject({ settings: { buildNum: { - userValue: sinon.match.number, + userValue: expect.any(Number), }, defaultIndex: { userValue: defaultIndex, @@ -171,8 +170,8 @@ export function docExistsSuite() { }, }); - expect(statusCode).to.be(400); - assertSinonMatch(result, { + expect(statusCode).toBe(400); + expect(result).toEqual({ error: 'Bad Request', message: 'Unable to update "foo" because it is overridden', statusCode: 400, @@ -188,18 +187,18 @@ export function docExistsSuite() { initialSettings: { defaultIndex }, }); - expect(await uiSettings.get('defaultIndex')).to.be(defaultIndex); + expect(await uiSettings.get('defaultIndex')).toBe(defaultIndex); const { statusCode, result } = await kbnServer.inject({ method: 'DELETE', url: '/api/kibana/settings/defaultIndex', }); - expect(statusCode).to.be(200); - assertSinonMatch(result, { + expect(statusCode).toBe(200); + expect(result).toMatchObject({ settings: { buildNum: { - userValue: sinon.match.number, + userValue: expect.any(Number), }, foo: { userValue: 'bar', @@ -216,8 +215,8 @@ export function docExistsSuite() { url: '/api/kibana/settings/foo', }); - expect(statusCode).to.be(400); - assertSinonMatch(result, { + expect(statusCode).toBe(400); + expect(result).toEqual({ error: 'Bad Request', message: 'Unable to update "foo" because it is overridden', statusCode: 400, diff --git a/src/core/server/ui_settings/integration_tests/doc_missing.ts b/src/core/server/ui_settings/integration_tests/doc_missing.ts index 580fe04b920878..f535f237c11de2 100644 --- a/src/core/server/ui_settings/integration_tests/doc_missing.ts +++ b/src/core/server/ui_settings/integration_tests/doc_missing.ts @@ -17,10 +17,7 @@ * under the License. */ -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -import { getServices, chance, assertSinonMatch } from './lib'; +import { getServices, chance } from './lib'; export function docMissingSuite() { // ensure the kibana index has no documents @@ -52,11 +49,11 @@ export function docMissingSuite() { url: '/api/kibana/settings', }); - expect(statusCode).to.be(200); - assertSinonMatch(result, { + expect(statusCode).toBe(200); + expect(result).toMatchObject({ settings: { buildNum: { - userValue: sinon.match.number, + userValue: expect.any(Number), }, foo: { userValue: 'bar', @@ -78,11 +75,11 @@ export function docMissingSuite() { payload: { value: defaultIndex }, }); - expect(statusCode).to.be(200); - assertSinonMatch(result, { + expect(statusCode).toBe(200); + expect(result).toMatchObject({ settings: { buildNum: { - userValue: sinon.match.number, + userValue: expect.any(Number), }, defaultIndex: { userValue: defaultIndex, @@ -109,11 +106,11 @@ export function docMissingSuite() { }, }); - expect(statusCode).to.be(200); - assertSinonMatch(result, { + expect(statusCode).toBe(200); + expect(result).toMatchObject({ settings: { buildNum: { - userValue: sinon.match.number, + userValue: expect.any(Number), }, defaultIndex: { userValue: defaultIndex, @@ -136,11 +133,11 @@ export function docMissingSuite() { url: '/api/kibana/settings/defaultIndex', }); - expect(statusCode).to.be(200); - assertSinonMatch(result, { + expect(statusCode).toBe(200); + expect(result).toMatchObject({ settings: { buildNum: { - userValue: sinon.match.number, + userValue: expect.any(Number), }, foo: { userValue: 'bar', diff --git a/src/core/server/ui_settings/integration_tests/doc_missing_and_index_read_only.ts b/src/core/server/ui_settings/integration_tests/doc_missing_and_index_read_only.ts index 1a17970081d9cc..5ac83aee52a656 100644 --- a/src/core/server/ui_settings/integration_tests/doc_missing_and_index_read_only.ts +++ b/src/core/server/ui_settings/integration_tests/doc_missing_and_index_read_only.ts @@ -17,10 +17,7 @@ * under the License. */ -import expect from '@kbn/expect'; -import sinon from 'sinon'; - -import { getServices, chance, assertSinonMatch } from './lib'; +import { getServices, chance } from './lib'; export function docMissingAndIndexReadOnlySuite() { // ensure the kibana index has no documents @@ -80,11 +77,12 @@ export function docMissingAndIndexReadOnlySuite() { url: '/api/kibana/settings', }); - expect(statusCode).to.be(200); - assertSinonMatch(result, { + expect(statusCode).toBe(200); + + expect(result).toMatchObject({ settings: { buildNum: { - userValue: sinon.match.number, + userValue: expect.any(Number), }, foo: { userValue: 'bar', @@ -106,10 +104,11 @@ export function docMissingAndIndexReadOnlySuite() { payload: { value: defaultIndex }, }); - expect(statusCode).to.be(403); - assertSinonMatch(result, { + expect(statusCode).toBe(403); + + expect(result).toEqual({ error: 'Forbidden', - message: sinon.match('index read-only'), + message: expect.stringContaining('index read-only'), statusCode: 403, }); }); @@ -128,10 +127,10 @@ export function docMissingAndIndexReadOnlySuite() { }, }); - expect(statusCode).to.be(403); - assertSinonMatch(result, { + expect(statusCode).toBe(403); + expect(result).toEqual({ error: 'Forbidden', - message: sinon.match('index read-only'), + message: expect.stringContaining('index read-only'), statusCode: 403, }); }); @@ -146,10 +145,10 @@ export function docMissingAndIndexReadOnlySuite() { url: '/api/kibana/settings/defaultIndex', }); - expect(statusCode).to.be(403); - assertSinonMatch(result, { + expect(statusCode).toBe(403); + expect(result).toEqual({ error: 'Forbidden', - message: sinon.match('index read-only'), + message: expect.stringContaining('index read-only'), statusCode: 403, }); }); diff --git a/src/core/server/ui_settings/integration_tests/lib/index.ts b/src/core/server/ui_settings/integration_tests/lib/index.ts index 33a1cbd4d780b4..b8349e5e41ccbb 100644 --- a/src/core/server/ui_settings/integration_tests/lib/index.ts +++ b/src/core/server/ui_settings/integration_tests/lib/index.ts @@ -20,5 +20,3 @@ export { startServers, getServices, stopServers } from './servers'; export { chance } from './chance'; - -export { assertSinonMatch } from './assert'; diff --git a/src/core/server/ui_settings/ui_settings_client.test.ts b/src/core/server/ui_settings/ui_settings_client.test.ts index 1c99637a89fed3..b8aa57291dccf6 100644 --- a/src/core/server/ui_settings/ui_settings_client.test.ts +++ b/src/core/server/ui_settings/ui_settings_client.test.ts @@ -17,16 +17,15 @@ * under the License. */ -import expect from '@kbn/expect'; import Chance from 'chance'; -import sinon from 'sinon'; import { loggingServiceMock } from '../logging/logging_service.mock'; +import { createOrUpgradeSavedConfigMock } from './create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.mock'; +import { SavedObjectsClient } from '../saved_objects'; +import { savedObjectsClientMock } from '../saved_objects/service/saved_objects_client.mock'; import { UiSettingsClient } from './ui_settings_client'; import { CannotOverrideError } from './ui_settings_errors'; -import * as createOrUpgradeSavedConfigNS from './create_or_upgrade_saved_config/create_or_upgrade_saved_config'; -import { createObjectsClientStub, savedObjectsClientErrors } from './create_objects_client_stub'; const logger = loggingServiceMock.create().get(); @@ -42,12 +41,11 @@ interface SetupOptions { } describe('ui settings', () => { - const sandbox = sinon.createSandbox(); - function setup(options: SetupOptions = {}) { const { defaults = {}, overrides = {}, esDocSource = {} } = options; - const savedObjectsClient = createObjectsClientStub(esDocSource); + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.get.mockReturnValue({ attributes: esDocSource } as any); const uiSettings = new UiSettingsClient({ type: TYPE, @@ -59,92 +57,74 @@ describe('ui settings', () => { log: logger, }); - const createOrUpgradeSavedConfig = sandbox.stub( - createOrUpgradeSavedConfigNS, - 'createOrUpgradeSavedConfig' - ); - - function assertGetQuery() { - sinon.assert.calledOnce(savedObjectsClient.get); - - const { args } = savedObjectsClient.get.getCall(0); - expect(args[0]).to.be(TYPE); - expect(args[1]).to.eql(ID); - } - - function assertUpdateQuery(expectedChanges: unknown) { - sinon.assert.calledOnce(savedObjectsClient.update); - - const { args } = savedObjectsClient.update.getCall(0); - expect(args[0]).to.be(TYPE); - expect(args[1]).to.eql(ID); - expect(args[2]).to.eql(expectedChanges); - } + const createOrUpgradeSavedConfig = createOrUpgradeSavedConfigMock; return { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig, - assertGetQuery, - assertUpdateQuery, }; } - afterEach(() => sandbox.restore()); + afterEach(() => jest.clearAllMocks()); describe('#setMany()', () => { it('returns a promise', () => { const { uiSettings } = setup(); - expect(uiSettings.setMany({ a: 'b' })).to.be.a(Promise); + expect(uiSettings.setMany({ a: 'b' })).toBeInstanceOf(Promise); }); it('updates a single value in one operation', async () => { - const { uiSettings, assertUpdateQuery } = setup(); + const { uiSettings, savedObjectsClient } = setup(); await uiSettings.setMany({ one: 'value' }); - assertUpdateQuery({ one: 'value' }); + + expect(savedObjectsClient.update).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.update).toHaveBeenCalledWith(TYPE, ID, { one: 'value' }); }); it('updates several values in one operation', async () => { - const { uiSettings, assertUpdateQuery } = setup(); + const { uiSettings, savedObjectsClient } = setup(); await uiSettings.setMany({ one: 'value', another: 'val' }); - assertUpdateQuery({ one: 'value', another: 'val' }); + + expect(savedObjectsClient.update).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.update).toHaveBeenCalledWith(TYPE, ID, { + one: 'value', + another: 'val', + }); }); it('automatically creates the savedConfig if it is missing', async () => { const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup(); savedObjectsClient.update - .onFirstCall() - .throws(savedObjectsClientErrors.createGenericNotFoundError()) - .onSecondCall() - .returns({}); + .mockRejectedValueOnce(SavedObjectsClient.errors.createGenericNotFoundError()) + .mockResolvedValueOnce({} as any); await uiSettings.setMany({ foo: 'bar' }); - sinon.assert.calledTwice(savedObjectsClient.update); - sinon.assert.calledOnce(createOrUpgradeSavedConfig); - sinon.assert.calledWith( - createOrUpgradeSavedConfig, - sinon.match({ handleWriteErrors: false }) + expect(savedObjectsClient.update).toHaveBeenCalledTimes(2); + expect(createOrUpgradeSavedConfig).toHaveBeenCalledTimes(1); + expect(createOrUpgradeSavedConfig).toHaveBeenCalledWith( + expect.objectContaining({ handleWriteErrors: false }) ); }); it('only tried to auto create once and throws NotFound', async () => { const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup(); - savedObjectsClient.update.throws(savedObjectsClientErrors.createGenericNotFoundError()); + savedObjectsClient.update.mockRejectedValue( + SavedObjectsClient.errors.createGenericNotFoundError() + ); try { await uiSettings.setMany({ foo: 'bar' }); throw new Error('expected setMany to throw a NotFound error'); } catch (error) { - expect(savedObjectsClientErrors.isNotFoundError(error)).to.be(true); + expect(SavedObjectsClient.errors.isNotFoundError(error)).toBe(true); } - sinon.assert.calledTwice(savedObjectsClient.update); - sinon.assert.calledOnce(createOrUpgradeSavedConfig); - - sinon.assert.calledWith( - createOrUpgradeSavedConfig, - sinon.match({ handleWriteErrors: false }) + expect(savedObjectsClient.update).toHaveBeenCalledTimes(2); + expect(createOrUpgradeSavedConfig).toHaveBeenCalledTimes(1); + expect(createOrUpgradeSavedConfig).toHaveBeenCalledWith( + expect.objectContaining({ handleWriteErrors: false }) ); }); @@ -161,8 +141,8 @@ describe('ui settings', () => { foo: 'baz', }); } catch (error) { - expect(error).to.be.a(CannotOverrideError); - expect(error.message).to.be('Unable to update "foo" because it is overridden'); + expect(error).toBeInstanceOf(CannotOverrideError); + expect(error.message).toBe('Unable to update "foo" because it is overridden'); } }); }); @@ -170,13 +150,17 @@ describe('ui settings', () => { describe('#set()', () => { it('returns a promise', () => { const { uiSettings } = setup(); - expect(uiSettings.set('a', 'b')).to.be.a(Promise); + expect(uiSettings.set('a', 'b')).toBeInstanceOf(Promise); }); it('updates single values by (key, value)', async () => { - const { uiSettings, assertUpdateQuery } = setup(); + const { uiSettings, savedObjectsClient } = setup(); await uiSettings.set('one', 'value'); - assertUpdateQuery({ one: 'value' }); + + expect(savedObjectsClient.update).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.update).toHaveBeenCalledWith(TYPE, ID, { + one: 'value', + }); }); it('throws CannotOverrideError if the key is overridden', async () => { @@ -189,8 +173,8 @@ describe('ui settings', () => { try { await uiSettings.set('foo', 'baz'); } catch (error) { - expect(error).to.be.a(CannotOverrideError); - expect(error.message).to.be('Unable to update "foo" because it is overridden'); + expect(error).toBeInstanceOf(CannotOverrideError); + expect(error.message).toBe('Unable to update "foo" because it is overridden'); } }); }); @@ -198,13 +182,15 @@ describe('ui settings', () => { describe('#remove()', () => { it('returns a promise', () => { const { uiSettings } = setup(); - expect(uiSettings.remove('one')).to.be.a(Promise); + expect(uiSettings.remove('one')).toBeInstanceOf(Promise); }); it('removes single values by key', async () => { - const { uiSettings, assertUpdateQuery } = setup(); + const { uiSettings, savedObjectsClient } = setup(); await uiSettings.remove('one'); - assertUpdateQuery({ one: null }); + + expect(savedObjectsClient.update).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.update).toHaveBeenCalledWith(TYPE, ID, { one: null }); }); it('throws CannotOverrideError if the key is overridden', async () => { @@ -217,8 +203,8 @@ describe('ui settings', () => { try { await uiSettings.remove('foo'); } catch (error) { - expect(error).to.be.a(CannotOverrideError); - expect(error.message).to.be('Unable to update "foo" because it is overridden'); + expect(error).toBeInstanceOf(CannotOverrideError); + expect(error.message).toBe('Unable to update "foo" because it is overridden'); } }); }); @@ -226,19 +212,27 @@ describe('ui settings', () => { describe('#removeMany()', () => { it('returns a promise', () => { const { uiSettings } = setup(); - expect(uiSettings.removeMany(['one'])).to.be.a(Promise); + expect(uiSettings.removeMany(['one'])).toBeInstanceOf(Promise); }); it('removes a single value', async () => { - const { uiSettings, assertUpdateQuery } = setup(); + const { uiSettings, savedObjectsClient } = setup(); await uiSettings.removeMany(['one']); - assertUpdateQuery({ one: null }); + + expect(savedObjectsClient.update).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.update).toHaveBeenCalledWith(TYPE, ID, { one: null }); }); it('updates several values in one operation', async () => { - const { uiSettings, assertUpdateQuery } = setup(); + const { uiSettings, savedObjectsClient } = setup(); await uiSettings.removeMany(['one', 'two', 'three']); - assertUpdateQuery({ one: null, two: null, three: null }); + + expect(savedObjectsClient.update).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.update).toHaveBeenCalledWith(TYPE, ID, { + one: null, + two: null, + three: null, + }); }); it('throws CannotOverrideError if any key is overridden', async () => { @@ -251,8 +245,8 @@ describe('ui settings', () => { try { await uiSettings.setMany({ baz: 'baz', foo: 'foo' }); } catch (error) { - expect(error).to.be.a(CannotOverrideError); - expect(error.message).to.be('Unable to update "foo" because it is overridden'); + expect(error).toBeInstanceOf(CannotOverrideError); + expect(error.message).toBe('Unable to update "foo" because it is overridden'); } }); }); @@ -262,22 +256,25 @@ describe('ui settings', () => { const value = chance.word(); const defaults = { key: { value } }; const { uiSettings } = setup({ defaults }); - expect(uiSettings.getRegistered()).to.be(defaults); + expect(uiSettings.getRegistered()).toBe(defaults); }); }); describe('#getUserProvided()', () => { it('pulls user configuration from ES', async () => { - const { uiSettings, assertGetQuery } = setup(); + const { uiSettings, savedObjectsClient } = setup(); await uiSettings.getUserProvided(); - assertGetQuery(); + + expect(savedObjectsClient.get).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.get).toHaveBeenCalledWith(TYPE, ID); }); it('returns user configuration', async () => { const esDocSource = { user: 'customized' }; const { uiSettings } = setup({ esDocSource }); const result = await uiSettings.getUserProvided(); - expect(result).to.eql({ + + expect(result).toEqual({ user: { userValue: 'customized', }, @@ -288,7 +285,8 @@ describe('ui settings', () => { const esDocSource = { user: 'customized', usingDefault: null, something: 'else' }; const { uiSettings } = setup({ esDocSource }); const result = await uiSettings.getUserProvided(); - expect(result).to.eql({ + + expect(result).toEqual({ user: { userValue: 'customized', }, @@ -300,59 +298,62 @@ describe('ui settings', () => { it('automatically creates the savedConfig if it is missing and returns empty object', async () => { const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup(); - savedObjectsClient.get - .onFirstCall() - .throws(savedObjectsClientErrors.createGenericNotFoundError()) - .onSecondCall() - .returns({ attributes: {} }); + savedObjectsClient.get = jest + .fn() + .mockRejectedValueOnce(SavedObjectsClient.errors.createGenericNotFoundError()) + .mockResolvedValueOnce({ attributes: {} }); - expect(await uiSettings.getUserProvided()).to.eql({}); + expect(await uiSettings.getUserProvided()).toEqual({}); - sinon.assert.calledTwice(savedObjectsClient.get); + expect(savedObjectsClient.get).toHaveBeenCalledTimes(2); - sinon.assert.calledOnce(createOrUpgradeSavedConfig); - sinon.assert.calledWith(createOrUpgradeSavedConfig, sinon.match({ handleWriteErrors: true })); + expect(createOrUpgradeSavedConfig).toHaveBeenCalledTimes(1); + expect(createOrUpgradeSavedConfig).toHaveBeenCalledWith( + expect.objectContaining({ handleWriteErrors: true }) + ); }); it('returns result of savedConfig creation in case of notFound error', async () => { const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup(); - createOrUpgradeSavedConfig.resolves({ foo: 'bar ' }); - savedObjectsClient.get.throws(savedObjectsClientErrors.createGenericNotFoundError()); + createOrUpgradeSavedConfig.mockResolvedValue({ foo: 'bar ' }); + savedObjectsClient.get.mockRejectedValue( + SavedObjectsClient.errors.createGenericNotFoundError() + ); - expect(await uiSettings.getUserProvided()).to.eql({ foo: { userValue: 'bar ' } }); + expect(await uiSettings.getUserProvided()).toEqual({ foo: { userValue: 'bar ' } }); }); it('returns an empty object on Forbidden responses', async () => { const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup(); - const error = savedObjectsClientErrors.decorateForbiddenError(new Error()); - savedObjectsClient.get.throws(error); + const error = SavedObjectsClient.errors.decorateForbiddenError(new Error()); + savedObjectsClient.get.mockRejectedValue(error); - expect(await uiSettings.getUserProvided()).to.eql({}); - sinon.assert.notCalled(createOrUpgradeSavedConfig); + expect(await uiSettings.getUserProvided()).toEqual({}); + expect(createOrUpgradeSavedConfig).toHaveBeenCalledTimes(0); }); it('returns an empty object on EsUnavailable responses', async () => { const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup(); - const error = savedObjectsClientErrors.decorateEsUnavailableError(new Error()); - savedObjectsClient.get.throws(error); + const error = SavedObjectsClient.errors.decorateEsUnavailableError(new Error()); + savedObjectsClient.get.mockRejectedValue(error); - expect(await uiSettings.getUserProvided()).to.eql({}); - sinon.assert.notCalled(createOrUpgradeSavedConfig); + expect(await uiSettings.getUserProvided()).toEqual({}); + expect(createOrUpgradeSavedConfig).toHaveBeenCalledTimes(0); }); it('throws Unauthorized errors', async () => { const { uiSettings, savedObjectsClient } = setup(); - const error = savedObjectsClientErrors.decorateNotAuthorizedError(new Error()); - savedObjectsClient.get.throws(error); + const error = SavedObjectsClient.errors.decorateNotAuthorizedError(new Error()); + savedObjectsClient.get.mockRejectedValue(error); try { await uiSettings.getUserProvided(); throw new Error('expect getUserProvided() to throw'); } catch (err) { - expect(err).to.be(error); + expect(err).toBe(error); } }); @@ -360,13 +361,13 @@ describe('ui settings', () => { const { uiSettings, savedObjectsClient } = setup(); const error = new Error('unexpected'); - savedObjectsClient.get.throws(error); + savedObjectsClient.get.mockRejectedValue(error); try { await uiSettings.getUserProvided(); throw new Error('expect getUserProvided() to throw'); } catch (err) { - expect(err).to.be(error); + expect(err).toBe(error); } }); @@ -381,7 +382,7 @@ describe('ui settings', () => { }; const { uiSettings } = setup({ esDocSource, overrides }); - expect(await uiSettings.getUserProvided()).to.eql({ + expect(await uiSettings.getUserProvided()).toEqual({ user: { userValue: 'customized', }, @@ -397,16 +398,17 @@ describe('ui settings', () => { describe('#getAll()', () => { it('pulls user configuration from ES', async () => { const esDocSource = {}; - const { uiSettings, assertGetQuery } = setup({ esDocSource }); + const { uiSettings, savedObjectsClient } = setup({ esDocSource }); await uiSettings.getAll(); - assertGetQuery(); + expect(savedObjectsClient.get).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.get).toHaveBeenCalledWith(TYPE, ID); }); it(`returns defaults when es doc is empty`, async () => { const esDocSource = {}; const defaults = { foo: { value: 'bar' } }; const { uiSettings } = setup({ esDocSource, defaults }); - expect(await uiSettings.getAll()).to.eql({ + expect(await uiSettings.getAll()).toEqual({ foo: 'bar', }); }); @@ -424,7 +426,8 @@ describe('ui settings', () => { }; const { uiSettings } = setup({ esDocSource, defaults }); - expect(await uiSettings.getAll()).to.eql({ + + expect(await uiSettings.getAll()).toEqual({ foo: 'user-override', bar: 'user-provided', }); @@ -447,7 +450,8 @@ describe('ui settings', () => { }; const { uiSettings } = setup({ esDocSource, defaults, overrides }); - expect(await uiSettings.getAll()).to.eql({ + + expect(await uiSettings.getAll()).toEqual({ foo: 'bax', bar: 'user-provided', }); @@ -457,9 +461,11 @@ describe('ui settings', () => { describe('#get()', () => { it('pulls user configuration from ES', async () => { const esDocSource = {}; - const { uiSettings, assertGetQuery } = setup({ esDocSource }); + const { uiSettings, savedObjectsClient } = setup({ esDocSource }); await uiSettings.get('any'); - assertGetQuery(); + + expect(savedObjectsClient.get).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.get).toHaveBeenCalledWith(TYPE, ID); }); it(`returns the promised value for a key`, async () => { @@ -467,28 +473,31 @@ describe('ui settings', () => { const defaults = { dateFormat: { value: chance.word() } }; const { uiSettings } = setup({ esDocSource, defaults }); const result = await uiSettings.get('dateFormat'); - expect(result).to.equal(defaults.dateFormat.value); + + expect(result).toBe(defaults.dateFormat.value); }); it(`returns the user-configured value for a custom key`, async () => { const esDocSource = { custom: 'value' }; const { uiSettings } = setup({ esDocSource }); const result = await uiSettings.get('custom'); - expect(result).to.equal('value'); + + expect(result).toBe('value'); }); it(`returns the user-configured value for a modified key`, async () => { const esDocSource = { dateFormat: 'YYYY-MM-DD' }; const { uiSettings } = setup({ esDocSource }); const result = await uiSettings.get('dateFormat'); - expect(result).to.equal('YYYY-MM-DD'); + expect(result).toBe('YYYY-MM-DD'); }); it('returns the overridden value for an overrided key', async () => { const esDocSource = { dateFormat: 'YYYY-MM-DD' }; const overrides = { dateFormat: 'foo' }; const { uiSettings } = setup({ esDocSource, overrides }); - expect(await uiSettings.get('dateFormat')).to.be('foo'); + + expect(await uiSettings.get('dateFormat')).toBe('foo'); }); it('returns the default value for an override with value null', async () => { @@ -496,35 +505,40 @@ describe('ui settings', () => { const overrides = { dateFormat: null }; const defaults = { dateFormat: { value: 'foo' } }; const { uiSettings } = setup({ esDocSource, overrides, defaults }); - expect(await uiSettings.get('dateFormat')).to.be('foo'); + + expect(await uiSettings.get('dateFormat')).toBe('foo'); }); it('returns the overridden value if the document does not exist', async () => { const overrides = { dateFormat: 'foo' }; const { uiSettings, savedObjectsClient } = setup({ overrides }); - savedObjectsClient.get - .onFirstCall() - .throws(savedObjectsClientErrors.createGenericNotFoundError()); - expect(await uiSettings.get('dateFormat')).to.be('foo'); + savedObjectsClient.get.mockRejectedValueOnce( + SavedObjectsClient.errors.createGenericNotFoundError() + ); + + expect(await uiSettings.get('dateFormat')).toBe('foo'); }); }); describe('#isOverridden()', () => { it('returns false if no overrides defined', () => { const { uiSettings } = setup(); - expect(uiSettings.isOverridden('foo')).to.be(false); + expect(uiSettings.isOverridden('foo')).toBe(false); }); + it('returns false if overrides defined but key is not included', () => { const { uiSettings } = setup({ overrides: { foo: true, bar: true } }); - expect(uiSettings.isOverridden('baz')).to.be(false); + expect(uiSettings.isOverridden('baz')).toBe(false); }); + it('returns false for object prototype properties', () => { const { uiSettings } = setup({ overrides: { foo: true, bar: true } }); - expect(uiSettings.isOverridden('hasOwnProperty')).to.be(false); + expect(uiSettings.isOverridden('hasOwnProperty')).toBe(false); }); + it('returns true if overrides defined and key is overridden', () => { const { uiSettings } = setup({ overrides: { foo: true, bar: true } }); - expect(uiSettings.isOverridden('bar')).to.be(true); + expect(uiSettings.isOverridden('bar')).toBe(true); }); }); }); From 553f93908cb9f5955171ea4f3d02f767530915ef Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Thu, 21 Nov 2019 15:27:09 +0200 Subject: [PATCH 2/6] Move some of filter bar's utils to appropriate locations (#51162) * Move IDataPluginServices interface to NP * Move stubs to NP * Fix eslint * Move build filters to NP * getFilterParams to NP Replace getQueryDslFromFilter with esFIlters.cleanFilter * Move getDisplayValueFromFilter and getIndexPatternFromFilter to NP * Update types * Move isFilterable to NP * Fix i18n error * Fixed import of isFIlterable * code review import change * fix typo --- .../apply_filter_popover_content.tsx | 5 +- .../public/filter/filter_bar/filter_bar.tsx | 6 +- .../filter/filter_bar/filter_editor/index.tsx | 47 +++-- .../lib/filter_editor_utils.test.ts | 176 +----------------- .../filter_editor/lib/filter_editor_utils.ts | 96 ++-------- .../filter_editor/lib/filter_label.tsx | 21 +-- .../filter_editor/lib/filter_operators.ts | 19 +- .../filter_editor/phrase_suggestor.tsx | 12 +- .../filter_editor/range_value_input.tsx | 4 +- .../public/filter/filter_bar/filter_item.tsx | 8 +- src/legacy/core_plugins/data/public/index.ts | 1 - .../index_patterns/index_patterns_service.ts | 1 - .../data/public/index_patterns/utils.test.ts | 48 ----- .../data/public/index_patterns/utils.ts | 13 -- .../public/index_patterns/__mocks__/index.ts | 1 - src/legacy/ui/public/index_patterns/index.ts | 1 - .../es_query/filters/build_filter.test.ts | 131 +++++++++++++ .../common/es_query/filters/build_filters.ts | 80 ++++++++ .../filters/get_filter_params.test.ts | 43 +++++ .../es_query/filters/get_filter_params.ts | 34 ++++ .../data/common/es_query/filters/index.ts | 3 + .../es_query/utils}/get_display_value.ts | 18 +- .../get_index_pattern_from_filter.test.ts | 28 +++ .../utils/get_index_pattern_from_filter.ts | 28 +++ .../data/common/es_query/utils/index.ts | 2 + .../common/index_patterns/fields/index.ts | 1 + .../common/index_patterns/fields/utils.ts | 30 +++ .../data/common/index_patterns/utils.test.ts | 60 ++++++ .../filter_manager/lib/generate_filters.ts | 22 ++- .../autocomplete_providers/__tests__/field.js | 2 +- .../public/autocomplete_providers/field.js | 2 +- 31 files changed, 537 insertions(+), 406 deletions(-) create mode 100644 src/plugins/data/common/es_query/filters/build_filter.test.ts create mode 100644 src/plugins/data/common/es_query/filters/build_filters.ts create mode 100644 src/plugins/data/common/es_query/filters/get_filter_params.test.ts create mode 100644 src/plugins/data/common/es_query/filters/get_filter_params.ts rename src/{legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib => plugins/data/common/es_query/utils}/get_display_value.ts (69%) create mode 100644 src/plugins/data/common/es_query/utils/get_index_pattern_from_filter.test.ts create mode 100644 src/plugins/data/common/es_query/utils/get_index_pattern_from_filter.ts create mode 100644 src/plugins/data/common/index_patterns/fields/utils.ts create mode 100644 src/plugins/data/common/index_patterns/utils.test.ts diff --git a/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filter_popover_content.tsx b/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filter_popover_content.tsx index ab52d56841612e..37d96a51d66d25 100644 --- a/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filter_popover_content.tsx +++ b/src/legacy/core_plugins/data/public/filter/apply_filters/apply_filter_popover_content.tsx @@ -32,8 +32,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component } from 'react'; import { IndexPattern } from '../../index_patterns'; import { FilterLabel } from '../filter_bar/filter_editor/lib/filter_label'; -import { mapAndFlattenFilters, esFilters } from '../../../../../../plugins/data/public'; -import { getDisplayValueFromFilter } from '../filter_bar/filter_editor/lib/get_display_value'; +import { mapAndFlattenFilters, esFilters, utils } from '../../../../../../plugins/data/public'; interface Props { filters: esFilters.Filter[]; @@ -58,7 +57,7 @@ export class ApplyFiltersPopoverContent extends Component { }; } private getLabel(filter: esFilters.Filter) { - const valueLabel = getDisplayValueFromFilter(filter, this.props.indexPatterns); + const valueLabel = utils.getDisplayValueFromFilter(filter, this.props.indexPatterns); return ; } diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx index 23c9c9ffc94bbe..e80bffb5e3c68e 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_bar.tsx @@ -21,18 +21,18 @@ import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPopover } from '@elastic/ import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import classNames from 'classnames'; import React, { useState } from 'react'; -import { IndexPattern } from '../../index_patterns'; + import { FilterEditor } from './filter_editor'; import { FilterItem } from './filter_item'; import { FilterOptions } from './filter_options'; import { useKibana } from '../../../../../../plugins/kibana_react/public'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { IIndexPattern, esFilters } from '../../../../../../plugins/data/public'; interface Props { filters: esFilters.Filter[]; onFiltersUpdated?: (filters: esFilters.Filter[]) => void; className: string; - indexPatterns: IndexPattern[]; + indexPatterns: IIndexPattern[]; intl: InjectedIntl; } diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/index.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/index.tsx index 84da576e8205cd..4f9424f30f5163 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/index.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/index.tsx @@ -36,37 +36,36 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import { get } from 'lodash'; import React, { Component } from 'react'; -import { Field, IndexPattern } from '../../../index_patterns'; import { GenericComboBox, GenericComboBoxProps } from './generic_combo_box'; import { - buildCustomFilter, - buildFilter, getFieldFromFilter, getFilterableFields, - getFilterParams, - getIndexPatternFromFilter, getOperatorFromFilter, getOperatorOptions, - getQueryDslFromFilter, isFilterValid, } from './lib/filter_editor_utils'; import { Operator } from './lib/filter_operators'; import { PhraseValueInput } from './phrase_value_input'; import { PhrasesValuesInput } from './phrases_values_input'; import { RangeValueInput } from './range_value_input'; -import { esFilters } from '../../../../../../../plugins/data/public'; +import { + esFilters, + utils, + IIndexPattern, + IFieldType, +} from '../../../../../../../plugins/data/public'; interface Props { filter: esFilters.Filter; - indexPatterns: IndexPattern[]; + indexPatterns: IIndexPattern[]; onSubmit: (filter: esFilters.Filter) => void; onCancel: () => void; intl: InjectedIntl; } interface State { - selectedIndexPattern?: IndexPattern; - selectedField?: Field; + selectedIndexPattern?: IIndexPattern; + selectedField?: IFieldType; selectedOperator?: Operator; params: any; useCustomLabel: boolean; @@ -82,10 +81,10 @@ class FilterEditorUI extends Component { selectedIndexPattern: this.getIndexPatternFromFilter(), selectedField: this.getFieldFromFilter(), selectedOperator: this.getSelectedOperator(), - params: getFilterParams(props.filter), + params: esFilters.getFilterParams(props.filter), useCustomLabel: props.filter.meta.alias !== null, customLabel: props.filter.meta.alias, - queryDsl: JSON.stringify(getQueryDslFromFilter(props.filter), null, 2), + queryDsl: JSON.stringify(esFilters.cleanFilter(props.filter), null, 2), isCustomEditorOpen: this.isUnknownFilterType(), }; } @@ -377,7 +376,7 @@ class FilterEditorUI extends Component { } private getIndexPatternFromFilter() { - return getIndexPatternFromFilter(this.props.filter, this.props.indexPatterns); + return utils.getIndexPatternFromFilter(this.props.filter, this.props.indexPatterns); } private getFieldFromFilter() { @@ -412,14 +411,14 @@ class FilterEditorUI extends Component { return isFilterValid(indexPattern, field, operator, params); } - private onIndexPatternChange = ([selectedIndexPattern]: IndexPattern[]) => { + private onIndexPatternChange = ([selectedIndexPattern]: IIndexPattern[]) => { const selectedField = undefined; const selectedOperator = undefined; const params = undefined; this.setState({ selectedIndexPattern, selectedField, selectedOperator, params }); }; - private onFieldChange = ([selectedField]: Field[]) => { + private onFieldChange = ([selectedField]: IFieldType[]) => { const selectedOperator = undefined; const params = undefined; this.setState({ selectedField, selectedOperator, params }); @@ -475,13 +474,21 @@ class FilterEditorUI extends Component { const { index, disabled, negate } = this.props.filter.meta; const newIndex = index || this.props.indexPatterns[0].id!; const body = JSON.parse(queryDsl); - const filter = buildCustomFilter(newIndex, body, disabled, negate, alias, $state.store); + const filter = esFilters.buildCustomFilter( + newIndex, + body, + disabled, + negate, + alias, + $state.store + ); this.props.onSubmit(filter); } else if (indexPattern && field && operator) { - const filter = buildFilter( + const filter = esFilters.buildFilter( indexPattern, field, - operator, + operator.type, + operator.negate, this.props.filter.meta.disabled, params, alias, @@ -492,11 +499,11 @@ class FilterEditorUI extends Component { }; } -function IndexPatternComboBox(props: GenericComboBoxProps) { +function IndexPatternComboBox(props: GenericComboBoxProps) { return GenericComboBox(props); } -function FieldComboBox(props: GenericComboBoxProps) { +function FieldComboBox(props: GenericComboBoxProps) { return GenericComboBox(props); } diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.test.ts b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.test.ts index 577861db38faf9..6dc9bc2300e044 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.test.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.test.ts @@ -28,23 +28,15 @@ import { } from '../../../../../../../../plugins/data/public/stubs'; import { IndexPattern, Field } from '../../../../index'; import { - buildFilter, getFieldFromFilter, getFilterableFields, - getFilterParams, - getIndexPatternFromFilter, getOperatorFromFilter, getOperatorOptions, - getQueryDslFromFilter, isFilterValid, } from './filter_editor_utils'; -import { - doesNotExistOperator, - existsOperator, - isBetweenOperator, - isOneOfOperator, - isOperator, -} from './filter_operators'; + +import { existsOperator, isBetweenOperator, isOneOfOperator, isOperator } from './filter_operators'; + import { esFilters } from '../../../../../../../../plugins/data/public'; jest.mock('ui/new_platform'); @@ -53,21 +45,6 @@ const mockedFields = stubFields as Field[]; const mockedIndexPattern = stubIndexPattern as IndexPattern; describe('Filter editor utils', () => { - describe('getQueryDslFromFilter', () => { - it('should return query DSL without meta and $state', () => { - const queryDsl = getQueryDslFromFilter(phraseFilter); - expect(queryDsl).not.toHaveProperty('meta'); - expect(queryDsl).not.toHaveProperty('$state'); - }); - }); - - describe('getIndexPatternFromFilter', () => { - it('should return the index pattern from the filter', () => { - const indexPattern = getIndexPatternFromFilter(phraseFilter, [mockedIndexPattern]); - expect(indexPattern).toBe(mockedIndexPattern); - }); - }); - describe('getFieldFromFilter', () => { it('should return the field from the filter', () => { const field = getFieldFromFilter(phraseFilter, mockedIndexPattern); @@ -138,28 +115,6 @@ describe('Filter editor utils', () => { }); }); - describe('getFilterParams', () => { - it('should retrieve params from phrase filter', () => { - const params = getFilterParams(phraseFilter); - expect(params).toBe('ios'); - }); - - it('should retrieve params from phrases filter', () => { - const params = getFilterParams(phrasesFilter); - expect(params).toEqual(['win xp', 'osx']); - }); - - it('should retrieve params from range filter', () => { - const params = getFilterParams(rangeFilter); - expect(params).toEqual({ from: 0, to: 10 }); - }); - - it('should return undefined for exists filter', () => { - const params = getFilterParams(existsFilter); - expect(params).toBeUndefined(); - }); - }); - describe('getFilterableFields', () => { it('returns the list of fields from the given index pattern', () => { const fieldOptions = getFilterableFields(mockedIndexPattern); @@ -245,129 +200,4 @@ describe('Filter editor utils', () => { expect(isValid).toBe(true); }); }); - - describe('buildFilter', () => { - it('should build phrase filters', () => { - const params = 'foo'; - const alias = 'bar'; - const state = esFilters.FilterStateStore.APP_STATE; - const filter = buildFilter( - mockedIndexPattern, - mockedFields[0], - isOperator, - false, - params, - alias, - state - ); - expect(filter.meta.negate).toBe(isOperator.negate); - expect(filter.meta.alias).toBe(alias); - - expect(filter.$state).toBeDefined(); - if (filter.$state) { - expect(filter.$state.store).toBe(state); - } - }); - - it('should build phrases filters', () => { - const params = ['foo', 'bar']; - const alias = 'bar'; - const state = esFilters.FilterStateStore.APP_STATE; - const filter = buildFilter( - mockedIndexPattern, - mockedFields[0], - isOneOfOperator, - false, - params, - alias, - state - ); - expect(filter.meta.type).toBe(isOneOfOperator.type); - expect(filter.meta.negate).toBe(isOneOfOperator.negate); - expect(filter.meta.alias).toBe(alias); - expect(filter.$state).toBeDefined(); - if (filter.$state) { - expect(filter.$state.store).toBe(state); - } - }); - - it('should build range filters', () => { - const params = { from: 'foo', to: 'qux' }; - const alias = 'bar'; - const state = esFilters.FilterStateStore.APP_STATE; - const filter = buildFilter( - mockedIndexPattern, - mockedFields[0], - isBetweenOperator, - false, - params, - alias, - state - ); - expect(filter.meta.negate).toBe(isBetweenOperator.negate); - expect(filter.meta.alias).toBe(alias); - expect(filter.$state).toBeDefined(); - if (filter.$state) { - expect(filter.$state.store).toBe(state); - } - }); - - it('should build exists filters', () => { - const params = undefined; - const alias = 'bar'; - const state = esFilters.FilterStateStore.APP_STATE; - const filter = buildFilter( - mockedIndexPattern, - mockedFields[0], - existsOperator, - false, - params, - alias, - state - ); - expect(filter.meta.negate).toBe(existsOperator.negate); - expect(filter.meta.alias).toBe(alias); - expect(filter.$state).toBeDefined(); - if (filter.$state) { - expect(filter.$state.store).toBe(state); - } - }); - - it('should include disabled state', () => { - const params = undefined; - const alias = 'bar'; - const state = esFilters.FilterStateStore.APP_STATE; - const filter = buildFilter( - mockedIndexPattern, - mockedFields[0], - doesNotExistOperator, - true, - params, - alias, - state - ); - expect(filter.meta.disabled).toBe(true); - }); - - it('should negate based on operator', () => { - const params = undefined; - const alias = 'bar'; - const state = esFilters.FilterStateStore.APP_STATE; - const filter = buildFilter( - mockedIndexPattern, - mockedFields[0], - doesNotExistOperator, - false, - params, - alias, - state - ); - expect(filter.meta.negate).toBe(doesNotExistOperator.negate); - expect(filter.meta.alias).toBe(alias); - expect(filter.$state).toBeDefined(); - if (filter.$state) { - expect(filter.$state.store).toBe(state); - } - }); - }); }); diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.ts b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.ts index b7d20526a6b924..e4487af42beaf4 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_editor_utils.ts @@ -18,20 +18,16 @@ */ import dateMath from '@elastic/datemath'; -import { omit } from 'lodash'; import { Ipv4Address } from '../../../../../../../../plugins/kibana_utils/public'; -import { Field, IndexPattern, isFilterable } from '../../../../index_patterns'; import { FILTER_OPERATORS, Operator } from './filter_operators'; -import { esFilters } from '../../../../../../../../plugins/data/public'; +import { + esFilters, + IIndexPattern, + IFieldType, + isFilterable, +} from '../../../../../../../../plugins/data/public'; -export function getIndexPatternFromFilter( - filter: esFilters.Filter, - indexPatterns: IndexPattern[] -): IndexPattern | undefined { - return indexPatterns.find(indexPattern => indexPattern.id === filter.meta.index); -} - -export function getFieldFromFilter(filter: esFilters.FieldFilter, indexPattern: IndexPattern) { +export function getFieldFromFilter(filter: esFilters.FieldFilter, indexPattern: IIndexPattern) { return indexPattern.fields.find(field => field.name === filter.meta.key); } @@ -41,34 +37,16 @@ export function getOperatorFromFilter(filter: esFilters.Filter) { }); } -export function getQueryDslFromFilter(filter: esFilters.Filter) { - return omit(filter, ['$state', 'meta']); -} - -export function getFilterableFields(indexPattern: IndexPattern) { +export function getFilterableFields(indexPattern: IIndexPattern) { return indexPattern.fields.filter(isFilterable); } -export function getOperatorOptions(field: Field) { +export function getOperatorOptions(field: IFieldType) { return FILTER_OPERATORS.filter(operator => { return !operator.fieldTypes || operator.fieldTypes.includes(field.type); }); } -export function getFilterParams(filter: esFilters.Filter) { - switch (filter.meta.type) { - case 'phrase': - return (filter as esFilters.PhraseFilter).meta.params.query; - case 'phrases': - return (filter as esFilters.PhrasesFilter).meta.params; - case 'range': - return { - from: (filter as esFilters.RangeFilter).meta.params.gte, - to: (filter as esFilters.RangeFilter).meta.params.lt, - }; - } -} - export function validateParams(params: any, type: string) { switch (type) { case 'date': @@ -86,8 +64,8 @@ export function validateParams(params: any, type: string) { } export function isFilterValid( - indexPattern?: IndexPattern, - field?: Field, + indexPattern?: IIndexPattern, + field?: IFieldType, operator?: Operator, params?: any ) { @@ -113,55 +91,3 @@ export function isFilterValid( throw new Error(`Unknown operator type: ${operator.type}`); } } - -export function buildFilter( - indexPattern: IndexPattern, - field: Field, - operator: Operator, - disabled: boolean, - params: any, - alias: string | null, - store: esFilters.FilterStateStore -): esFilters.Filter { - const filter = buildBaseFilter(indexPattern, field, operator, params); - filter.meta.alias = alias; - filter.meta.negate = operator.negate; - filter.meta.disabled = disabled; - filter.$state = { store }; - return filter; -} - -function buildBaseFilter( - indexPattern: IndexPattern, - field: Field, - operator: Operator, - params: any -): esFilters.Filter { - switch (operator.type) { - case 'phrase': - return esFilters.buildPhraseFilter(field, params, indexPattern); - case 'phrases': - return esFilters.buildPhrasesFilter(field, params, indexPattern); - case 'range': - const newParams = { gte: params.from, lt: params.to }; - return esFilters.buildRangeFilter(field, newParams, indexPattern); - case 'exists': - return esFilters.buildExistsFilter(field, indexPattern); - default: - throw new Error(`Unknown operator type: ${operator.type}`); - } -} - -export function buildCustomFilter( - index: string, - queryDsl: any, - disabled: boolean, - negate: boolean, - alias: string | null, - store: esFilters.FilterStateStore -): esFilters.Filter { - const meta: esFilters.FilterMeta = { index, type: 'custom', disabled, negate, alias }; - const filter: esFilters.Filter = { ...queryDsl, meta }; - filter.$state = { store }; - return filter; -} diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_label.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_label.tsx index d16158226579c6..1b4bdb881116b2 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_label.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_label.tsx @@ -51,50 +51,43 @@ export function FilterLabel({ filter, valueLabel }: Props) { } switch (filter.meta.type) { - case 'exists': + case esFilters.FILTERS.EXISTS: return ( {prefix} {filter.meta.key} {existsOperator.message} ); - case 'geo_bounding_box': + case esFilters.FILTERS.GEO_BOUNDING_BOX: return ( {prefix} {filter.meta.key}: {valueLabel} ); - case 'geo_polygon': + case esFilters.FILTERS.GEO_POLYGON: return ( {prefix} {filter.meta.key}: {valueLabel} ); - case 'phrase': - return ( - - {prefix} - {filter.meta.key}: {valueLabel} - - ); - case 'phrases': + case esFilters.FILTERS.PHRASES: return ( {prefix} {filter.meta.key} {isOneOfOperator.message} {valueLabel} ); - case 'query_string': + case esFilters.FILTERS.QUERY_STRING: return ( {prefix} {valueLabel} ); - case 'range': - case 'phrase': + case esFilters.FILTERS.PHRASE: + case esFilters.FILTERS.RANGE: return ( {prefix} diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_operators.ts b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_operators.ts index 469f5355df106f..a3da03db71d6ea 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_operators.ts +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/filter_operators.ts @@ -18,10 +18,11 @@ */ import { i18n } from '@kbn/i18n'; +import { esFilters } from '../../../../../../../../plugins/data/public'; export interface Operator { message: string; - type: string; + type: esFilters.FILTERS; negate: boolean; fieldTypes?: string[]; } @@ -30,7 +31,7 @@ export const isOperator = { message: i18n.translate('data.filter.filterEditor.isOperatorOptionLabel', { defaultMessage: 'is', }), - type: 'phrase', + type: esFilters.FILTERS.PHRASE, negate: false, }; @@ -38,7 +39,7 @@ export const isNotOperator = { message: i18n.translate('data.filter.filterEditor.isNotOperatorOptionLabel', { defaultMessage: 'is not', }), - type: 'phrase', + type: esFilters.FILTERS.PHRASE, negate: true, }; @@ -46,7 +47,7 @@ export const isOneOfOperator = { message: i18n.translate('data.filter.filterEditor.isOneOfOperatorOptionLabel', { defaultMessage: 'is one of', }), - type: 'phrases', + type: esFilters.FILTERS.PHRASES, negate: false, fieldTypes: ['string', 'number', 'date', 'ip', 'geo_point', 'geo_shape'], }; @@ -55,7 +56,7 @@ export const isNotOneOfOperator = { message: i18n.translate('data.filter.filterEditor.isNotOneOfOperatorOptionLabel', { defaultMessage: 'is not one of', }), - type: 'phrases', + type: esFilters.FILTERS.PHRASES, negate: true, fieldTypes: ['string', 'number', 'date', 'ip', 'geo_point', 'geo_shape'], }; @@ -64,7 +65,7 @@ export const isBetweenOperator = { message: i18n.translate('data.filter.filterEditor.isBetweenOperatorOptionLabel', { defaultMessage: 'is between', }), - type: 'range', + type: esFilters.FILTERS.RANGE, negate: false, fieldTypes: ['number', 'date', 'ip'], }; @@ -73,7 +74,7 @@ export const isNotBetweenOperator = { message: i18n.translate('data.filter.filterEditor.isNotBetweenOperatorOptionLabel', { defaultMessage: 'is not between', }), - type: 'range', + type: esFilters.FILTERS.RANGE, negate: true, fieldTypes: ['number', 'date', 'ip'], }; @@ -82,7 +83,7 @@ export const existsOperator = { message: i18n.translate('data.filter.filterEditor.existsOperatorOptionLabel', { defaultMessage: 'exists', }), - type: 'exists', + type: esFilters.FILTERS.EXISTS, negate: false, }; @@ -90,7 +91,7 @@ export const doesNotExistOperator = { message: i18n.translate('data.filter.filterEditor.doesNotExistOperatorOptionLabel', { defaultMessage: 'does not exist', }), - type: 'exists', + type: esFilters.FILTERS.EXISTS, negate: true, }; diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/phrase_suggestor.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/phrase_suggestor.tsx index c8b36d84f440e1..092bf8daa8f2eb 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/phrase_suggestor.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/phrase_suggestor.tsx @@ -19,17 +19,21 @@ import { Component } from 'react'; import { debounce } from 'lodash'; -import { Field, IndexPattern } from '../../../index_patterns'; import { withKibana, KibanaReactContextValue, } from '../../../../../../../plugins/kibana_react/public'; -import { IDataPluginServices } from '../../../../../../../plugins/data/public'; + +import { + IDataPluginServices, + IIndexPattern, + IFieldType, +} from '../../../../../../../plugins/data/public'; export interface PhraseSuggestorProps { kibana: KibanaReactContextValue; - indexPattern: IndexPattern; - field?: Field; + indexPattern: IIndexPattern; + field?: IFieldType; } export interface PhraseSuggestorState { diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/range_value_input.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/range_value_input.tsx index 6a5229ac826cbd..3c39a770377a0e 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/range_value_input.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/range_value_input.tsx @@ -22,7 +22,7 @@ import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import { get } from 'lodash'; import React from 'react'; import { useKibana } from '../../../../../../../plugins/kibana_react/public'; -import { Field } from '../../../index_patterns'; +import { IFieldType } from '../../../../../../../plugins/data/public'; import { ValueInputType } from './value_input_type'; interface RangeParams { @@ -33,7 +33,7 @@ interface RangeParams { type RangeParamsPartial = Partial; interface Props { - field?: Field; + field?: IFieldType; value?: RangeParams; onChange: (params: RangeParamsPartial) => void; intl: InjectedIntl; diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_item.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_item.tsx index 0dbe92dcb0da67..27406232dd5d3a 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_item.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_item.tsx @@ -22,16 +22,14 @@ import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import classNames from 'classnames'; import React, { Component } from 'react'; import { UiSettingsClientContract } from 'src/core/public'; -import { IndexPattern } from '../../index_patterns'; import { FilterEditor } from './filter_editor'; import { FilterView } from './filter_view'; -import { getDisplayValueFromFilter } from './filter_editor/lib/get_display_value'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters, utils, IIndexPattern } from '../../../../../../plugins/data/public'; interface Props { id: string; filter: esFilters.Filter; - indexPatterns: IndexPattern[]; + indexPatterns: IIndexPattern[]; className?: string; onUpdate: (filter: esFilters.Filter) => void; onRemove: () => void; @@ -62,7 +60,7 @@ class FilterItemUI extends Component { this.props.className ); - const valueLabel = getDisplayValueFromFilter(filter, this.props.indexPatterns); + const valueLabel = utils.getDisplayValueFromFilter(filter, this.props.indexPatterns); const dataTestSubjKey = filter.meta.key ? `filter-key-${filter.meta.key}` : ''; const dataTestSubjValue = filter.meta.value ? `filter-value-${valueLabel}` : ''; const dataTestSubjDisabled = `filter-${ diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 2412541e8c5c8f..ffce162cadde45 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -48,7 +48,6 @@ export { CONTAINS_SPACES, getFromSavedObject, getRoutes, - isFilterable, IndexPatternSelect, validateIndexPattern, ILLEGAL_CHARACTERS, diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts index c9c52400b1f19c..f97246bc5a9bf0 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts @@ -99,7 +99,6 @@ export { ILLEGAL_CHARACTERS, INDEX_PATTERN_ILLEGAL_CHARACTERS, INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, - isFilterable, validateIndexPattern, } from './utils'; diff --git a/src/legacy/core_plugins/data/public/index_patterns/utils.test.ts b/src/legacy/core_plugins/data/public/index_patterns/utils.test.ts index 1a186a65147633..cff48144489f05 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/utils.test.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/utils.test.ts @@ -21,19 +21,9 @@ import { CONTAINS_SPACES, ILLEGAL_CHARACTERS, INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, - isFilterable, validateIndexPattern, } from './utils'; -import { Field } from './fields'; - -const mockField = { - name: 'foo', - scripted: false, - searchable: true, - type: 'string', -} as Field; - describe('Index Pattern Utils', () => { describe('Validation', () => { it('should not allow space in the pattern', () => { @@ -52,42 +42,4 @@ describe('Index Pattern Utils', () => { expect(validateIndexPattern('my-pattern-*')).toEqual({}); }); }); - - describe('isFilterable', () => { - describe('types', () => { - it('should return true for filterable types', () => { - ['string', 'number', 'date', 'ip', 'boolean'].forEach(type => { - expect(isFilterable({ ...mockField, type })).toBe(true); - }); - }); - - it('should return false for filterable types if the field is not searchable', () => { - ['string', 'number', 'date', 'ip', 'boolean'].forEach(type => { - expect(isFilterable({ ...mockField, type, searchable: false })).toBe(false); - }); - }); - - it('should return false for un-filterable types', () => { - [ - 'geo_point', - 'geo_shape', - 'attachment', - 'murmur3', - '_source', - 'unknown', - 'conflict', - ].forEach(type => { - expect(isFilterable({ ...mockField, type })).toBe(false); - }); - }); - }); - - it('should return true for scripted fields', () => { - expect(isFilterable({ ...mockField, scripted: true, searchable: false })).toBe(true); - }); - - it('should return true for the _id field', () => { - expect(isFilterable({ ...mockField, name: '_id' })).toBe(true); - }); - }); }); diff --git a/src/legacy/core_plugins/data/public/index_patterns/utils.ts b/src/legacy/core_plugins/data/public/index_patterns/utils.ts index 1c877f4f142513..8542c1dcce24d2 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/utils.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/utils.ts @@ -19,9 +19,6 @@ import { find, get } from 'lodash'; -import { Field } from './fields'; -import { getFilterableKbnTypeNames } from '../../../../../plugins/data/public'; - import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../core/public'; export const ILLEGAL_CHARACTERS = 'ILLEGAL_CHARACTERS'; @@ -107,16 +104,6 @@ export function validateIndexPattern(indexPattern: string) { return errors; } -const filterableTypes = getFilterableKbnTypeNames(); - -export function isFilterable(field: Field): boolean { - return ( - field.name === '_id' || - field.scripted || - Boolean(field.searchable && filterableTypes.includes(field.type)) - ); -} - export function getFromSavedObject(savedObject: any) { if (get(savedObject, 'attributes.fields') === undefined) { return; diff --git a/src/legacy/ui/public/index_patterns/__mocks__/index.ts b/src/legacy/ui/public/index_patterns/__mocks__/index.ts index 2dd3f370c6d6aa..f51ae86b5c9a78 100644 --- a/src/legacy/ui/public/index_patterns/__mocks__/index.ts +++ b/src/legacy/ui/public/index_patterns/__mocks__/index.ts @@ -35,7 +35,6 @@ export { CONTAINS_SPACES, getFromSavedObject, getRoutes, - isFilterable, IndexPatternSelect, validateIndexPattern, ILLEGAL_CHARACTERS, diff --git a/src/legacy/ui/public/index_patterns/index.ts b/src/legacy/ui/public/index_patterns/index.ts index 3b4952ac815192..690a9cffaa1388 100644 --- a/src/legacy/ui/public/index_patterns/index.ts +++ b/src/legacy/ui/public/index_patterns/index.ts @@ -38,7 +38,6 @@ export { CONTAINS_SPACES, getFromSavedObject, getRoutes, - isFilterable, validateIndexPattern, ILLEGAL_CHARACTERS, INDEX_PATTERN_ILLEGAL_CHARACTERS, diff --git a/src/plugins/data/common/es_query/filters/build_filter.test.ts b/src/plugins/data/common/es_query/filters/build_filter.test.ts new file mode 100644 index 00000000000000..22b44035d6ca85 --- /dev/null +++ b/src/plugins/data/common/es_query/filters/build_filter.test.ts @@ -0,0 +1,131 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { buildFilter, FilterStateStore, FILTERS } from '.'; +import { stubIndexPattern, stubFields } from '../../../public/stubs'; + +describe('buildFilter', () => { + it('should build phrase filters', () => { + const params = 'foo'; + const alias = 'bar'; + const state = FilterStateStore.APP_STATE; + const filter = buildFilter( + stubIndexPattern, + stubFields[0], + FILTERS.PHRASE, + false, + false, + params, + alias, + state + ); + expect(filter.meta.negate).toBe(false); + expect(filter.meta.alias).toBe(alias); + + expect(filter.$state).toBeDefined(); + if (filter.$state) { + expect(filter.$state.store).toBe(state); + } + }); + + it('should build phrases filters', () => { + const params = ['foo', 'bar']; + const alias = 'bar'; + const state = FilterStateStore.APP_STATE; + const filter = buildFilter( + stubIndexPattern, + stubFields[0], + FILTERS.PHRASES, + false, + false, + params, + alias, + state + ); + expect(filter.meta.type).toBe(FILTERS.PHRASES); + expect(filter.meta.negate).toBe(false); + expect(filter.meta.alias).toBe(alias); + expect(filter.$state).toBeDefined(); + if (filter.$state) { + expect(filter.$state.store).toBe(state); + } + }); + + it('should build range filters', () => { + const params = { from: 'foo', to: 'qux' }; + const alias = 'bar'; + const state = FilterStateStore.APP_STATE; + const filter = buildFilter( + stubIndexPattern, + stubFields[0], + FILTERS.RANGE, + false, + false, + params, + alias, + state + ); + expect(filter.meta.negate).toBe(false); + expect(filter.meta.alias).toBe(alias); + expect(filter.$state).toBeDefined(); + if (filter.$state) { + expect(filter.$state.store).toBe(state); + } + }); + + it('should build exists filters', () => { + const params = undefined; + const alias = 'bar'; + const state = FilterStateStore.APP_STATE; + const filter = buildFilter( + stubIndexPattern, + stubFields[0], + FILTERS.EXISTS, + false, + false, + params, + alias, + state + ); + expect(filter.meta.negate).toBe(false); + expect(filter.meta.alias).toBe(alias); + expect(filter.$state).toBeDefined(); + if (filter.$state) { + expect(filter.$state.store).toBe(state); + } + }); + + it('should include disabled state', () => { + const params = undefined; + const alias = 'bar'; + const state = FilterStateStore.APP_STATE; + const filter = buildFilter( + stubIndexPattern, + stubFields[0], + FILTERS.EXISTS, + true, + true, + params, + alias, + state + ); + expect(filter.meta.disabled).toBe(true); + expect(filter.meta.negate).toBe(true); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/build_filters.ts b/src/plugins/data/common/es_query/filters/build_filters.ts new file mode 100644 index 00000000000000..affd213c295177 --- /dev/null +++ b/src/plugins/data/common/es_query/filters/build_filters.ts @@ -0,0 +1,80 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { esFilters, IIndexPattern, IFieldType } from '../..'; +import { FilterMeta, FilterStateStore } from '.'; + +export function buildFilter( + indexPattern: IIndexPattern, + field: IFieldType, + type: esFilters.FILTERS, + negate: boolean, + disabled: boolean, + params: any, + alias: string | null, + store: esFilters.FilterStateStore +): esFilters.Filter { + const filter = buildBaseFilter(indexPattern, field, type, params); + filter.meta.alias = alias; + filter.meta.negate = negate; + filter.meta.disabled = disabled; + filter.$state = { store }; + return filter; +} + +export function buildCustomFilter( + indexPatternString: string, + queryDsl: any, + disabled: boolean, + negate: boolean, + alias: string | null, + store: FilterStateStore +): esFilters.Filter { + const meta: FilterMeta = { + index: indexPatternString, + type: esFilters.FILTERS.CUSTOM, + disabled, + negate, + alias, + }; + const filter: esFilters.Filter = { ...queryDsl, meta }; + filter.$state = { store }; + return filter; +} + +function buildBaseFilter( + indexPattern: IIndexPattern, + field: IFieldType, + type: esFilters.FILTERS, + params: any +): esFilters.Filter { + switch (type) { + case 'phrase': + return esFilters.buildPhraseFilter(field, params, indexPattern); + case 'phrases': + return esFilters.buildPhrasesFilter(field, params, indexPattern); + case 'range': + const newParams = { gte: params.from, lt: params.to }; + return esFilters.buildRangeFilter(field, newParams, indexPattern); + case 'exists': + return esFilters.buildExistsFilter(field, indexPattern); + default: + throw new Error(`Unknown filter type: ${type}`); + } +} diff --git a/src/plugins/data/common/es_query/filters/get_filter_params.test.ts b/src/plugins/data/common/es_query/filters/get_filter_params.test.ts new file mode 100644 index 00000000000000..b0e992318327e1 --- /dev/null +++ b/src/plugins/data/common/es_query/filters/get_filter_params.test.ts @@ -0,0 +1,43 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { phraseFilter, phrasesFilter, rangeFilter, existsFilter } from './stubs'; +import { getFilterParams } from './get_filter_params'; + +describe('getFilterParams', () => { + it('should retrieve params from phrase filter', () => { + const params = getFilterParams(phraseFilter); + expect(params).toBe('ios'); + }); + + it('should retrieve params from phrases filter', () => { + const params = getFilterParams(phrasesFilter); + expect(params).toEqual(['win xp', 'osx']); + }); + + it('should retrieve params from range filter', () => { + const params = getFilterParams(rangeFilter); + expect(params).toEqual({ from: 0, to: 10 }); + }); + + it('should return undefined for exists filter', () => { + const params = getFilterParams(existsFilter); + expect(params).toBeUndefined(); + }); +}); diff --git a/src/plugins/data/common/es_query/filters/get_filter_params.ts b/src/plugins/data/common/es_query/filters/get_filter_params.ts new file mode 100644 index 00000000000000..2e90ff0fe06912 --- /dev/null +++ b/src/plugins/data/common/es_query/filters/get_filter_params.ts @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Filter, FILTERS, PhraseFilter, PhrasesFilter, RangeFilter } from '.'; + +export function getFilterParams(filter: Filter) { + switch (filter.meta.type) { + case FILTERS.PHRASE: + return (filter as PhraseFilter).meta.params.query; + case FILTERS.PHRASES: + return (filter as PhrasesFilter).meta.params; + case FILTERS.RANGE: + return { + from: (filter as RangeFilter).meta.params.gte, + to: (filter as RangeFilter).meta.params.lt, + }; + } +} diff --git a/src/plugins/data/common/es_query/filters/index.ts b/src/plugins/data/common/es_query/filters/index.ts index c19545eb83a060..1bd534bf74ff7c 100644 --- a/src/plugins/data/common/es_query/filters/index.ts +++ b/src/plugins/data/common/es_query/filters/index.ts @@ -20,6 +20,9 @@ import { omit, get } from 'lodash'; import { Filter } from './meta_filter'; +export * from './build_filters'; +export * from './get_filter_params'; + export * from './custom_filter'; export * from './exists_filter'; export * from './geo_bounding_box_filter'; diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/get_display_value.ts b/src/plugins/data/common/es_query/utils/get_display_value.ts similarity index 69% rename from src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/get_display_value.ts rename to src/plugins/data/common/es_query/utils/get_display_value.ts index d8af7b3e97ad23..4bf7e1c9c6ba70 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/lib/get_display_value.ts +++ b/src/plugins/data/common/es_query/utils/get_display_value.ts @@ -18,25 +18,21 @@ */ import { get } from 'lodash'; -import { esFilters } from '../../../../../../../../plugins/data/public'; -import { IndexPattern } from '../../../../index_patterns/index_patterns'; -import { Field } from '../../../../index_patterns/fields'; -import { getIndexPatternFromFilter } from './filter_editor_utils'; +import { IIndexPattern, IFieldType } from '../..'; +import { getIndexPatternFromFilter } from './get_index_pattern_from_filter'; +import { Filter } from '../filters'; -function getValueFormatter(indexPattern?: IndexPattern, key?: string) { +function getValueFormatter(indexPattern?: IIndexPattern, key?: string) { if (!indexPattern || !key) return; let format = get(indexPattern, ['fields', 'byName', key, 'format']); - if (!format && indexPattern.fields.getByName) { + if (!format && (indexPattern.fields as any).getByName) { // TODO: Why is indexPatterns sometimes a map and sometimes an array? - format = (indexPattern.fields.getByName(key) as Field).format; + format = ((indexPattern.fields as any).getByName(key) as IFieldType).format; } return format; } -export function getDisplayValueFromFilter( - filter: esFilters.Filter, - indexPatterns: IndexPattern[] -): string { +export function getDisplayValueFromFilter(filter: Filter, indexPatterns: IIndexPattern[]): string { const indexPattern = getIndexPatternFromFilter(filter, indexPatterns); if (typeof filter.meta.value === 'function') { diff --git a/src/plugins/data/common/es_query/utils/get_index_pattern_from_filter.test.ts b/src/plugins/data/common/es_query/utils/get_index_pattern_from_filter.test.ts new file mode 100644 index 00000000000000..2f31fafcb74e48 --- /dev/null +++ b/src/plugins/data/common/es_query/utils/get_index_pattern_from_filter.test.ts @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { stubIndexPattern, phraseFilter } from 'src/plugins/data/public/stubs'; +import { getIndexPatternFromFilter } from './get_index_pattern_from_filter'; + +describe('getIndexPatternFromFilter', () => { + it('should return the index pattern from the filter', () => { + const indexPattern = getIndexPatternFromFilter(phraseFilter, [stubIndexPattern]); + expect(indexPattern).toBe(stubIndexPattern); + }); +}); diff --git a/src/plugins/data/common/es_query/utils/get_index_pattern_from_filter.ts b/src/plugins/data/common/es_query/utils/get_index_pattern_from_filter.ts new file mode 100644 index 00000000000000..43d4bdaf03bc1a --- /dev/null +++ b/src/plugins/data/common/es_query/utils/get_index_pattern_from_filter.ts @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Filter } from '../filters'; +import { IIndexPattern } from '../..'; + +export function getIndexPatternFromFilter( + filter: Filter, + indexPatterns: IIndexPattern[] +): IIndexPattern | undefined { + return indexPatterns.find(indexPattern => indexPattern.id === filter.meta.index); +} diff --git a/src/plugins/data/common/es_query/utils/index.ts b/src/plugins/data/common/es_query/utils/index.ts index 27f51c1f44cf2f..79856c9e0267ee 100644 --- a/src/plugins/data/common/es_query/utils/index.ts +++ b/src/plugins/data/common/es_query/utils/index.ts @@ -18,3 +18,5 @@ */ export * from './get_time_zone_from_settings'; +export * from './get_index_pattern_from_filter'; +export * from './get_display_value'; diff --git a/src/plugins/data/common/index_patterns/fields/index.ts b/src/plugins/data/common/index_patterns/fields/index.ts index d8f7b5091eb8f6..2b43dffa8c161e 100644 --- a/src/plugins/data/common/index_patterns/fields/index.ts +++ b/src/plugins/data/common/index_patterns/fields/index.ts @@ -18,3 +18,4 @@ */ export * from './types'; +export { isFilterable } from './utils'; diff --git a/src/plugins/data/common/index_patterns/fields/utils.ts b/src/plugins/data/common/index_patterns/fields/utils.ts new file mode 100644 index 00000000000000..c7bec5e5ad3476 --- /dev/null +++ b/src/plugins/data/common/index_patterns/fields/utils.ts @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { getFilterableKbnTypeNames, IFieldType } from '../..'; + +const filterableTypes = getFilterableKbnTypeNames(); + +export function isFilterable(field: IFieldType): boolean { + return ( + field.name === '_id' || + field.scripted || + Boolean(field.searchable && filterableTypes.includes(field.type)) + ); +} diff --git a/src/plugins/data/common/index_patterns/utils.test.ts b/src/plugins/data/common/index_patterns/utils.test.ts new file mode 100644 index 00000000000000..e2707d469a3171 --- /dev/null +++ b/src/plugins/data/common/index_patterns/utils.test.ts @@ -0,0 +1,60 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { isFilterable } from '.'; +import { IFieldType } from './fields'; + +const mockField = { + name: 'foo', + scripted: false, + searchable: true, + type: 'string', +} as IFieldType; + +describe('isFilterable', () => { + describe('types', () => { + it('should return true for filterable types', () => { + ['string', 'number', 'date', 'ip', 'boolean'].forEach(type => { + expect(isFilterable({ ...mockField, type })).toBe(true); + }); + }); + + it('should return false for filterable types if the field is not searchable', () => { + ['string', 'number', 'date', 'ip', 'boolean'].forEach(type => { + expect(isFilterable({ ...mockField, type, searchable: false })).toBe(false); + }); + }); + + it('should return false for un-filterable types', () => { + ['geo_point', 'geo_shape', 'attachment', 'murmur3', '_source', 'unknown', 'conflict'].forEach( + type => { + expect(isFilterable({ ...mockField, type })).toBe(false); + } + ); + }); + }); + + it('should return true for scripted fields', () => { + expect(isFilterable({ ...mockField, scripted: true, searchable: false })).toBe(true); + }); + + it('should return true for the _id field', () => { + expect(isFilterable({ ...mockField, name: '_id' })).toBe(true); + }); +}); diff --git a/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts index f2fd55af4f4180..b4d46ae9fb3cf7 100644 --- a/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/generate_filters.ts @@ -95,16 +95,18 @@ export function generateFilters( } else { const tmpIndexPattern = { id: index } as IIndexPattern; - switch (fieldName) { - case '_exists_': - filter = esFilters.buildExistsFilter(fieldObj, tmpIndexPattern); - break; - default: - filter = esFilters.buildPhraseFilter(fieldObj, value, tmpIndexPattern); - break; - } - - filter.meta.negate = negate; + const filterType = + fieldName === '_exists_' ? esFilters.FILTERS.EXISTS : esFilters.FILTERS.PHRASE; + filter = esFilters.buildFilter( + tmpIndexPattern, + fieldObj, + filterType, + negate, + false, + value, + null, + esFilters.FilterStateStore.APP_STATE + ); } newFilters.push(filter); diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/field.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/field.js index 8a20337317cfb4..6eae233cf5deaa 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/field.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/field.js @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { getSuggestionsProvider } from '../field'; import indexPatternResponse from '../__fixtures__/index_pattern_response.json'; -import { isFilterable } from 'ui/index_patterns'; +import { isFilterable } from '../../../../../../../src/plugins/data/public'; describe('Kuery field suggestions', function () { let indexPattern; diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js index 3d7e1979d224bf..1a00c668833aa0 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js @@ -7,7 +7,7 @@ import React from 'react'; import { flatten } from 'lodash'; import { escapeKuery } from './escape_kuery'; import { sortPrefixFirst } from 'ui/utils/sort_prefix_first'; -import { isFilterable } from 'ui/index_patterns'; +import { isFilterable } from '../../../../../../src/plugins/data/public'; import { FormattedMessage } from '@kbn/i18n/react'; From 46e0f9fe580943a8e71e516d117422610b729189 Mon Sep 17 00:00:00 2001 From: patrykkopycinski Date: Thu, 21 Nov 2019 14:28:58 +0100 Subject: [PATCH 3/6] Update note_card_body snapshot (#51278) * Update note_card_body snapshot * paginated_table --- .../note_card_body.test.tsx.snap | 51 ++++++++++++++++--- .../__snapshots__/index.test.tsx.snap | 51 ++++++++++++++++--- 2 files changed, 86 insertions(+), 16 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap index 7e4b6babae449c..c9a40975e7b92c 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_card/__snapshots__/note_card_body.test.tsx.snap @@ -32,14 +32,6 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = ` "secondary": "#9dc2bc", "warning": "#ebc98e", }, - "buttonTypes": Object { - "danger": "#ff6666", - "ghost": "#ffffff", - "primary": "#1ba9f5", - "secondary": "#7de2d1", - "text": "#98a2b3", - "warning": "#ffce7a", - }, "euiAnimSlightBounce": "cubic-bezier(0.34, 1.61, 0.7, 1)", "euiAnimSlightResistance": "cubic-bezier(0.694, 0.0482, 0.335, 1)", "euiAnimSpeedExtraFast": "90ms", @@ -65,10 +57,43 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = ` "xs": 0, }, "euiButtonColorDisabled": "#434548", + "euiButtonEmptyTypes": Object { + "danger": "#ff6666", + "disabled": "#2d2e30", + "ghost": "#ffffff", + "primary": "#1ba9f5", + "text": "#dfe5ef", + }, "euiButtonHeight": "40px", "euiButtonHeightSmall": "32px", + "euiButtonIconTypes": Object { + "danger": "#ff6666", + "disabled": "#434548", + "ghost": "#ffffff", + "primary": "#1ba9f5", + "subdued": "#98a2b3", + "success": "#7de2d1", + "text": "#dfe5ef", + "warning": "#ffce7a", + }, "euiButtonMinWidth": "112px", "euiButtonToggleBorderColor": "#343741", + "euiButtonToggleTypes": Object { + "danger": "#ff6666", + "ghost": "#ffffff", + "primary": "#1ba9f5", + "secondary": "#7de2d1", + "text": "#98a2b3", + "warning": "#ffce7a", + }, + "euiButtonTypes": Object { + "danger": "#ff6666", + "ghost": "#ffffff", + "primary": "#1ba9f5", + "secondary": "#7de2d1", + "text": "#98a2b3", + "warning": "#ffce7a", + }, "euiCallOutTypes": Object { "danger": "#ff6666", "primary": "#1ba9f5", @@ -155,6 +180,16 @@ exports[`NoteCardBody renders correctly against snapshot 1`] = ` "euiColorVis9": "#920000", "euiColorWarning": "#ffce7a", "euiContextMenuWidth": "256px", + "euiControlBarBackground": "#000000", + "euiControlBarBorderColor": "rgba(255, 255, 255, 0.19999999999999996)", + "euiControlBarHeights": Object { + "l": "100vh", + "m": "480px", + "s": "240px", + }, + "euiControlBarInitialHeight": "40px", + "euiControlBarMaxHeight": "calc(100vh - 80px)", + "euiControlBarText": "#a9aaad", "euiDataGridCellPaddingL": "8px", "euiDataGridCellPaddingM": "6px", "euiDataGridCellPaddingS": "4px", diff --git a/x-pack/legacy/plugins/siem/public/components/paginated_table/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/paginated_table/__snapshots__/index.test.tsx.snap index e000eeb14214e8..4ac25720080a92 100644 --- a/x-pack/legacy/plugins/siem/public/components/paginated_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/paginated_table/__snapshots__/index.test.tsx.snap @@ -32,14 +32,6 @@ exports[`Paginated Table Component rendering it renders the default load more ta "secondary": "#9dc2bc", "warning": "#ebc98e", }, - "buttonTypes": Object { - "danger": "#ff6666", - "ghost": "#ffffff", - "primary": "#1ba9f5", - "secondary": "#7de2d1", - "text": "#98a2b3", - "warning": "#ffce7a", - }, "euiAnimSlightBounce": "cubic-bezier(0.34, 1.61, 0.7, 1)", "euiAnimSlightResistance": "cubic-bezier(0.694, 0.0482, 0.335, 1)", "euiAnimSpeedExtraFast": "90ms", @@ -65,10 +57,43 @@ exports[`Paginated Table Component rendering it renders the default load more ta "xs": 0, }, "euiButtonColorDisabled": "#434548", + "euiButtonEmptyTypes": Object { + "danger": "#ff6666", + "disabled": "#2d2e30", + "ghost": "#ffffff", + "primary": "#1ba9f5", + "text": "#dfe5ef", + }, "euiButtonHeight": "40px", "euiButtonHeightSmall": "32px", + "euiButtonIconTypes": Object { + "danger": "#ff6666", + "disabled": "#434548", + "ghost": "#ffffff", + "primary": "#1ba9f5", + "subdued": "#98a2b3", + "success": "#7de2d1", + "text": "#dfe5ef", + "warning": "#ffce7a", + }, "euiButtonMinWidth": "112px", "euiButtonToggleBorderColor": "#343741", + "euiButtonToggleTypes": Object { + "danger": "#ff6666", + "ghost": "#ffffff", + "primary": "#1ba9f5", + "secondary": "#7de2d1", + "text": "#98a2b3", + "warning": "#ffce7a", + }, + "euiButtonTypes": Object { + "danger": "#ff6666", + "ghost": "#ffffff", + "primary": "#1ba9f5", + "secondary": "#7de2d1", + "text": "#98a2b3", + "warning": "#ffce7a", + }, "euiCallOutTypes": Object { "danger": "#ff6666", "primary": "#1ba9f5", @@ -155,6 +180,16 @@ exports[`Paginated Table Component rendering it renders the default load more ta "euiColorVis9": "#920000", "euiColorWarning": "#ffce7a", "euiContextMenuWidth": "256px", + "euiControlBarBackground": "#000000", + "euiControlBarBorderColor": "rgba(255, 255, 255, 0.19999999999999996)", + "euiControlBarHeights": Object { + "l": "100vh", + "m": "480px", + "s": "240px", + }, + "euiControlBarInitialHeight": "40px", + "euiControlBarMaxHeight": "calc(100vh - 80px)", + "euiControlBarText": "#a9aaad", "euiDataGridCellPaddingL": "8px", "euiDataGridCellPaddingM": "6px", "euiDataGridCellPaddingS": "4px", From a6f7b18546e187f1ac58bb56f6e4ff148616fb7a Mon Sep 17 00:00:00 2001 From: Maja Grubic Date: Thu, 21 Nov 2019 14:45:03 +0000 Subject: [PATCH 4/6] [Dashboard] EUI-fication of the empty dashboard screen (#51151) * [Dashboard][POC] De-angularize empty screen * EUI-fication of the empty dashboard screen * Adding tests for empty dashboard screen * Removing unused imports * Reorganizing directive so that it works with local Angular * Adding string constants * Adding missing snapshot --- .../dashboard_empty_screen.test.tsx.snap | 516 ++++++++++++++++++ .../__tests__/dashboard_empty_screen.test.tsx | 48 ++ .../kibana/public/dashboard/application.ts | 1 + .../public/dashboard/dashboard_app.html | 66 +-- .../dashboard/dashboard_app_controller.tsx | 2 + .../dashboard/dashboard_empty_screen.tsx | 91 +++ .../dashboard_empty_screen_constants.tsx | 78 +++ .../dashboard_empty_screen_directive.ts | 30 + .../translations/translations/ja-JP.json | 9 +- .../translations/translations/zh-CN.json | 9 +- 10 files changed, 780 insertions(+), 70 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_constants.tsx create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap new file mode 100644 index 00000000000000..07e4173d5323fb --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -0,0 +1,516 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DashboardEmptyScreen renders correctly with visualize paragraph 1`] = ` + + + + + + + + + +

+ This dashboard is empty. Let’s fill it up! +

+

+ + Click the + + + + button in the menu bar above to add a visualization to the dashboard. + +

+

+ + visit the Visualize app + , + } + } + > + If you haven't set up any visualizations yet, + + visit the Visualize app + + to create your first visualization + +

+
+
+
+
+`; + +exports[`DashboardEmptyScreen renders correctly without visualize paragraph 1`] = ` + + + + + + + + + +

+ This dashboard is empty. Let’s fill it up! +

+

+ + Click the + + + + button in the menu bar above to start working on your new dashboard. + +

+
+
+
+
+`; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx new file mode 100644 index 00000000000000..a4604d17ddecda --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { DashboardEmptyScreen, Props } from '../dashboard_empty_screen'; + +describe('DashboardEmptyScreen', () => { + const defaultProps = { + showLinkToVisualize: true, + onLinkClick: jest.fn(), + }; + + function mountComponent(props?: Props) { + const compProps = props || defaultProps; + const comp = mountWithIntl(); + return comp; + } + + test('renders correctly with visualize paragraph', () => { + const component = mountComponent(); + expect(component).toMatchSnapshot(); + const paragraph = component.find('.linkToVisualizeParagraph'); + expect(paragraph.length).toBe(1); + }); + + test('renders correctly without visualize paragraph', () => { + const component = mountComponent({ ...defaultProps, ...{ showLinkToVisualize: false } }); + expect(component).toMatchSnapshot(); + const paragraph = component.find('.linkToVisualizeParagraph'); + expect(paragraph.length).toBe(0); + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/application.ts index d507d547d9ba95..57391223fa1472 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/application.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/application.ts @@ -134,6 +134,7 @@ function createLocalAngularModule(core: AppMountContext['core'], navigation: Nav 'app/dashboard/State', 'app/dashboard/ConfirmModal', 'app/dashboard/icon', + 'app/dashboard/emptyScreen', ]); return dashboardAngularModule; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html index b645bb408300f1..0b842fbfaeddc5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html @@ -51,71 +51,17 @@
- -

-
-

-

- -

- - - -

-

+
-

-

- -

- - - -

+
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 16c0e4437c344c..1a0e13417d1e1f 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -41,6 +41,8 @@ import { import { FilterStateManager, IndexPattern, SavedQuery } from '../../../data/public'; import { Query } from '../../../../../plugins/data/public'; +import './dashboard_empty_screen_directive'; + import { DashboardContainer, DASHBOARD_CONTAINER_TYPE, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx new file mode 100644 index 00000000000000..d5a4e6e6a325d9 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen.tsx @@ -0,0 +1,91 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { I18nProvider, FormattedMessage } from '@kbn/i18n/react'; +import { EuiIcon, EuiLink } from '@elastic/eui'; +import * as constants from './dashboard_empty_screen_constants'; + +export interface Props { + showLinkToVisualize: boolean; + onLinkClick: () => void; +} + +export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick }: Props) { + const linkToVisualizeParagraph = ( +

+ + {constants.visualizeAppLinkTest} + + ), + }} + /> +

+ ); + const paragraph = ( + description1: string, + description2: string, + linkText: string, + ariaLabel: string, + dataTestSubj?: string + ) => { + return ( +

+ + {description1} + + {linkText} + + {description2} + +

+ ); + }; + const addVisualizationParagraph = ( + + {paragraph( + constants.addVisualizationDescription1, + constants.addVisualizationDescription2, + constants.addVisualizationLinkText, + constants.addVisualizationLinkAriaLabel, + 'emptyDashboardAddPanelButton' + )} + {linkToVisualizeParagraph} + + ); + const enterEditModeParagraph = paragraph( + constants.howToStartWorkingOnNewDashboardDescription1, + constants.howToStartWorkingOnNewDashboardDescription2, + constants.howToStartWorkingOnNewDashboardEditLinkText, + constants.howToStartWorkingOnNewDashboardEditLinkAriaLabel + ); + return ( + + + +

{constants.fillDashboardTitle}

+ {showLinkToVisualize ? addVisualizationParagraph : enterEditModeParagraph} +
+
+ ); +} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_constants.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_constants.tsx new file mode 100644 index 00000000000000..0f510375aaf597 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_constants.tsx @@ -0,0 +1,78 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; + +export const addVisualizationDescription1: string = i18n.translate( + 'kbn.dashboard.addVisualizationDescription1', + { + defaultMessage: 'Click the ', + } +); +export const addVisualizationDescription2: string = i18n.translate( + 'kbn.dashboard.addVisualizationDescription2', + { + defaultMessage: ' button in the menu bar above to add a visualization to the dashboard.', + } +); +export const addVisualizationLinkText: string = i18n.translate( + 'kbn.dashboard.addVisualizationLinkText', + { + defaultMessage: 'Add', + } +); +export const addVisualizationLinkAriaLabel: string = i18n.translate( + 'kbn.dashboard.addVisualizationLinkAriaLabel', + { + defaultMessage: 'Add visualization', + } +); +export const howToStartWorkingOnNewDashboardDescription1: string = i18n.translate( + 'kbn.dashboard.howToStartWorkingOnNewDashboardDescription1', + { + defaultMessage: 'Click the ', + } +); +export const howToStartWorkingOnNewDashboardDescription2: string = i18n.translate( + 'kbn.dashboard.howToStartWorkingOnNewDashboardDescription2', + { + defaultMessage: ' button in the menu bar above to start working on your new dashboard.', + } +); +export const howToStartWorkingOnNewDashboardEditLinkText: string = i18n.translate( + 'kbn.dashboard.howToStartWorkingOnNewDashboardEditLinkText', + { + defaultMessage: 'Edit', + } +); +export const howToStartWorkingOnNewDashboardEditLinkAriaLabel: string = i18n.translate( + 'kbn.dashboard.howToStartWorkingOnNewDashboardEditLinkAriaLabel', + { + defaultMessage: 'Edit dashboard', + } +); +export const fillDashboardTitle: string = i18n.translate('kbn.dashboard.fillDashboardTitle', { + defaultMessage: 'This dashboard is empty. Let\u2019s fill it up!', +}); +export const visualizeAppLinkTest: string = i18n.translate( + 'kbn.dashboard.visitVisualizeAppLinkText', + { + defaultMessage: 'visit the Visualize app', + } +); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.ts new file mode 100644 index 00000000000000..5ebefd817ca4a7 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_empty_screen_directive.ts @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +// @ts-ignore +import angular from 'angular'; +import { DashboardEmptyScreen } from './dashboard_empty_screen'; + +angular + .module('app/dashboard/emptyScreen', ['react']) + .directive('dashboardEmptyScreen', function(reactDirective: any) { + return reactDirective(DashboardEmptyScreen, [ + ['showLinkToVisualize', { watchDepth: 'value' }], + ['onLinkClick', { watchDepth: 'reference' }], + ]); + }); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8312ba3d0c83fc..5c5da391f08b5e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -879,6 +879,9 @@ "data.search.searchBar.savedQueryForm.titleMissingText": "名前が必要です", "data.query.queryBar.searchInputAriaLabel": "{pageType} ページの検索とフィルタリングを行うには入力を開始してください", "data.search.searchBar.savedQueryPopoverSaveAsNewButtonAriaLabel": "新規保存クエリを保存", + "data.functions.esaggs.help": "AggConfig 集約を実行します", + "data.functions.esaggs.inspector.dataRequest.description": "このリクエストは Elasticsearch にクエリし、ビジュアライゼーション用のデータを取得します。", + "data.functions.esaggs.inspector.dataRequest.title": "データ", "embeddableApi.actions.applyFilterActionTitle": "現在のビューにフィルターを適用", "embeddableApi.addPanel.createNewDefaultOption": "新規作成...", "embeddableApi.addPanel.displayName": "パネルの追加", @@ -954,9 +957,6 @@ "visualizations.function.visDimension.accessor.help": "使用するデータセット内の列 (列インデックスまたは列名)", "visualizations.function.visDimension.error.accessor": "入力された列名は無効です。", "visualizations.function.visDimension.help": "visConfig ディメンションオブジェクトを生成します", - "data.functions.esaggs.help": "AggConfig 集約を実行します", - "data.functions.esaggs.inspector.dataRequest.description": "このリクエストは Elasticsearch にクエリし、ビジュアライゼーション用のデータを取得します。", - "data.functions.esaggs.inspector.dataRequest.title": "データ", "expressions.functions.kibana_context.help": "Kibana グローバルコンテキストを更新します", "expressions.functions.kibana.help": "Kibana グローバルコンテキストを取得します", "expressions.functions.font.args.alignHelpText": "水平テキスト配置", @@ -1440,7 +1440,6 @@ "kbn.context.unableToLoadDocumentDescription": "ドキュメントが読み込めません", "kbn.dashboard.addVisualizationDescription1": "上のメニューバーの ", "kbn.dashboard.addVisualizationDescription2": " ボタンをクリックして、ダッシュボードにビジュアライゼーションを追加します。", - "kbn.dashboard.addVisualizationDescription3": "まだビジュアライゼーションをセットアップしていない場合は、{visitVisualizeAppLink} して初めのビジュアライゼーションを作成します。", "kbn.dashboard.addVisualizationLinkAriaLabel": "ビジュアライゼーションを追加", "kbn.dashboard.addVisualizationLinkText": "追加", "kbn.dashboard.badge.readOnly.text": "読み込み専用", @@ -12761,4 +12760,4 @@ "xpack.licensing.check.errorUnavailableMessage": "現在ライセンス情報が利用できないため {pluginName} を使用できません。", "xpack.licensing.check.errorUnsupportedMessage": "ご使用の {licenseType} ライセンスは {pluginName} をサポートしていません。ライセンスをアップグレードしてください。" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f90d2f9522c311..cb4f0790e8310d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -880,6 +880,9 @@ "data.search.searchBar.savedQueryForm.titleMissingText": "“名称”必填", "data.query.queryBar.searchInputAriaLabel": "开始键入内容,以搜索并筛选 {pageType} 页面", "data.search.searchBar.savedQueryPopoverSaveAsNewButtonAriaLabel": "另存为新的已保存查询", + "data.functions.esaggs.help": "运行 AggConfig 聚合", + "data.functions.esaggs.inspector.dataRequest.description": "此请求将查询 Elasticsearch 以获取用于可视化的数据。", + "data.functions.esaggs.inspector.dataRequest.title": "数据", "embeddableApi.actions.applyFilterActionTitle": "将筛选应用于当前视图", "embeddableApi.addPanel.createNewDefaultOption": "创建新的......", "embeddableApi.addPanel.displayName": "添加面板", @@ -955,9 +958,6 @@ "visualizations.function.visDimension.accessor.help": "数据集中要使用的列(列索引或列名称)", "visualizations.function.visDimension.error.accessor": "提供的列名称无效", "visualizations.function.visDimension.help": "生成 visConfig 维度对象", - "data.functions.esaggs.help": "运行 AggConfig 聚合", - "data.functions.esaggs.inspector.dataRequest.description": "此请求将查询 Elasticsearch 以获取用于可视化的数据。", - "data.functions.esaggs.inspector.dataRequest.title": "数据", "expressions.functions.kibana_context.help": "更新 kibana 全局上下文", "expressions.functions.kibana.help": "获取 kibana 全局上下文", "expressions.functions.font.args.alignHelpText": "水平文本对齐。", @@ -1441,7 +1441,6 @@ "kbn.context.unableToLoadDocumentDescription": "无法加载文档", "kbn.dashboard.addVisualizationDescription1": "单击上述菜单栏中的 ", "kbn.dashboard.addVisualizationDescription2": " 按钮,以将可视化添加到仪表板。", - "kbn.dashboard.addVisualizationDescription3": "如果尚未设置任何可视化,请{visitVisualizeAppLink}以创建您的第一个可视化。", "kbn.dashboard.addVisualizationLinkAriaLabel": "添加可视化", "kbn.dashboard.addVisualizationLinkText": "添加", "kbn.dashboard.badge.readOnly.text": "只读", @@ -12851,4 +12850,4 @@ "xpack.licensing.check.errorUnavailableMessage": "您不能使用 {pluginName},因为许可证信息当前不可用。", "xpack.licensing.check.errorUnsupportedMessage": "您的{licenseType}许可证不支持 {pluginName}。请升级您的许可证。" } -} +} \ No newline at end of file From 06bee6051258009cca6c0c92fa1362c56823e734 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Thu, 21 Nov 2019 15:46:34 +0100 Subject: [PATCH 5/6] [APM] Migrate server routes to NP (#49455) * [APM] Migrate server routes to NP Closes #49238. Pass legacy API to NP plugin in order to use internal SO client Fix issues with agent configuration APIs Update tsconfig template for TS optimization Fix typo * Review feedback * Fix type issues after browser duration changes * Revert changes in getServices due to readability concerns * Review feedback --- src/legacy/core_plugins/apm_oss/index.js | 1 - src/plugins/apm_oss/kibana.json | 11 + src/plugins/apm_oss/server/index.ts | 41 ++ src/plugins/apm_oss/server/plugin.ts | 38 ++ .../plugins/apm/common/projections/errors.ts | 8 +- .../plugins/apm/common/projections/metrics.ts | 8 +- .../apm/common/projections/service_nodes.ts | 8 +- .../apm/common/projections/services.ts | 12 +- .../common/projections/transaction_groups.ts | 8 +- .../apm/common/projections/transactions.ts | 8 +- x-pack/legacy/plugins/apm/index.ts | 20 +- x-pack/legacy/plugins/apm/mappings.json | 3 - .../app/Settings/ApmIndices/index.tsx | 7 - .../apm/public/new-platform/plugin.tsx | 5 +- .../public/services/__test__/callApi.test.ts | 2 +- .../services/__test__/callApmApi.test.ts | 4 +- .../apm/public/services/rest/callApi.ts | 16 +- .../public/services/rest/createCallApmApi.ts | 8 +- .../plugins/apm/public/utils/testHelpers.tsx | 20 +- .../scripts/optimize-tsconfig/tsconfig.json | 1 + .../apm/server/lib/apm_telemetry/index.ts | 26 +- .../__snapshots__/queries.test.ts.snap | 4 +- .../__tests__/get_buckets.test.ts | 13 +- .../lib/errors/distribution/get_buckets.ts | 8 +- .../errors/distribution/get_distribution.ts | 16 +- .../apm/server/lib/errors/get_error_group.ts | 8 +- .../apm/server/lib/errors/get_error_groups.ts | 8 +- .../get_trace_errors_per_transaction.ts | 4 +- .../convert_ui_filters/get_ui_filters_es.ts | 1 - .../apm/server/lib/helpers/es_client.ts | 69 ++-- .../lib/helpers/saved_objects_client.ts | 4 +- .../server/lib/helpers/setup_request.test.ts | 146 ++++--- .../apm/server/lib/helpers/setup_request.ts | 92 +++-- .../create_static_index_pattern.test.ts | 45 ++- .../create_static_index_pattern.ts | 14 +- .../get_dynamic_index_pattern.ts | 10 +- .../server/lib/metrics/by_agent/default.ts | 8 +- .../java/gc/fetchAndTransformGcMetrics.ts | 8 +- .../by_agent/java/gc/getGcRateChart.ts | 8 +- .../by_agent/java/gc/getGcTimeChart.ts | 8 +- .../by_agent/java/heap_memory/index.ts | 8 +- .../server/lib/metrics/by_agent/java/index.ts | 8 +- .../by_agent/java/non_heap_memory/index.ts | 8 +- .../by_agent/java/thread_count/index.ts | 8 +- .../lib/metrics/by_agent/shared/cpu/index.ts | 8 +- .../metrics/by_agent/shared/memory/index.ts | 8 +- .../metrics/fetch_and_transform_metrics.ts | 8 +- .../get_metrics_chart_data_by_agent.ts | 8 +- .../apm/server/lib/service_nodes/index.ts | 8 +- .../lib/services/get_service_agent_name.ts | 7 +- .../lib/services/get_service_node_metadata.ts | 8 +- .../services/get_service_transaction_types.ts | 4 +- .../get_services/get_services_items.ts | 10 +- .../server/lib/services/get_services/index.ts | 27 +- .../create_agent_config_index.ts | 36 +- .../create_or_update_configuration.ts | 2 +- .../delete_configuration.ts | 2 +- .../get_existing_environments_for_service.ts | 2 +- .../list_configurations.ts | 2 +- .../mark_applied_by_agent.ts | 2 +- .../settings/agent_configuration/search.ts | 2 +- .../settings/apm_indices/get_apm_indices.ts | 60 ++- .../settings/apm_indices/save_apm_indices.ts | 7 +- .../apm/server/lib/traces/get_trace.ts | 4 +- .../apm/server/lib/traces/get_trace_items.ts | 9 +- .../lib/transaction_groups/fetcher.test.ts | 13 +- .../server/lib/transaction_groups/fetcher.ts | 13 +- .../server/lib/transaction_groups/index.ts | 11 +- .../avg_duration_by_browser/fetcher.test.ts | 8 +- .../avg_duration_by_browser/index.ts | 8 +- .../avg_duration_by_country/index.ts | 8 +- .../lib/transactions/breakdown/index.test.ts | 13 +- .../lib/transactions/breakdown/index.ts | 8 +- .../charts/get_anomaly_data/fetcher.ts | 4 +- .../get_anomaly_data/get_ml_bucket_size.ts | 4 +- .../charts/get_anomaly_data/index.test.ts | 13 +- .../charts/get_anomaly_data/index.ts | 8 +- .../get_timeseries_data/fetcher.test.ts | 13 +- .../charts/get_timeseries_data/fetcher.ts | 8 +- .../charts/get_timeseries_data/index.ts | 8 +- .../server/lib/transactions/charts/index.ts | 8 +- .../transactions/constants.ts} | 8 +- .../distribution/get_buckets/fetcher.ts | 8 +- .../distribution/get_buckets/index.ts | 8 +- .../distribution/get_distribution_max.ts | 8 +- .../lib/transactions/distribution/index.ts | 21 +- .../lib/transactions/get_transaction/index.ts | 8 +- .../server/lib/ui_filters/get_environments.ts | 7 +- .../get_filter_aggregations.ts | 69 ++-- .../lib/ui_filters/local_ui_filters/index.ts | 2 +- .../plugins/apm/server/new-platform/plugin.ts | 23 -- .../server/routes/create_api/index.test.ts | 359 ++++++++++-------- .../apm/server/routes/create_api/index.ts | 174 ++++++--- .../plugins/apm/server/routes/errors.ts | 24 +- .../apm/server/routes/index_pattern.ts | 14 +- .../plugins/apm/server/routes/metrics.ts | 9 +- .../apm/server/routes/service_nodes.ts | 7 +- .../plugins/apm/server/routes/services.ts | 35 +- .../routes/settings/agent_configuration.ts | 51 +-- .../apm/server/routes/settings/apm_indices.ts | 31 +- .../plugins/apm/server/routes/traces.ts | 11 +- .../apm/server/routes/transaction_groups.ts | 46 +-- .../plugins/apm/server/routes/typings.ts | 58 ++- .../plugins/apm/server/routes/ui_filters.ts | 27 +- x-pack/plugins/apm/kibana.json | 14 + x-pack/plugins/apm/server/index.ts | 43 +++ x-pack/plugins/apm/server/plugin.ts | 69 ++++ 107 files changed, 1399 insertions(+), 868 deletions(-) create mode 100644 src/plugins/apm_oss/kibana.json create mode 100644 src/plugins/apm_oss/server/index.ts create mode 100644 src/plugins/apm_oss/server/plugin.ts rename x-pack/legacy/plugins/apm/server/{new-platform/index.ts => lib/transactions/constants.ts} (55%) delete mode 100644 x-pack/legacy/plugins/apm/server/new-platform/plugin.ts create mode 100644 x-pack/plugins/apm/kibana.json create mode 100644 x-pack/plugins/apm/server/index.ts create mode 100644 x-pack/plugins/apm/server/plugin.ts diff --git a/src/legacy/core_plugins/apm_oss/index.js b/src/legacy/core_plugins/apm_oss/index.js index 0c281ec939bb13..9de571ab7cae90 100644 --- a/src/legacy/core_plugins/apm_oss/index.js +++ b/src/legacy/core_plugins/apm_oss/index.js @@ -38,7 +38,6 @@ export default function apmOss(kibana) { spanIndices: Joi.string().default('apm-*'), metricsIndices: Joi.string().default('apm-*'), onboardingIndices: Joi.string().default('apm-*'), - apmAgentConfigurationIndex: Joi.string().default('.apm-agent-configuration'), }).default(); }, diff --git a/src/plugins/apm_oss/kibana.json b/src/plugins/apm_oss/kibana.json new file mode 100644 index 00000000000000..5853ba198e717d --- /dev/null +++ b/src/plugins/apm_oss/kibana.json @@ -0,0 +1,11 @@ +{ + "id": "apm_oss", + "version": "8.0.0", + "server": true, + "kibanaVersion": "kibana", + "configPath": [ + "apm_oss" + ], + "ui": false, + "requiredPlugins": [] +} diff --git a/src/plugins/apm_oss/server/index.ts b/src/plugins/apm_oss/server/index.ts new file mode 100644 index 00000000000000..801140694c1394 --- /dev/null +++ b/src/plugins/apm_oss/server/index.ts @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { schema, TypeOf } from '@kbn/config-schema'; +import { PluginInitializerContext } from '../../../core/server'; +import { APMOSSPlugin } from './plugin'; + +export const config = { + schema: schema.object({ + transactionIndices: schema.string({ defaultValue: 'apm-*' }), + spanIndices: schema.string({ defaultValue: 'apm-*' }), + errorIndices: schema.string({ defaultValue: 'apm-*' }), + metricsIndices: schema.string({ defaultValue: 'apm-*' }), + sourcemapIndices: schema.string({ defaultValue: 'apm-*' }), + onboardingIndices: schema.string({ defaultValue: 'apm-*' }), + indexPattern: schema.string({ defaultValue: 'apm-*' }), + }), +}; + +export function plugin(initializerContext: PluginInitializerContext) { + return new APMOSSPlugin(initializerContext); +} + +export type APMOSSConfig = TypeOf; + +export { APMOSSPlugin as Plugin }; diff --git a/src/plugins/apm_oss/server/plugin.ts b/src/plugins/apm_oss/server/plugin.ts new file mode 100644 index 00000000000000..2708f7729482bd --- /dev/null +++ b/src/plugins/apm_oss/server/plugin.ts @@ -0,0 +1,38 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { Plugin, CoreSetup, PluginInitializerContext } from 'src/core/server'; +import { Observable } from 'rxjs'; +import { APMOSSConfig } from './'; + +export class APMOSSPlugin implements Plugin<{ config$: Observable }> { + constructor(private readonly initContext: PluginInitializerContext) { + this.initContext = initContext; + } + + public setup(core: CoreSetup) { + const config$ = this.initContext.config.create(); + + return { + config$, + }; + } + + start() {} + stop() {} +} diff --git a/x-pack/legacy/plugins/apm/common/projections/errors.ts b/x-pack/legacy/plugins/apm/common/projections/errors.ts index adbd2eb1d6d27d..27e1de43a1a940 100644 --- a/x-pack/legacy/plugins/apm/common/projections/errors.ts +++ b/x-pack/legacy/plugins/apm/common/projections/errors.ts @@ -4,7 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Setup } from '../../server/lib/helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../server/lib/helpers/setup_request'; import { PROCESSOR_EVENT, SERVICE_NAME, @@ -16,7 +20,7 @@ export function getErrorGroupsProjection({ setup, serviceName }: { - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; serviceName: string; }) { const { start, end, uiFiltersES, indices } = setup; diff --git a/x-pack/legacy/plugins/apm/common/projections/metrics.ts b/x-pack/legacy/plugins/apm/common/projections/metrics.ts index 25d1484624e152..066f5789752a74 100644 --- a/x-pack/legacy/plugins/apm/common/projections/metrics.ts +++ b/x-pack/legacy/plugins/apm/common/projections/metrics.ts @@ -4,7 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Setup } from '../../server/lib/helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../server/lib/helpers/setup_request'; import { SERVICE_NAME, PROCESSOR_EVENT, @@ -30,7 +34,7 @@ export function getMetricsProjection({ serviceName, serviceNodeName }: { - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; serviceName: string; serviceNodeName?: string; }) { diff --git a/x-pack/legacy/plugins/apm/common/projections/service_nodes.ts b/x-pack/legacy/plugins/apm/common/projections/service_nodes.ts index 10ce75785c3bc7..42fcdd38cc9fd1 100644 --- a/x-pack/legacy/plugins/apm/common/projections/service_nodes.ts +++ b/x-pack/legacy/plugins/apm/common/projections/service_nodes.ts @@ -4,7 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Setup } from '../../server/lib/helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../server/lib/helpers/setup_request'; import { SERVICE_NODE_NAME } from '../elasticsearch_fieldnames'; import { mergeProjection } from './util/merge_projection'; import { getMetricsProjection } from './metrics'; @@ -14,7 +18,7 @@ export function getServiceNodesProjection({ serviceName, serviceNodeName }: { - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; serviceName: string; serviceNodeName?: string; }) { diff --git a/x-pack/legacy/plugins/apm/common/projections/services.ts b/x-pack/legacy/plugins/apm/common/projections/services.ts index e889899e116347..3531607d59fc4e 100644 --- a/x-pack/legacy/plugins/apm/common/projections/services.ts +++ b/x-pack/legacy/plugins/apm/common/projections/services.ts @@ -4,11 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Setup } from '../../server/lib/helpers/setup_request'; +import { + Setup, + SetupUIFilters, + SetupTimeRange +} from '../../server/lib/helpers/setup_request'; import { SERVICE_NAME, PROCESSOR_EVENT } from '../elasticsearch_fieldnames'; import { rangeFilter } from '../../server/lib/helpers/range_filter'; -export function getServicesProjection({ setup }: { setup: Setup }) { +export function getServicesProjection({ + setup +}: { + setup: Setup & SetupTimeRange & SetupUIFilters; +}) { const { start, end, uiFiltersES, indices } = setup; return { diff --git a/x-pack/legacy/plugins/apm/common/projections/transaction_groups.ts b/x-pack/legacy/plugins/apm/common/projections/transaction_groups.ts index 6f7be349b0cba7..abda606f693846 100644 --- a/x-pack/legacy/plugins/apm/common/projections/transaction_groups.ts +++ b/x-pack/legacy/plugins/apm/common/projections/transaction_groups.ts @@ -4,7 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import { omit } from 'lodash'; -import { Setup } from '../../server/lib/helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../server/lib/helpers/setup_request'; import { TRANSACTION_NAME, PARENT_ID } from '../elasticsearch_fieldnames'; import { Options } from '../../server/lib/transaction_groups/fetcher'; import { getTransactionsProjection } from './transactions'; @@ -14,7 +18,7 @@ export function getTransactionGroupsProjection({ setup, options }: { - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; options: Options; }) { const transactionsProjection = getTransactionsProjection({ diff --git a/x-pack/legacy/plugins/apm/common/projections/transactions.ts b/x-pack/legacy/plugins/apm/common/projections/transactions.ts index fb249340c867c9..ecbd0c8bf1a316 100644 --- a/x-pack/legacy/plugins/apm/common/projections/transactions.ts +++ b/x-pack/legacy/plugins/apm/common/projections/transactions.ts @@ -4,7 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Setup } from '../../server/lib/helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../server/lib/helpers/setup_request'; import { SERVICE_NAME, TRANSACTION_TYPE, @@ -19,7 +23,7 @@ export function getTransactionsProjection({ transactionName, transactionType }: { - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; serviceName?: string; transactionName?: string; transactionType?: string; diff --git a/x-pack/legacy/plugins/apm/index.ts b/x-pack/legacy/plugins/apm/index.ts index e2cf907a614578..0cac20ef340d24 100644 --- a/x-pack/legacy/plugins/apm/index.ts +++ b/x-pack/legacy/plugins/apm/index.ts @@ -7,10 +7,10 @@ import { i18n } from '@kbn/i18n'; import { Server } from 'hapi'; import { resolve } from 'path'; -import { PluginInitializerContext } from '../../../../src/core/server'; +import { APMPluginContract } from '../../../plugins/apm/server/plugin'; import { LegacyPluginInitializer } from '../../../../src/legacy/types'; import mappings from './mappings.json'; -import { plugin } from './server/new-platform'; +import { makeApmUsageCollector } from './server/lib/apm_telemetry'; export const apm: LegacyPluginInitializer = kibana => { return new kibana.Plugin({ @@ -68,10 +68,6 @@ export const apm: LegacyPluginInitializer = kibana => { // enable plugin enabled: Joi.boolean().default(true), - // buckets - minimumBucketSize: Joi.number().default(15), - bucketTargetCount: Joi.number().default(15), - // index patterns autocreateApmIndexPattern: Joi.boolean().default(true), @@ -112,15 +108,11 @@ export const apm: LegacyPluginInitializer = kibana => { } } }); + makeApmUsageCollector(server); + const apmPlugin = server.newPlatform.setup.plugins + .apm as APMPluginContract; - const initializerContext = {} as PluginInitializerContext; - const legacySetup = { - server - }; - plugin(initializerContext).setup( - server.newPlatform.setup.core, - legacySetup - ); + apmPlugin.registerLegacyAPI({ server }); } }); }; diff --git a/x-pack/legacy/plugins/apm/mappings.json b/x-pack/legacy/plugins/apm/mappings.json index 02296606b1c012..61bc90da287567 100644 --- a/x-pack/legacy/plugins/apm/mappings.json +++ b/x-pack/legacy/plugins/apm/mappings.json @@ -61,9 +61,6 @@ }, "apm_oss.metricsIndices": { "type": "keyword" - }, - "apm_oss.apmAgentConfigurationIndex": { - "type": "keyword" } } } diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx index 67957ae76b1f10..6323599436ca8a 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx @@ -63,13 +63,6 @@ const APM_INDEX_LABELS = [ label: i18n.translate('xpack.apm.settings.apmIndices.metricsIndicesLabel', { defaultMessage: 'Metrics Indices' }) - }, - { - configurationName: 'apm_oss.apmAgentConfigurationIndex', - label: i18n.translate( - 'xpack.apm.settings.apmIndices.apmAgentConfigurationIndexLabel', - { defaultMessage: 'Agent Configuration Index' } - ) } ]; diff --git a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx index 637dcbcd52f583..ac4aca4c795b7e 100644 --- a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx +++ b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx @@ -71,6 +71,9 @@ export class Plugin { ); // create static index pattern and store as saved object. Not needed by APM UI but for legacy reasons in Discover, Dashboard etc. - createStaticIndexPattern(core.http); + createStaticIndexPattern(core.http).catch(e => { + // eslint-disable-next-line no-console + console.log('Error fetching static index pattern', e); + }); } } diff --git a/x-pack/legacy/plugins/apm/public/services/__test__/callApi.test.ts b/x-pack/legacy/plugins/apm/public/services/__test__/callApi.test.ts index cd681e354e2ed5..31ba1e8d40aaa3 100644 --- a/x-pack/legacy/plugins/apm/public/services/__test__/callApi.test.ts +++ b/x-pack/legacy/plugins/apm/public/services/__test__/callApi.test.ts @@ -48,7 +48,7 @@ describe('callApi', () => { it('should not add debug param for non-APM endpoints', async () => { await callApi(http, { pathname: `/api/kibana` }); - expect(http.get).toHaveBeenCalledWith('/api/kibana', {}); + expect(http.get).toHaveBeenCalledWith('/api/kibana', { query: {} }); }); }); diff --git a/x-pack/legacy/plugins/apm/public/services/__test__/callApmApi.test.ts b/x-pack/legacy/plugins/apm/public/services/__test__/callApmApi.test.ts index 22b37c275596f7..e8a9fa74bd1da5 100644 --- a/x-pack/legacy/plugins/apm/public/services/__test__/callApmApi.test.ts +++ b/x-pack/legacy/plugins/apm/public/services/__test__/callApmApi.test.ts @@ -81,10 +81,10 @@ describe('callApmApi', () => { expect.objectContaining({ pathname: '/api/apm', method: 'POST', - body: JSON.stringify({ + body: { foo: 'bar', bar: 'foo' - }) + } }) ); }); diff --git a/x-pack/legacy/plugins/apm/public/services/rest/callApi.ts b/x-pack/legacy/plugins/apm/public/services/rest/callApi.ts index 031f21e6e2feb2..e1b61d06e35590 100644 --- a/x-pack/legacy/plugins/apm/public/services/rest/callApi.ts +++ b/x-pack/legacy/plugins/apm/public/services/rest/callApi.ts @@ -20,15 +20,23 @@ function fetchOptionsWithDebug(fetchOptions: FetchOptions) { sessionStorage.getItem('apm_debug') === 'true' && startsWith(fetchOptions.pathname, '/api/apm'); - if (!debugEnabled) { - return fetchOptions; - } + const isGet = !fetchOptions.method || fetchOptions.method === 'GET'; + + // Need an empty body to pass route validation + const body = isGet + ? {} + : { + body: JSON.stringify( + fetchOptions.body || ({} as HttpFetchOptions['body']) + ) + }; return { ...fetchOptions, + ...body, query: { ...fetchOptions.query, - _debug: true + ...(debugEnabled ? { _debug: true } : {}) } }; } diff --git a/x-pack/legacy/plugins/apm/public/services/rest/createCallApmApi.ts b/x-pack/legacy/plugins/apm/public/services/rest/createCallApmApi.ts index e2084599a04998..964cc12794075f 100644 --- a/x-pack/legacy/plugins/apm/public/services/rest/createCallApmApi.ts +++ b/x-pack/legacy/plugins/apm/public/services/rest/createCallApmApi.ts @@ -22,10 +22,6 @@ export const createCallApmApi = (http: HttpServiceBase) => const { pathname, params = {}, ...opts } = options; const path = (params.path || {}) as Record; - const body = params.body - ? { body: JSON.stringify(params.body) } - : undefined; - const query = params.query ? { query: params.query } : undefined; const formattedPathname = Object.keys(path).reduce((acc, paramName) => { return acc.replace(`{${paramName}}`, path[paramName]); @@ -34,7 +30,7 @@ export const createCallApmApi = (http: HttpServiceBase) => return callApi(http, { ...opts, pathname: formattedPathname, - ...body, - ...query + body: params.body, + query: params.query }); }) as APMClient; diff --git a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx index 751756146b8fe7..321ce761422f09 100644 --- a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx @@ -14,6 +14,7 @@ import { Moment } from 'moment-timezone'; import React from 'react'; import { render, waitForElement } from 'react-testing-library'; import { MemoryRouter } from 'react-router-dom'; +import { APMConfig } from '../../../../../plugins/apm/server'; import { LocationProvider } from '../context/LocationContext'; import { PromiseReturnType } from '../../typings/common'; import { ESFilter } from '../../typings/elasticsearch'; @@ -98,10 +99,7 @@ interface MockSetup { end: number; client: any; internalClient: any; - config: { - get: any; - has: any; - }; + config: APMConfig; uiFiltersES: ESFilter[]; indices: { 'apm_oss.sourcemapIndices': string; @@ -110,7 +108,7 @@ interface MockSetup { 'apm_oss.spanIndices': string; 'apm_oss.transactionIndices': string; 'apm_oss.metricsIndices': string; - 'apm_oss.apmAgentConfigurationIndex': string; + apmAgentConfigurationIndex: string; }; } @@ -138,10 +136,12 @@ export async function inspectSearchParams( internalClient: { search: internalClientSpy } as any, - config: { - get: () => 'myIndex' as any, - has: () => true - }, + config: new Proxy( + {}, + { + get: () => 'myIndex' + } + ) as APMConfig, uiFiltersES: [ { term: { 'service.environment': 'prod' } @@ -154,7 +154,7 @@ export async function inspectSearchParams( 'apm_oss.spanIndices': 'myIndex', 'apm_oss.transactionIndices': 'myIndex', 'apm_oss.metricsIndices': 'myIndex', - 'apm_oss.apmAgentConfigurationIndex': 'myIndex' + apmAgentConfigurationIndex: 'myIndex' }, dynamicIndexPattern: null as any }; diff --git a/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/tsconfig.json b/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/tsconfig.json index e7d9abea65a3ac..c2f87503b4548f 100644 --- a/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/tsconfig.json +++ b/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../apm.tsconfig.json", "include": [ "./**/*", + "../../../plugins/apm/**/*", "../../../typings/**/*" ], "exclude": [ diff --git a/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/index.ts b/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/index.ts index 640072d6ec4d8b..de8846a8f9fb41 100644 --- a/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/index.ts @@ -4,17 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Server } from 'hapi'; import { countBy } from 'lodash'; import { SavedObjectAttributes } from 'src/core/server'; -import { CoreSetup } from 'src/core/server'; import { isAgentName } from '../../../common/agent_name'; import { getInternalSavedObjectsClient } from '../helpers/saved_objects_client'; import { APM_SERVICES_TELEMETRY_SAVED_OBJECT_TYPE, APM_SERVICES_TELEMETRY_SAVED_OBJECT_ID } from '../../../common/apm_saved_object_constants'; -import { LegacySetup } from '../../new-platform/plugin'; +import { APMLegacyServer } from '../../routes/typings'; export function createApmTelementry( agentNames: string[] = [] @@ -27,12 +25,12 @@ export function createApmTelementry( } export async function storeApmServicesTelemetry( - server: Server, + server: APMLegacyServer, apmTelemetry: SavedObjectAttributes ) { try { - const internalSavedObjectsClient = getInternalSavedObjectsClient(server); - await internalSavedObjectsClient.create( + const savedObjectsClient = getInternalSavedObjectsClient(server); + await savedObjectsClient.create( APM_SERVICES_TELEMETRY_SAVED_OBJECT_TYPE, apmTelemetry, { @@ -45,21 +43,7 @@ export async function storeApmServicesTelemetry( } } -interface LegacySetupWithUsageCollector extends LegacySetup { - server: LegacySetup['server'] & { - usage: { - collectorSet: { - makeUsageCollector: (options: unknown) => unknown; - register: (options: unknown) => unknown; - }; - }; - }; -} - -export function makeApmUsageCollector( - core: CoreSetup, - { server }: LegacySetupWithUsageCollector -) { +export function makeApmUsageCollector(server: APMLegacyServer) { const apmUsageCollector = server.usage.collectorSet.makeUsageCollector({ type: 'apm', fetch: async () => { diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__snapshots__/queries.test.ts.snap b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__snapshots__/queries.test.ts.snap index e1d7e9843bf697..0065c28f60d2d8 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__snapshots__/queries.test.ts.snap +++ b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__snapshots__/queries.test.ts.snap @@ -11,7 +11,7 @@ Object { "min": 1528113600000, }, "field": "@timestamp", - "interval": NaN, + "interval": 57600000, "min_doc_count": 0, }, }, @@ -63,7 +63,7 @@ Object { "min": 1528113600000, }, "field": "@timestamp", - "interval": NaN, + "interval": 57600000, "min_doc_count": 0, }, }, diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts index 18ffb743934c01..cf8798d445f8ae 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/__tests__/get_buckets.test.ts @@ -6,6 +6,7 @@ import { PROCESSOR_EVENT } from '../../../../../common/elasticsearch_fieldnames'; import { getBuckets } from '../get_buckets'; +import { APMConfig } from '../../../../../../../../plugins/apm/server'; describe('timeseriesFetcher', () => { let clientSpy: jest.Mock; @@ -34,10 +35,12 @@ describe('timeseriesFetcher', () => { internalClient: { search: clientSpy } as any, - config: { - get: () => 'myIndex' as any, - has: () => true - }, + config: new Proxy( + {}, + { + get: () => 'myIndex' + } + ) as APMConfig, uiFiltersES: [ { term: { 'service.environment': 'prod' } @@ -50,7 +53,7 @@ describe('timeseriesFetcher', () => { 'apm_oss.spanIndices': 'apm-*', 'apm_oss.transactionIndices': 'apm-*', 'apm_oss.metricsIndices': 'apm-*', - 'apm_oss.apmAgentConfigurationIndex': '.apm-agent-configuration' + apmAgentConfigurationIndex: '.apm-agent-configuration' }, dynamicIndexPattern: null as any } diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_buckets.ts b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_buckets.ts index 37889e69ad8f2b..9274f96d58d83c 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_buckets.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_buckets.ts @@ -11,7 +11,11 @@ import { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames'; import { rangeFilter } from '../../helpers/range_filter'; -import { Setup } from '../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../helpers/setup_request'; export async function getBuckets({ serviceName, @@ -22,7 +26,7 @@ export async function getBuckets({ serviceName: string; groupId?: string; bucketSize: number; - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; }) { const { start, end, uiFiltersES, client, indices } = setup; const filter: ESFilter[] = [ diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_distribution.ts b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_distribution.ts index 81019b52610444..6172c71a0ed152 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_distribution.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/distribution/get_distribution.ts @@ -5,12 +5,16 @@ */ import { PromiseReturnType } from '../../../../typings/common'; -import { Setup } from '../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../helpers/setup_request'; import { getBuckets } from './get_buckets'; +import { BUCKET_TARGET_COUNT } from '../../transactions/constants'; -function getBucketSize({ start, end, config }: Setup) { - const bucketTargetCount = config.get('xpack.apm.bucketTargetCount'); - return Math.floor((end - start) / bucketTargetCount); +function getBucketSize({ start, end }: SetupTimeRange) { + return Math.floor((end - start) / BUCKET_TARGET_COUNT); } export type ErrorDistributionAPIResponse = PromiseReturnType< @@ -24,9 +28,9 @@ export async function getErrorDistribution({ }: { serviceName: string; groupId?: string; - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; }) { - const bucketSize = getBucketSize(setup); + const bucketSize = getBucketSize({ start: setup.start, end: setup.end }); const { buckets, noHits } = await getBuckets({ serviceName, groupId, diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/get_error_group.ts b/x-pack/legacy/plugins/apm/server/lib/errors/get_error_group.ts index fd1199d07b95fe..8d19455651be34 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/get_error_group.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/get_error_group.ts @@ -13,7 +13,11 @@ import { import { PromiseReturnType } from '../../../typings/common'; import { APMError } from '../../../typings/es_schemas/ui/APMError'; import { rangeFilter } from '../helpers/range_filter'; -import { Setup } from '../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../helpers/setup_request'; import { getTransaction } from '../transactions/get_transaction'; export type ErrorGroupAPIResponse = PromiseReturnType; @@ -26,7 +30,7 @@ export async function getErrorGroup({ }: { serviceName: string; groupId: string; - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; }) { const { start, end, uiFiltersES, client, indices } = setup; diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts b/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts index 24e7114efddeea..aaa4ca9fb82230 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/get_error_groups.ts @@ -13,7 +13,11 @@ import { } from '../../../common/elasticsearch_fieldnames'; import { PromiseReturnType } from '../../../typings/common'; import { APMError } from '../../../typings/es_schemas/ui/APMError'; -import { Setup } from '../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../helpers/setup_request'; import { getErrorGroupsProjection } from '../../../common/projections/errors'; import { mergeProjection } from '../../../common/projections/util/merge_projection'; import { SortOptions } from '../../../typings/elasticsearch/aggregations'; @@ -31,7 +35,7 @@ export async function getErrorGroups({ serviceName: string; sortField?: string; sortDirection?: 'asc' | 'desc'; - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; }) { const { client } = setup; diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts b/x-pack/legacy/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts index 5074f9315d8aed..acbbeed2a527dc 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts @@ -11,7 +11,7 @@ import { TRANSACTION_ID } from '../../../common/elasticsearch_fieldnames'; import { rangeFilter } from '../helpers/range_filter'; -import { Setup } from '../helpers/setup_request'; +import { Setup, SetupTimeRange } from '../helpers/setup_request'; export interface ErrorsPerTransaction { [transactionId: string]: number; @@ -21,7 +21,7 @@ const includedLogLevels = ['critical', 'error', 'fatal']; export async function getTraceErrorsPerTransaction( traceId: string, - setup: Setup + setup: Setup & SetupTimeRange ): Promise { const { start, end, client, indices } = setup; diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts index b9ca268c86beb0..cee097d010212b 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts @@ -19,7 +19,6 @@ export function getUiFiltersES( uiFilters: UIFilters ) { const { kuery, environment, ...localFilterValues } = uiFilters; - const mappedFilters = localUIFilterNames .filter(name => name in localFilterValues) .map(filterName => { diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts index 9c111910f16f9c..28035ac2f9be20 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts @@ -11,14 +11,17 @@ import { IndicesDeleteParams, IndicesCreateParams } from 'elasticsearch'; -import { Legacy } from 'kibana'; -import { cloneDeep, has, isString, set, pick } from 'lodash'; +import { merge } from 'lodash'; +import { cloneDeep, isString } from 'lodash'; +import { KibanaRequest } from 'src/core/server'; import { OBSERVER_VERSION_MAJOR } from '../../../common/elasticsearch_fieldnames'; -import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; import { ESSearchResponse, ESSearchRequest } from '../../../typings/elasticsearch'; +import { APMRequestHandlerContext } from '../../routes/typings'; +import { pickKeys } from '../../../public/utils/pickKeys'; +import { getApmIndices } from '../settings/apm_indices/get_apm_indices'; // `type` was deprecated in 7.0 export type APMIndexDocumentParams = Omit, 'type'>; @@ -38,7 +41,7 @@ export function isApmIndex( function addFilterForLegacyData( apmIndices: string[], - params: SearchParams, + params: ESSearchRequest, { includeLegacyData = false } = {} ): SearchParams { // search across all data (including data) @@ -46,10 +49,18 @@ function addFilterForLegacyData( return params; } - const nextParams = cloneDeep(params); - if (!has(nextParams, 'body.query.bool.filter')) { - set(nextParams, 'body.query.bool.filter', []); - } + const nextParams = merge( + { + body: { + query: { + bool: { + filter: [] + } + } + } + }, + cloneDeep(params) + ); // add filter for omitting pre-7.x data nextParams.body.query.bool.filter.push({ @@ -61,30 +72,27 @@ function addFilterForLegacyData( // add additional params for search (aka: read) requests async function getParamsForSearchRequest( - req: Legacy.Request, - params: SearchParams, + context: APMRequestHandlerContext, + params: ESSearchRequest, apmOptions?: APMOptions ) { - const uiSettings = req.getUiSettingsService(); - const { server } = req; + const { uiSettings } = context.core; const [indices, includeFrozen] = await Promise.all([ - getApmIndices({ - config: server.config(), - savedObjectsClient: server.savedObjects.getScopedSavedObjectsClient(req) - }), - uiSettings.get('search:includeFrozen') + getApmIndices(context), + uiSettings.client.get('search:includeFrozen') ]); // Get indices for legacy data filter (only those which apply) - const apmIndices: string[] = Object.values( - pick(indices, [ + const apmIndices = Object.values( + pickKeys( + indices, 'apm_oss.sourcemapIndices', 'apm_oss.errorIndices', 'apm_oss.onboardingIndices', 'apm_oss.spanIndices', 'apm_oss.transactionIndices', 'apm_oss.metricsIndices' - ]) + ) ); return { ...addFilterForLegacyData(apmIndices, params, apmOptions), // filter out pre-7.0 data @@ -103,15 +111,18 @@ interface ClientCreateOptions { export type ESClient = ReturnType; export function getESClient( - req: Legacy.Request, + context: APMRequestHandlerContext, + request: KibanaRequest, { clientAsInternalUser = false }: ClientCreateOptions = {} ) { - const cluster = req.server.plugins.elasticsearch.getCluster('data'); - const query = req.query as Record; + const { + callAsCurrentUser, + callAsInternalUser + } = context.core.elasticsearch.dataClient; const callMethod = clientAsInternalUser - ? cluster.callWithInternalUser.bind(cluster) - : cluster.callWithRequest.bind(cluster, req); + ? callAsInternalUser + : callAsCurrentUser; return { search: async < @@ -122,17 +133,15 @@ export function getESClient( apmOptions?: APMOptions ): Promise> => { const nextParams = await getParamsForSearchRequest( - req, + context, params, apmOptions ); - if (query._debug) { + if (context.params.query._debug) { console.log(`--DEBUG ES QUERY--`); console.log( - `${req.method.toUpperCase()} ${req.url.pathname} ${JSON.stringify( - query - )}` + `${request.url.pathname} ${JSON.stringify(context.params.query)}` ); console.log(`GET ${nextParams.index}/_search`); console.log(JSON.stringify(nextParams.body, null, 4)); diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.ts index 9eada84955d26f..ced6f77944b6ce 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/saved_objects_client.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Server } from 'hapi'; +import { APMLegacyServer } from '../../routes/typings'; -export function getInternalSavedObjectsClient(server: Server) { +export function getInternalSavedObjectsClient(server: APMLegacyServer) { const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects; const { callWithInternalUser } = server.plugins.elasticsearch.getCluster( 'admin' diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts index 0745c323c7fd2b..f320712d6151f3 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts @@ -3,9 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Legacy } from 'kibana'; import { setupRequest } from './setup_request'; -import { uiSettingsServiceMock } from 'src/core/server/mocks'; +import { APMConfig } from '../../../../../../plugins/apm/server'; +import { APMRequestHandlerContext } from '../../routes/typings'; +import { KibanaRequest } from 'src/core/server'; jest.mock('../settings/apm_indices/get_apm_indices', () => ({ getApmIndices: async () => ({ @@ -15,7 +16,7 @@ jest.mock('../settings/apm_indices/get_apm_indices', () => ({ 'apm_oss.spanIndices': 'apm-*', 'apm_oss.transactionIndices': 'apm-*', 'apm_oss.metricsIndices': 'apm-*', - 'apm_oss.apmAgentConfigurationIndex': 'apm-*' + apmAgentConfigurationIndex: 'apm-*' }) })); @@ -26,37 +27,62 @@ jest.mock('../index_pattern/get_dynamic_index_pattern', () => ({ })); function getMockRequest() { - const callWithRequestSpy = jest.fn(); - const callWithInternalUserSpy = jest.fn(); - const mockRequest = ({ - params: {}, - query: {}, - server: { - config: () => ({ get: () => 'apm-*' }), - plugins: { - elasticsearch: { - getCluster: () => ({ - callWithRequest: callWithRequestSpy, - callWithInternalUser: callWithInternalUserSpy - }) + const mockContext = ({ + config: new Proxy( + {}, + { + get: () => 'apm-*' + } + ) as APMConfig, + params: { + query: { + _debug: false + } + }, + core: { + elasticsearch: { + dataClient: { + callAsCurrentUser: jest.fn(), + callAsInternalUser: jest.fn() } }, - savedObjects: { - getScopedSavedObjectsClient: () => ({ get: async () => false }) + uiSettings: { + client: { + get: jest.fn().mockResolvedValue(false) + } } - }, - getUiSettingsService: () => ({ get: async () => false }) - } as any) as Legacy.Request; + } + } as unknown) as APMRequestHandlerContext & { + core: { + elasticsearch: { + dataClient: { + callAsCurrentUser: jest.Mock; + callAsInternalUser: jest.Mock; + }; + }; + uiSettings: { + client: { + get: jest.Mock; + }; + }; + }; + }; + + const mockRequest = ({ + url: '' + } as unknown) as KibanaRequest; - return { callWithRequestSpy, callWithInternalUserSpy, mockRequest }; + return { mockContext, mockRequest }; } describe('setupRequest', () => { it('should call callWithRequest with default args', async () => { - const { mockRequest, callWithRequestSpy } = getMockRequest(); - const { client } = await setupRequest(mockRequest); + const { mockContext, mockRequest } = getMockRequest(); + const { client } = await setupRequest(mockContext, mockRequest); await client.search({ index: 'apm-*', body: { foo: 'bar' } } as any); - expect(callWithRequestSpy).toHaveBeenCalledWith(mockRequest, 'search', { + expect( + mockContext.core.elasticsearch.dataClient.callAsCurrentUser + ).toHaveBeenCalledWith('search', { index: 'apm-*', body: { foo: 'bar', @@ -71,13 +97,15 @@ describe('setupRequest', () => { }); it('should call callWithInternalUser with default args', async () => { - const { mockRequest, callWithInternalUserSpy } = getMockRequest(); - const { internalClient } = await setupRequest(mockRequest); + const { mockContext, mockRequest } = getMockRequest(); + const { internalClient } = await setupRequest(mockContext, mockRequest); await internalClient.search({ index: 'apm-*', body: { foo: 'bar' } } as any); - expect(callWithInternalUserSpy).toHaveBeenCalledWith('search', { + expect( + mockContext.core.elasticsearch.dataClient.callAsInternalUser + ).toHaveBeenCalledWith('search', { index: 'apm-*', body: { foo: 'bar', @@ -94,13 +122,15 @@ describe('setupRequest', () => { describe('observer.version_major filter', () => { describe('if index is apm-*', () => { it('should merge `observer.version_major` filter with existing boolean filters', async () => { - const { mockRequest, callWithRequestSpy } = getMockRequest(); - const { client } = await setupRequest(mockRequest); + const { mockContext, mockRequest } = getMockRequest(); + const { client } = await setupRequest(mockContext, mockRequest); await client.search({ index: 'apm-*', body: { query: { bool: { filter: [{ term: 'someTerm' }] } } } }); - const params = callWithRequestSpy.mock.calls[0][2]; + const params = + mockContext.core.elasticsearch.dataClient.callAsCurrentUser.mock + .calls[0][1]; expect(params.body).toEqual({ query: { bool: { @@ -114,10 +144,12 @@ describe('setupRequest', () => { }); it('should add `observer.version_major` filter if none exists', async () => { - const { mockRequest, callWithRequestSpy } = getMockRequest(); - const { client } = await setupRequest(mockRequest); + const { mockContext, mockRequest } = getMockRequest(); + const { client } = await setupRequest(mockContext, mockRequest); await client.search({ index: 'apm-*' }); - const params = callWithRequestSpy.mock.calls[0][2]; + const params = + mockContext.core.elasticsearch.dataClient.callAsCurrentUser.mock + .calls[0][1]; expect(params.body).toEqual({ query: { bool: { @@ -128,8 +160,8 @@ describe('setupRequest', () => { }); it('should not add `observer.version_major` filter if `includeLegacyData=true`', async () => { - const { mockRequest, callWithRequestSpy } = getMockRequest(); - const { client } = await setupRequest(mockRequest); + const { mockContext, mockRequest } = getMockRequest(); + const { client } = await setupRequest(mockContext, mockRequest); await client.search( { index: 'apm-*', @@ -139,7 +171,9 @@ describe('setupRequest', () => { includeLegacyData: true } ); - const params = callWithRequestSpy.mock.calls[0][2]; + const params = + mockContext.core.elasticsearch.dataClient.callAsCurrentUser.mock + .calls[0][1]; expect(params.body).toEqual({ query: { bool: { filter: [{ term: 'someTerm' }] } } }); @@ -147,15 +181,17 @@ describe('setupRequest', () => { }); it('if index is not an APM index, it should not add `observer.version_major` filter', async () => { - const { mockRequest, callWithRequestSpy } = getMockRequest(); - const { client } = await setupRequest(mockRequest); + const { mockContext, mockRequest } = getMockRequest(); + const { client } = await setupRequest(mockContext, mockRequest); await client.search({ index: '.ml-*', body: { query: { bool: { filter: [{ term: 'someTerm' }] } } } }); - const params = callWithRequestSpy.mock.calls[0][2]; + const params = + mockContext.core.elasticsearch.dataClient.callAsCurrentUser.mock + .calls[0][1]; expect(params.body).toEqual({ query: { bool: { @@ -168,28 +204,34 @@ describe('setupRequest', () => { describe('ignore_throttled', () => { it('should set `ignore_throttled=true` if `includeFrozen=false`', async () => { - const { mockRequest, callWithRequestSpy } = getMockRequest(); + const { mockContext, mockRequest } = getMockRequest(); - const uiSettingsService = uiSettingsServiceMock.createClient(); // mock includeFrozen to return false - uiSettingsService.get.mockResolvedValue(false); - mockRequest.getUiSettingsService = () => uiSettingsService; - const { client } = await setupRequest(mockRequest); + mockContext.core.uiSettings.client.get.mockResolvedValue(false); + + const { client } = await setupRequest(mockContext, mockRequest); + await client.search({}); - const params = callWithRequestSpy.mock.calls[0][2]; + + const params = + mockContext.core.elasticsearch.dataClient.callAsCurrentUser.mock + .calls[0][1]; expect(params.ignore_throttled).toBe(true); }); it('should set `ignore_throttled=false` if `includeFrozen=true`', async () => { - const { mockRequest, callWithRequestSpy } = getMockRequest(); + const { mockContext, mockRequest } = getMockRequest(); - const uiSettingsService = uiSettingsServiceMock.createClient(); // mock includeFrozen to return true - uiSettingsService.get.mockResolvedValue(true); - mockRequest.getUiSettingsService = () => uiSettingsService; - const { client } = await setupRequest(mockRequest); + mockContext.core.uiSettings.client.get.mockResolvedValue(true); + + const { client } = await setupRequest(mockContext, mockRequest); + await client.search({}); - const params = callWithRequestSpy.mock.calls[0][2]; + + const params = + mockContext.core.elasticsearch.dataClient.callAsCurrentUser.mock + .calls[0][1]; expect(params.ignore_throttled).toBe(false); }); }); diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts index c5f76ec51b279a..8f19f4baed7ee9 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts @@ -4,21 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Legacy } from 'kibana'; import moment from 'moment'; -import { KibanaConfig } from 'src/legacy/server/kbn_server'; -import { getESClient } from './es_client'; -import { getUiFiltersES } from './convert_ui_filters/get_ui_filters_es'; +import { KibanaRequest } from 'src/core/server'; +import { StaticIndexPattern } from 'ui/index_patterns'; +import { IIndexPattern } from 'src/plugins/data/common'; +import { APMConfig } from '../../../../../../plugins/apm/server'; import { getApmIndices, ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices'; import { ESFilter } from '../../../typings/elasticsearch'; import { ESClient } from './es_client'; -import { StaticIndexPattern } from '../../../../../../../src/legacy/core_plugins/data/public'; -import { getDynamicIndexPattern } from '../index_pattern/get_dynamic_index_pattern'; -import { IIndexPattern } from '../../../../../../../src/plugins/data/common'; +import { getUiFiltersES } from './convert_ui_filters/get_ui_filters_es'; +import { APMRequestHandlerContext } from '../../routes/typings'; +import { getESClient } from './es_client'; import { ProcessorEvent } from '../../../common/processor_event'; +import { getDynamicIndexPattern } from '../index_pattern/get_dynamic_index_pattern'; function decodeUiFilters( indexPattern: StaticIndexPattern | undefined, @@ -30,52 +31,73 @@ function decodeUiFilters( const uiFilters = JSON.parse(uiFiltersEncoded); return getUiFiltersES(indexPattern, uiFilters); } - -export interface APMRequestQuery { - _debug?: string; - start?: string; - end?: string; - uiFilters?: string; - processorEvent?: ProcessorEvent; -} // Explicitly type Setup to prevent TS initialization errors // https://github.com/microsoft/TypeScript/issues/34933 export interface Setup { - start: number; - end: number; - uiFiltersES: ESFilter[]; client: ESClient; internalClient: ESClient; - config: KibanaConfig; + config: APMConfig; indices: ApmIndicesConfig; dynamicIndexPattern?: IIndexPattern; } -export async function setupRequest(req: Legacy.Request): Promise { - const query = (req.query as unknown) as APMRequestQuery; - const { server } = req; - const savedObjectsClient = server.savedObjects.getScopedSavedObjectsClient( - req - ); - const config = server.config(); - const indices = await getApmIndices({ config, savedObjectsClient }); +export interface SetupTimeRange { + start: number; + end: number; +} +export interface SetupUIFilters { + uiFiltersES: ESFilter[]; +} + +interface SetupRequestParams { + query?: { + _debug?: boolean; + start?: string; + end?: string; + uiFilters?: string; + processorEvent?: ProcessorEvent; + }; +} + +type InferSetup = Setup & + (TParams extends { query: { start: string } } ? { start: number } : {}) & + (TParams extends { query: { end: string } } ? { end: number } : {}) & + (TParams extends { query: { uiFilters: string } } + ? { uiFiltersES: ESFilter[] } + : {}); + +export async function setupRequest( + context: APMRequestHandlerContext, + request: KibanaRequest +): Promise> { + const { config } = context; + const { query } = context.params; + + const indices = await getApmIndices(context); const dynamicIndexPattern = await getDynamicIndexPattern({ - request: req, + context, indices, processorEvent: query.processorEvent }); + const uiFiltersES = decodeUiFilters(dynamicIndexPattern, query.uiFilters); - return { - start: moment.utc(query.start).valueOf(), - end: moment.utc(query.end).valueOf(), - uiFiltersES, - client: getESClient(req, { clientAsInternalUser: false }), - internalClient: getESClient(req, { clientAsInternalUser: true }), - config, + const coreSetupRequest = { indices, + client: getESClient(context, request, { clientAsInternalUser: false }), + internalClient: getESClient(context, request, { + clientAsInternalUser: true + }), + config, dynamicIndexPattern }; + + return { + ...('start' in query ? { start: moment.utc(query.start).valueOf() } : {}), + ...('end' in query ? { end: moment.utc(query.end).valueOf() } : {}), + ...('uiFilters' in query ? { uiFiltersES } : {}), + ...coreSetupRequest + } as InferSetup; } diff --git a/x-pack/legacy/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts b/x-pack/legacy/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts index 83294ede080c7a..2a31563b53c2c0 100644 --- a/x-pack/legacy/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/index_pattern/create_static_index_pattern.test.ts @@ -4,14 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Server } from 'hapi'; import { createStaticIndexPattern } from './create_static_index_pattern'; import { Setup } from '../helpers/setup_request'; import * as savedObjectsClient from '../helpers/saved_objects_client'; import * as HistoricalAgentData from '../services/get_services/has_historical_agent_data'; +import { APMRequestHandlerContext } from '../../routes/typings'; -function getMockConfig(config: Record) { - return () => ({ get: (key: string) => config[key] }); +function getMockContext(config: Record) { + return ({ + config, + __LEGACY: { + server: { + savedObjects: { + getSavedObjectsRepository: jest.fn() + } + } + } + } as unknown) as APMRequestHandlerContext; } describe('createStaticIndexPattern', () => { @@ -27,46 +36,40 @@ describe('createStaticIndexPattern', () => { it(`should not create index pattern if 'xpack.apm.autocreateApmIndexPattern=false'`, async () => { const setup = {} as Setup; - const server = { - config: getMockConfig({ - 'xpack.apm.autocreateApmIndexPattern': false - }) - } as Server; - await createStaticIndexPattern(setup, server); + const context = getMockContext({ + 'xpack.apm.autocreateApmIndexPattern': false + }); + await createStaticIndexPattern(setup, context); expect(createSavedObject).not.toHaveBeenCalled(); }); it(`should not create index pattern if no APM data is found`, async () => { const setup = {} as Setup; - const server = { - config: getMockConfig({ - 'xpack.apm.autocreateApmIndexPattern': true - }) - } as Server; + const context = getMockContext({ + 'xpack.apm.autocreateApmIndexPattern': true + }); // does not have APM data jest .spyOn(HistoricalAgentData, 'hasHistoricalAgentData') .mockResolvedValue(false); - await createStaticIndexPattern(setup, server); + await createStaticIndexPattern(setup, context); expect(createSavedObject).not.toHaveBeenCalled(); }); it(`should create index pattern`, async () => { const setup = {} as Setup; - const server = { - config: getMockConfig({ - 'xpack.apm.autocreateApmIndexPattern': true - }) - } as Server; + const context = getMockContext({ + 'xpack.apm.autocreateApmIndexPattern': true + }); // does have APM data jest .spyOn(HistoricalAgentData, 'hasHistoricalAgentData') .mockResolvedValue(true); - await createStaticIndexPattern(setup, server); + await createStaticIndexPattern(setup, context); expect(createSavedObject).toHaveBeenCalled(); }); diff --git a/x-pack/legacy/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts b/x-pack/legacy/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts index 709d2872839329..562eb8850aa0c5 100644 --- a/x-pack/legacy/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts +++ b/x-pack/legacy/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts @@ -3,7 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Server } from 'hapi'; import { getInternalSavedObjectsClient } from '../helpers/saved_objects_client'; import apmIndexPattern from '../../../../../../../src/legacy/core_plugins/kibana/server/tutorials/apm/index_pattern.json'; import { APM_STATIC_INDEX_PATTERN_ID } from '../../../common/index_pattern_constants'; @@ -11,15 +10,16 @@ import { APM_STATIC_INDEX_PATTERN_ID } from '../../../common/index_pattern_const import { SavedObjectsErrorHelpers } from '../../../../../../../src/core/server/saved_objects'; import { hasHistoricalAgentData } from '../services/get_services/has_historical_agent_data'; import { Setup } from '../helpers/setup_request'; +import { APMRequestHandlerContext } from '../../routes/typings'; export async function createStaticIndexPattern( setup: Setup, - server: Server + context: APMRequestHandlerContext ): Promise { - const config = server.config(); + const { config } = context; // don't autocreate APM index pattern if it's been disabled via the config - if (!config.get('xpack.apm.autocreateApmIndexPattern')) { + if (!config['xpack.apm.autocreateApmIndexPattern']) { return; } @@ -31,8 +31,10 @@ export async function createStaticIndexPattern( } try { - const apmIndexPatternTitle = config.get('apm_oss.indexPattern'); - const internalSavedObjectsClient = getInternalSavedObjectsClient(server); + const apmIndexPatternTitle = config['apm_oss.indexPattern']; + const internalSavedObjectsClient = getInternalSavedObjectsClient( + context.__LEGACY.server + ); await internalSavedObjectsClient.create( 'index-pattern', { diff --git a/x-pack/legacy/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts b/x-pack/legacy/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts index b0a6eb7aebe8b1..f113e645ed95fb 100644 --- a/x-pack/legacy/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts +++ b/x-pack/legacy/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Legacy } from 'kibana'; import { StaticIndexPattern } from 'ui/index_patterns'; import { APICaller } from 'src/core/server'; import LRU from 'lru-cache'; @@ -14,6 +13,7 @@ import { } from '../../../../../../../src/plugins/data/server'; import { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices'; import { ProcessorEvent } from '../../../common/processor_event'; +import { APMRequestHandlerContext } from '../../routes/typings'; const cache = new LRU({ max: 100, @@ -22,11 +22,11 @@ const cache = new LRU({ // TODO: this is currently cached globally. In the future we might want to cache this per user export const getDynamicIndexPattern = async ({ - request, + context, indices, processorEvent }: { - request: Legacy.Request; + context: APMRequestHandlerContext; indices: ApmIndicesConfig; processorEvent?: ProcessorEvent; }) => { @@ -39,9 +39,7 @@ export const getDynamicIndexPattern = async ({ const indexPatternsFetcher = new IndexPatternsFetcher( (...rest: Parameters) => - request.server.plugins.elasticsearch - .getCluster('data') - .callWithRequest(request, ...rest) + context.core.elasticsearch.adminClient.callAsCurrentUser(...rest) ); // Since `getDynamicIndexPattern` is called in setup_request (and thus by every endpoint) diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/default.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/default.ts index f55d3320c7f4e3..72aa93d7f6ce33 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/default.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/default.ts @@ -4,12 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Setup } from '../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../helpers/setup_request'; import { getCPUChartData } from './shared/cpu'; import { getMemoryChartData } from './shared/memory'; export async function getDefaultMetricsCharts( - setup: Setup, + setup: Setup & SetupTimeRange & SetupUIFilters, serviceName: string ) { const charts = await Promise.all([ diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts index 8cff6e5d3aa805..8ffc115a193483 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/fetchAndTransformGcMetrics.ts @@ -11,7 +11,11 @@ import { sum, round } from 'lodash'; import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { Setup } from '../../../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../../../helpers/setup_request'; import { getMetricsDateHistogramParams } from '../../../../helpers/metrics'; import { ChartBase } from '../../../types'; import { getMetricsProjection } from '../../../../../../common/projections/metrics'; @@ -32,7 +36,7 @@ export async function fetchAndTransformGcMetrics({ chartBase, fieldName }: { - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; serviceName: string; serviceNodeName?: string; chartBase: ChartBase; diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/getGcRateChart.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/getGcRateChart.ts index 642c6a901da9da..21417891fa15f7 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/getGcRateChart.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/getGcRateChart.ts @@ -7,7 +7,11 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; import { METRIC_JAVA_GC_COUNT } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../../../helpers/setup_request'; import { fetchAndTransformGcMetrics } from './fetchAndTransformGcMetrics'; import { ChartBase } from '../../../types'; @@ -31,7 +35,7 @@ const chartBase: ChartBase = { }; const getGcRateChart = ( - setup: Setup, + setup: Setup & SetupTimeRange & SetupUIFilters, serviceName: string, serviceNodeName?: string ) => { diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/getGcTimeChart.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/getGcTimeChart.ts index b6e992acf62a98..ea7557fabacd04 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/getGcTimeChart.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/gc/getGcTimeChart.ts @@ -7,7 +7,11 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; import { METRIC_JAVA_GC_TIME } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../../../helpers/setup_request'; import { fetchAndTransformGcMetrics } from './fetchAndTransformGcMetrics'; import { ChartBase } from '../../../types'; @@ -31,7 +35,7 @@ const chartBase: ChartBase = { }; const getGcTimeChart = ( - setup: Setup, + setup: Setup & SetupTimeRange & SetupUIFilters, serviceName: string, serviceNodeName?: string ) => { diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts index ca98a486b3a588..901812815b3f3b 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/heap_memory/index.ts @@ -12,7 +12,11 @@ import { METRIC_JAVA_HEAP_MEMORY_USED, SERVICE_AGENT_NAME } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../../../helpers/setup_request'; import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; import { ChartBase } from '../../../types'; @@ -51,7 +55,7 @@ const chartBase: ChartBase = { }; export async function getHeapMemoryChart( - setup: Setup, + setup: Setup & SetupTimeRange & SetupUIFilters, serviceName: string, serviceNodeName?: string ) { diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/index.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/index.ts index e8f9e4345d06c0..191a7a2c14d23f 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/index.ts @@ -5,7 +5,11 @@ */ import { getHeapMemoryChart } from './heap_memory'; -import { Setup } from '../../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../../helpers/setup_request'; import { getNonHeapMemoryChart } from './non_heap_memory'; import { getThreadCountChart } from './thread_count'; import { getCPUChartData } from '../shared/cpu'; @@ -14,7 +18,7 @@ import { getGcRateChart } from './gc/getGcRateChart'; import { getGcTimeChart } from './gc/getGcTimeChart'; export async function getJavaMetricsCharts( - setup: Setup, + setup: Setup & SetupTimeRange & SetupUIFilters, serviceName: string, serviceNodeName?: string ) { diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts index 899852e1d5659b..7ff4e073e919b5 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/non_heap_memory/index.ts @@ -12,7 +12,11 @@ import { METRIC_JAVA_NON_HEAP_MEMORY_USED, SERVICE_AGENT_NAME } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../../../helpers/setup_request'; import { ChartBase } from '../../../types'; import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; @@ -48,7 +52,7 @@ const chartBase: ChartBase = { }; export async function getNonHeapMemoryChart( - setup: Setup, + setup: Setup & SetupUIFilters & SetupTimeRange, serviceName: string, serviceNodeName?: string ) { diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts index eb0984d7aaf59e..cf8e120b00e0d3 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/java/thread_count/index.ts @@ -10,7 +10,11 @@ import { METRIC_JAVA_THREAD_COUNT, SERVICE_AGENT_NAME } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../../../helpers/setup_request'; import { ChartBase } from '../../../types'; import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; @@ -40,7 +44,7 @@ const chartBase: ChartBase = { }; export async function getThreadCountChart( - setup: Setup, + setup: Setup & SetupTimeRange & SetupUIFilters, serviceName: string, serviceNodeName?: string ) { diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts index 5f4ad0af474c19..179ed77eedbb32 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/shared/cpu/index.ts @@ -10,7 +10,11 @@ import { METRIC_SYSTEM_CPU_PERCENT, METRIC_PROCESS_CPU_PERCENT } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../../../helpers/setup_request'; import { ChartBase } from '../../../types'; import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; @@ -52,7 +56,7 @@ const chartBase: ChartBase = { }; export async function getCPUChartData( - setup: Setup, + setup: Setup & SetupTimeRange & SetupUIFilters, serviceName: string, serviceNodeName?: string ) { diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts index bf0f21dcb3d423..8c6ed2ebcec75b 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts @@ -9,7 +9,11 @@ import { METRIC_SYSTEM_FREE_MEMORY, METRIC_SYSTEM_TOTAL_MEMORY } from '../../../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../../../helpers/setup_request'; import { ChartBase } from '../../../types'; import { fetchAndTransformMetrics } from '../../../fetch_and_transform_metrics'; @@ -45,7 +49,7 @@ const percentUsedScript = { }; export async function getMemoryChartData( - setup: Setup, + setup: Setup & SetupTimeRange & SetupUIFilters, serviceName: string, serviceNodeName?: string ) { diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts index 3d425e50bc60af..76460bb40bedb2 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/fetch_and_transform_metrics.ts @@ -5,7 +5,11 @@ */ import { Unionize } from 'utility-types'; -import { Setup } from '../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../helpers/setup_request'; import { getMetricsDateHistogramParams } from '../helpers/metrics'; import { ChartBase } from './types'; import { transformDataToMetricsChart } from './transform_metrics_chart'; @@ -39,7 +43,7 @@ export async function fetchAndTransformMetrics({ aggs, additionalFilters = [] }: { - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; serviceName: string; serviceNodeName?: string; chartBase: ChartBase; diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent.ts index 34c8b2f867fbb1..e0b496754fc9e7 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent.ts @@ -3,7 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Setup } from '../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../helpers/setup_request'; import { getJavaMetricsCharts } from './by_agent/java'; import { getDefaultMetricsCharts } from './by_agent/default'; import { GenericMetricsChart } from './transform_metrics_chart'; @@ -18,7 +22,7 @@ export async function getMetricsChartDataByAgent({ serviceNodeName, agentName }: { - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; serviceName: string; serviceNodeName?: string; agentName: string; diff --git a/x-pack/legacy/plugins/apm/server/lib/service_nodes/index.ts b/x-pack/legacy/plugins/apm/server/lib/service_nodes/index.ts index 1e415252200ce4..b674afe635bcdf 100644 --- a/x-pack/legacy/plugins/apm/server/lib/service_nodes/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/service_nodes/index.ts @@ -4,7 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Setup } from '../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../helpers/setup_request'; import { getServiceNodesProjection } from '../../../common/projections/service_nodes'; import { mergeProjection } from '../../../common/projections/util/merge_projection'; import { SERVICE_NODE_NAME_MISSING } from '../../../common/service_nodes'; @@ -19,7 +23,7 @@ const getServiceNodes = async ({ setup, serviceName }: { - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; serviceName: string; }) => { const { client } = setup; diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_service_agent_name.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_service_agent_name.ts index 9e070f936a25f0..a1a2c1a38b3d41 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_service_agent_name.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_service_agent_name.ts @@ -9,9 +9,12 @@ import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; import { rangeFilter } from '../helpers/range_filter'; -import { Setup } from '../helpers/setup_request'; +import { Setup, SetupTimeRange } from '../helpers/setup_request'; -export async function getServiceAgentName(serviceName: string, setup: Setup) { +export async function getServiceAgentName( + serviceName: string, + setup: Setup & SetupTimeRange +) { const { start, end, client, indices } = setup; const params = { diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_service_node_metadata.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_service_node_metadata.ts index e93f6b4a1c17ca..7120d3bca6c259 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_service_node_metadata.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_service_node_metadata.ts @@ -4,7 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Setup } from '../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../helpers/setup_request'; import { HOST_NAME, CONTAINER_ID @@ -20,7 +24,7 @@ export async function getServiceNodeMetadata({ }: { serviceName: string; serviceNodeName: string; - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; }) { const { client } = setup; diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_service_transaction_types.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_service_transaction_types.ts index 098342bf0221d7..60f6e63bb7b256 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_service_transaction_types.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_service_transaction_types.ts @@ -9,11 +9,11 @@ import { TRANSACTION_TYPE } from '../../../common/elasticsearch_fieldnames'; import { rangeFilter } from '../helpers/range_filter'; -import { Setup } from '../helpers/setup_request'; +import { Setup, SetupTimeRange } from '../helpers/setup_request'; export async function getServiceTransactionTypes( serviceName: string, - setup: Setup + setup: Setup & SetupTimeRange ) { const { start, end, client, indices } = setup; diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts index 6611ae76bc3395..8e578a839ae56f 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts @@ -12,11 +12,17 @@ import { TRANSACTION_DURATION } from '../../../../common/elasticsearch_fieldnames'; import { PromiseReturnType } from '../../../../typings/common'; -import { Setup } from '../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../helpers/setup_request'; import { getServicesProjection } from '../../../../common/projections/services'; export type ServiceListAPIResponse = PromiseReturnType; -export async function getServicesItems(setup: Setup) { +export async function getServicesItems( + setup: Setup & SetupTimeRange & SetupUIFilters +) { const { start, end, client } = setup; const projection = getServicesProjection({ setup }); diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_services/index.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_services/index.ts index ffa9555c29070f..d9fc89062cf888 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_services/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_services/index.ts @@ -6,26 +6,33 @@ import { isEmpty } from 'lodash'; import { PromiseReturnType } from '../../../../typings/common'; -import { Setup } from '../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../helpers/setup_request'; import { hasHistoricalAgentData } from './has_historical_agent_data'; import { getLegacyDataStatus } from './get_legacy_data_status'; import { getServicesItems } from './get_services_items'; export type ServiceListAPIResponse = PromiseReturnType; -export async function getServices(setup: Setup) { - const items = await getServicesItems(setup); - const hasLegacyData = await getLegacyDataStatus(setup); - // conditionally check for historical data if no services were found in the current time range +export async function getServices( + setup: Setup & SetupTimeRange & SetupUIFilters +) { + const [items, hasLegacyData] = await Promise.all([ + getServicesItems(setup), + getLegacyDataStatus(setup) + ]); + const noDataInCurrentTimeRange = isEmpty(items); - let hasHistorialAgentData = true; - if (noDataInCurrentTimeRange) { - hasHistorialAgentData = await hasHistoricalAgentData(setup); - } + const hasHistoricalData = noDataInCurrentTimeRange + ? await hasHistoricalAgentData(setup) + : true; return { items, - hasHistoricalData: hasHistorialAgentData, + hasHistoricalData, hasLegacyData }; } diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts index 434eda8c0f46eb..52ba22cbc0b998 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts @@ -4,31 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup } from 'src/core/server'; +import { IClusterClient } from 'src/core/server'; +import { APMConfig } from '../../../../../../../plugins/apm/server'; import { CallCluster } from '../../../../../../../../src/legacy/core_plugins/elasticsearch'; -import { getApmIndices } from '../apm_indices/get_apm_indices'; -import { LegacySetup } from '../../../new-platform/plugin'; -import { getInternalSavedObjectsClient } from '../../helpers/saved_objects_client'; +import { getApmIndicesConfig } from '../apm_indices/get_apm_indices'; -export async function createApmAgentConfigurationIndex( - core: CoreSetup, - { server }: LegacySetup -) { +export async function createApmAgentConfigurationIndex({ + esClient, + config +}: { + esClient: IClusterClient; + config: APMConfig; +}) { try { - const config = server.config(); - const internalSavedObjectsClient = getInternalSavedObjectsClient(server); - const indices = await getApmIndices({ - savedObjectsClient: internalSavedObjectsClient, - config - }); - const index = indices['apm_oss.apmAgentConfigurationIndex']; - const { callWithInternalUser } = server.plugins.elasticsearch.getCluster( - 'admin' - ); - const indexExists = await callWithInternalUser('indices.exists', { index }); + const index = getApmIndicesConfig(config).apmAgentConfigurationIndex; + const { callAsInternalUser } = esClient; + const indexExists = await callAsInternalUser('indices.exists', { index }); const result = indexExists - ? await updateExistingIndex(index, callWithInternalUser) - : await createNewIndex(index, callWithInternalUser); + ? await updateExistingIndex(index, callAsInternalUser) + : await createNewIndex(index, callAsInternalUser); if (!result.acknowledged) { const resultError = diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts index 23faa4b74cf8fb..5a67f78de6f652 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts @@ -25,7 +25,7 @@ export async function createOrUpdateConfiguration({ const params: APMIndexDocumentParams = { refresh: true, - index: indices['apm_oss.apmAgentConfigurationIndex'], + index: indices.apmAgentConfigurationIndex, body: { agent_name: configuration.agent_name, service: { diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts index ed20a58b271e10..293c01d4b61d53 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/delete_configuration.ts @@ -17,7 +17,7 @@ export async function deleteConfiguration({ const params = { refresh: 'wait_for', - index: indices['apm_oss.apmAgentConfigurationIndex'], + index: indices.apmAgentConfigurationIndex, id: configurationId }; diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts index fc3b62738f8fec..f54217461510fd 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/get_environments/get_existing_environments_for_service.ts @@ -25,7 +25,7 @@ export async function getExistingEnvironmentsForService({ : { must_not: [{ exists: { field: SERVICE_NAME } }] }; const params = { - index: indices['apm_oss.apmAgentConfigurationIndex'], + index: indices.apmAgentConfigurationIndex, body: { size: 0, query: { bool }, diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts index dd4d019ef7263b..12faa9fba10746 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/list_configurations.ts @@ -15,7 +15,7 @@ export async function listConfigurations({ setup }: { setup: Setup }) { const { internalClient, indices } = setup; const params = { - index: indices['apm_oss.apmAgentConfigurationIndex'] + index: indices.apmAgentConfigurationIndex }; const resp = await internalClient.search(params); diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts index b7b9c21172140a..b6aecd1d7f0ca0 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts @@ -19,7 +19,7 @@ export async function markAppliedByAgent({ const { internalClient, indices } = setup; const params = { - index: indices['apm_oss.apmAgentConfigurationIndex'], + index: indices.apmAgentConfigurationIndex, id, // by specifying the `id` elasticsearch will do an "upsert" body: { ...body, diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts index 969bbc542f8a6e..a02dd7af755e0f 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts @@ -33,7 +33,7 @@ export async function searchConfigurations({ : []; const params = { - index: indices['apm_oss.apmAgentConfigurationIndex'], + index: indices.apmAgentConfigurationIndex, body: { query: { bool: { diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts index e942a26da373ef..0ed30ec4cdd27d 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts @@ -5,13 +5,15 @@ */ import { merge } from 'lodash'; -import { KibanaConfig } from 'src/legacy/server/kbn_server'; import { Server } from 'hapi'; +import { SavedObjectsClientContract } from 'kibana/server'; import { PromiseReturnType } from '../../../../typings/common'; import { APM_INDICES_SAVED_OBJECT_TYPE, APM_INDICES_SAVED_OBJECT_ID } from '../../../../common/apm_saved_object_constants'; +import { APMConfig } from '../../../../../../../plugins/apm/server'; +import { APMRequestHandlerContext } from '../../../routes/typings'; export interface ApmIndicesConfig { 'apm_oss.sourcemapIndices': string; @@ -20,7 +22,7 @@ export interface ApmIndicesConfig { 'apm_oss.spanIndices': string; 'apm_oss.transactionIndices': string; 'apm_oss.metricsIndices': string; - 'apm_oss.apmAgentConfigurationIndex': string; + apmAgentConfigurationIndex: string; } export type ApmIndicesName = keyof ApmIndicesConfig; @@ -30,7 +32,7 @@ export type ScopedSavedObjectsClient = ReturnType< >; async function getApmIndicesSavedObject( - savedObjectsClient: ScopedSavedObjectsClient + savedObjectsClient: SavedObjectsClientContract ) { const apmIndices = await savedObjectsClient.get>( APM_INDICES_SAVED_OBJECT_TYPE, @@ -39,39 +41,28 @@ async function getApmIndicesSavedObject( return apmIndices.attributes; } -function getApmIndicesConfig(config: KibanaConfig): ApmIndicesConfig { +export function getApmIndicesConfig(config: APMConfig): ApmIndicesConfig { return { - 'apm_oss.sourcemapIndices': config.get('apm_oss.sourcemapIndices'), - 'apm_oss.errorIndices': config.get('apm_oss.errorIndices'), - 'apm_oss.onboardingIndices': config.get( - 'apm_oss.onboardingIndices' - ), - 'apm_oss.spanIndices': config.get('apm_oss.spanIndices'), - 'apm_oss.transactionIndices': config.get( - 'apm_oss.transactionIndices' - ), - 'apm_oss.metricsIndices': config.get('apm_oss.metricsIndices'), - 'apm_oss.apmAgentConfigurationIndex': config.get( - 'apm_oss.apmAgentConfigurationIndex' - ) + 'apm_oss.sourcemapIndices': config['apm_oss.sourcemapIndices'], + 'apm_oss.errorIndices': config['apm_oss.errorIndices'], + 'apm_oss.onboardingIndices': config['apm_oss.onboardingIndices'], + 'apm_oss.spanIndices': config['apm_oss.spanIndices'], + 'apm_oss.transactionIndices': config['apm_oss.transactionIndices'], + 'apm_oss.metricsIndices': config['apm_oss.metricsIndices'], + // system indices, not configurable + apmAgentConfigurationIndex: '.apm-agent-configuration' }; } -export async function getApmIndices({ - savedObjectsClient, - config -}: { - savedObjectsClient: ScopedSavedObjectsClient; - config: KibanaConfig; -}) { +export async function getApmIndices(context: APMRequestHandlerContext) { try { const apmIndicesSavedObject = await getApmIndicesSavedObject( - savedObjectsClient + context.core.savedObjects.client ); - const apmIndicesConfig = getApmIndicesConfig(config); + const apmIndicesConfig = getApmIndicesConfig(context.config); return merge({}, apmIndicesConfig, apmIndicesSavedObject); } catch (error) { - return getApmIndicesConfig(config); + return getApmIndicesConfig(context.config); } } @@ -81,20 +72,19 @@ const APM_UI_INDICES: ApmIndicesName[] = [ 'apm_oss.onboardingIndices', 'apm_oss.spanIndices', 'apm_oss.transactionIndices', - 'apm_oss.metricsIndices', - 'apm_oss.apmAgentConfigurationIndex' + 'apm_oss.metricsIndices' ]; export async function getApmIndexSettings({ - config, - savedObjectsClient + context }: { - config: KibanaConfig; - savedObjectsClient: ScopedSavedObjectsClient; + context: APMRequestHandlerContext; }) { let apmIndicesSavedObject: PromiseReturnType; try { - apmIndicesSavedObject = await getApmIndicesSavedObject(savedObjectsClient); + apmIndicesSavedObject = await getApmIndicesSavedObject( + context.core.savedObjects.client + ); } catch (error) { if (error.output && error.output.statusCode === 404) { apmIndicesSavedObject = {}; @@ -102,7 +92,7 @@ export async function getApmIndexSettings({ throw error; } } - const apmIndicesConfig = getApmIndicesConfig(config); + const apmIndicesConfig = getApmIndicesConfig(context.config); return APM_UI_INDICES.map(configurationName => ({ configurationName, diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/save_apm_indices.ts b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/save_apm_indices.ts index e57e64942ab898..2fdfd79ce933b4 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/save_apm_indices.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/apm_indices/save_apm_indices.ts @@ -4,17 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ApmIndicesConfig, ScopedSavedObjectsClient } from './get_apm_indices'; import { APM_INDICES_SAVED_OBJECT_TYPE, APM_INDICES_SAVED_OBJECT_ID } from '../../../../common/apm_saved_object_constants'; +import { ApmIndicesConfig } from './get_apm_indices'; +import { APMRequestHandlerContext } from '../../../routes/typings'; export async function saveApmIndices( - savedObjectsClient: ScopedSavedObjectsClient, + context: APMRequestHandlerContext, apmIndicesSavedObject: Partial ) { - return await savedObjectsClient.create( + return await context.core.savedObjects.client.create( APM_INDICES_SAVED_OBJECT_TYPE, apmIndicesSavedObject, { diff --git a/x-pack/legacy/plugins/apm/server/lib/traces/get_trace.ts b/x-pack/legacy/plugins/apm/server/lib/traces/get_trace.ts index a296bcbdecccf4..e38ce56edde80e 100644 --- a/x-pack/legacy/plugins/apm/server/lib/traces/get_trace.ts +++ b/x-pack/legacy/plugins/apm/server/lib/traces/get_trace.ts @@ -6,11 +6,11 @@ import { PromiseReturnType } from '../../../typings/common'; import { getTraceErrorsPerTransaction } from '../errors/get_trace_errors_per_transaction'; -import { Setup } from '../helpers/setup_request'; +import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getTraceItems } from './get_trace_items'; export type TraceAPIResponse = PromiseReturnType; -export async function getTrace(traceId: string, setup: Setup) { +export async function getTrace(traceId: string, setup: Setup & SetupTimeRange) { const [trace, errorsPerTransaction] = await Promise.all([ getTraceItems(traceId, setup), getTraceErrorsPerTransaction(traceId, setup) diff --git a/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts b/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts index 0df5cc016431d6..8ea548ab3724bc 100644 --- a/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts +++ b/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts @@ -14,11 +14,14 @@ import { import { Span } from '../../../typings/es_schemas/ui/Span'; import { Transaction } from '../../../typings/es_schemas/ui/Transaction'; import { rangeFilter } from '../helpers/range_filter'; -import { Setup } from '../helpers/setup_request'; +import { Setup, SetupTimeRange } from '../helpers/setup_request'; -export async function getTraceItems(traceId: string, setup: Setup) { +export async function getTraceItems( + traceId: string, + setup: Setup & SetupTimeRange +) { const { start, end, client, config, indices } = setup; - const maxTraceItems = config.get('xpack.apm.ui.maxTraceItems'); + const maxTraceItems = config['xpack.apm.ui.maxTraceItems']; const params = { index: [ diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.test.ts b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.test.ts index 1d849fbbaaaf5b..4121ff74bfaccb 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.test.ts @@ -5,6 +5,7 @@ */ import { transactionGroupsFetcher } from './fetcher'; +import { APMConfig } from '../../../../../../plugins/apm/server'; function getSetup() { return { @@ -17,14 +18,8 @@ function getSetup() { search: jest.fn() } as any, config: { - get: jest.fn((key: string) => { - switch (key) { - case 'xpack.apm.ui.transactionGroupBucketSize': - return 100; - } - }), - has: () => true - }, + 'xpack.apm.ui.transactionGroupBucketSize': 100 + } as APMConfig, uiFiltersES: [{ term: { 'service.environment': 'test' } }], indices: { 'apm_oss.sourcemapIndices': 'myIndex', @@ -33,7 +28,7 @@ function getSetup() { 'apm_oss.spanIndices': 'myIndex', 'apm_oss.transactionIndices': 'myIndex', 'apm_oss.metricsIndices': 'myIndex', - 'apm_oss.apmAgentConfigurationIndex': 'myIndex' + apmAgentConfigurationIndex: 'myIndex' }, dynamicIndexPattern: null as any }; diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts index bfa46abcad36fd..b08bdc334fc878 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts @@ -9,7 +9,11 @@ import { TRANSACTION_SAMPLED } from '../../../common/elasticsearch_fieldnames'; import { PromiseReturnType } from '../../../typings/common'; -import { Setup } from '../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../helpers/setup_request'; import { getTransactionGroupsProjection } from '../../../common/projections/transaction_groups'; import { mergeProjection } from '../../../common/projections/util/merge_projection'; import { SortOptions } from '../../../typings/elasticsearch/aggregations'; @@ -30,7 +34,10 @@ interface TopTraceOptions { export type Options = TopTransactionOptions | TopTraceOptions; export type ESResponse = PromiseReturnType; -export function transactionGroupsFetcher(options: Options, setup: Setup) { +export function transactionGroupsFetcher( + options: Options, + setup: Setup & SetupTimeRange & SetupUIFilters +) { const { client, config } = setup; const projection = getTransactionGroupsProjection({ @@ -57,7 +64,7 @@ export function transactionGroupsFetcher(options: Options, setup: Setup) { terms: { ...projection.body.aggs.transactions.terms, order: { sum: 'desc' as const }, - size: config.get('xpack.apm.ui.transactionGroupBucketSize') + size: config['xpack.apm.ui.transactionGroupBucketSize'] }, aggs: { sample: { diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/index.ts b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/index.ts index 73e30f28c42067..3656b32c170929 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/index.ts @@ -4,7 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Setup } from '../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../helpers/setup_request'; import { transactionGroupsFetcher, Options } from './fetcher'; import { transactionGroupsTransformer } from './transform'; import { PromiseReturnType } from '../../../typings/common'; @@ -12,7 +16,10 @@ import { PromiseReturnType } from '../../../typings/common'; export type TransactionGroupListAPIResponse = PromiseReturnType< typeof getTransactionGroupList >; -export async function getTransactionGroupList(options: Options, setup: Setup) { +export async function getTransactionGroupList( + options: Options, + setup: Setup & SetupTimeRange & SetupUIFilters +) { const { start, end } = setup; const response = await transactionGroupsFetcher(options, setup); return transactionGroupsTransformer({ diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.test.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.test.ts index f2227524db0813..1a5921e06d0d16 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/fetcher.test.ts @@ -4,7 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Setup } from '../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../helpers/setup_request'; import { fetcher } from './fetcher'; describe('fetcher', () => { @@ -14,7 +18,7 @@ describe('fetcher', () => { client: { search }, indices: {}, uiFiltersES: [] - } as unknown) as Setup; + } as unknown) as Setup & SetupTimeRange & SetupUIFilters; await fetcher({ serviceName: 'testServiceName', setup }); diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/index.ts index 57b3c8cbe9f937..07b598a86caa48 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/index.ts @@ -5,13 +5,17 @@ */ import { Coordinate } from '../../../../typings/timeseries'; -import { Setup } from '../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../helpers/setup_request'; import { fetcher } from './fetcher'; import { transformer } from './transformer'; export interface Options { serviceName: string; - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; } export type AvgDurationByBrowserAPIResponse = Array<{ diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts index ed6bdf203f2d44..e2dfb5d0f7a581 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_country/index.ts @@ -12,7 +12,11 @@ import { TRANSACTION_TYPE, TRANSACTION_NAME } from '../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../helpers/setup_request'; import { rangeFilter } from '../../helpers/range_filter'; import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types'; @@ -21,7 +25,7 @@ export async function getTransactionAvgDurationByCountry({ serviceName, transactionName }: { - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; serviceName: string; transactionName?: string; }) { diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts index 8254f028923d34..f49c1e022a0701 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts @@ -8,6 +8,7 @@ import { getTransactionBreakdown } from '.'; import * as constants from './constants'; import noDataResponse from './mock-responses/noData.json'; import dataResponse from './mock-responses/data.json'; +import { APMConfig } from '../../../../../../../plugins/apm/server'; const mockIndices = { 'apm_oss.sourcemapIndices': 'myIndex', @@ -16,7 +17,7 @@ const mockIndices = { 'apm_oss.spanIndices': 'myIndex', 'apm_oss.transactionIndices': 'myIndex', 'apm_oss.metricsIndices': 'myIndex', - 'apm_oss.apmAgentConfigurationIndex': 'myIndex' + apmAgentConfigurationIndex: 'myIndex' }; function getMockSetup(esResponse: any) { @@ -26,10 +27,12 @@ function getMockSetup(esResponse: any) { end: 500000, client: { search: clientSpy } as any, internalClient: { search: clientSpy } as any, - config: { - get: () => 'myIndex' as any, - has: () => true - }, + config: new Proxy( + {}, + { + get: () => 'myIndex' + } + ) as APMConfig, uiFiltersES: [], indices: mockIndices, dynamicIndexPattern: null as any diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts index 12f66941169502..26e62d8902eebd 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.ts @@ -15,7 +15,11 @@ import { TRANSACTION_BREAKDOWN_COUNT, PROCESSOR_EVENT } from '../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../helpers/setup_request'; import { rangeFilter } from '../../helpers/range_filter'; import { getMetricsDateHistogramParams } from '../../helpers/metrics'; import { MAX_KPIS } from './constants'; @@ -27,7 +31,7 @@ export async function getTransactionBreakdown({ transactionName, transactionType }: { - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; serviceName: string; transactionName?: string; transactionType: string; diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.ts index 9b51d9336d2e65..5f211b1427259c 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.ts @@ -6,7 +6,7 @@ import { getMlIndex } from '../../../../../common/ml_job_constants'; import { PromiseReturnType } from '../../../../../typings/common'; -import { Setup } from '../../../helpers/setup_request'; +import { Setup, SetupTimeRange } from '../../../helpers/setup_request'; export type ESResponse = Exclude< PromiseReturnType, @@ -24,7 +24,7 @@ export async function anomalySeriesFetcher({ transactionType: string; intervalString: string; mlBucketSize: number; - setup: Setup; + setup: Setup & SetupTimeRange; }) { const { client, start, end } = setup; diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts index 4933f1b1ed431a..9419fa7a77fb91 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts @@ -5,12 +5,12 @@ */ import { getMlIndex } from '../../../../../common/ml_job_constants'; -import { Setup } from '../../../helpers/setup_request'; +import { Setup, SetupTimeRange } from '../../../helpers/setup_request'; interface IOptions { serviceName: string; transactionType: string; - setup: Setup; + setup: Setup & SetupTimeRange; } interface ESResponse { diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts index a5008ce10f9f90..2a56f744f2f456 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts @@ -8,6 +8,7 @@ import { getAnomalySeries } from '.'; import { mlAnomalyResponse } from './mock-responses/mlAnomalyResponse'; import { mlBucketSpanResponse } from './mock-responses/mlBucketSpanResponse'; import { PromiseReturnType } from '../../../../../typings/common'; +import { APMConfig } from '../../../../../../../../plugins/apm/server'; describe('getAnomalySeries', () => { let avgAnomalies: PromiseReturnType; @@ -27,10 +28,12 @@ describe('getAnomalySeries', () => { end: 500000, client: { search: clientSpy } as any, internalClient: { search: clientSpy } as any, - config: { - get: () => 'myIndex' as any, - has: () => true - }, + config: new Proxy( + {}, + { + get: () => 'myIndex' + } + ) as APMConfig, uiFiltersES: [], indices: { 'apm_oss.sourcemapIndices': 'myIndex', @@ -39,7 +42,7 @@ describe('getAnomalySeries', () => { 'apm_oss.spanIndices': 'myIndex', 'apm_oss.transactionIndices': 'myIndex', 'apm_oss.metricsIndices': 'myIndex', - 'apm_oss.apmAgentConfigurationIndex': 'myIndex' + apmAgentConfigurationIndex: 'myIndex' }, dynamicIndexPattern: null as any } diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts index b55d264cfbbfea..c631772b0e18cb 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts @@ -5,7 +5,11 @@ */ import { getBucketSize } from '../../../helpers/get_bucket_size'; -import { Setup } from '../../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../../helpers/setup_request'; import { anomalySeriesFetcher } from './fetcher'; import { getMlBucketSize } from './get_ml_bucket_size'; import { anomalySeriesTransform } from './transform'; @@ -21,7 +25,7 @@ export async function getAnomalySeries({ transactionType: string | undefined; transactionName: string | undefined; timeSeriesDates: number[]; - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; }) { // don't fetch anomalies for transaction details page if (transactionName) { diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts index 7bdfb17ba17b33..676ad4ded6b692 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts @@ -6,6 +6,7 @@ import { PROCESSOR_EVENT } from '../../../../../common/elasticsearch_fieldnames'; import { ESResponse, timeseriesFetcher } from './fetcher'; +import { APMConfig } from '../../../../../../../../plugins/apm/server'; describe('timeseriesFetcher', () => { let res: ESResponse; @@ -22,10 +23,12 @@ describe('timeseriesFetcher', () => { end: 1528977600000, client: { search: clientSpy } as any, internalClient: { search: clientSpy } as any, - config: { - get: () => 'myIndex' as any, - has: () => true - }, + config: new Proxy( + {}, + { + get: () => 'myIndex' + } + ) as APMConfig, uiFiltersES: [ { term: { 'service.environment': 'test' } @@ -38,7 +41,7 @@ describe('timeseriesFetcher', () => { 'apm_oss.spanIndices': 'myIndex', 'apm_oss.transactionIndices': 'myIndex', 'apm_oss.metricsIndices': 'myIndex', - 'apm_oss.apmAgentConfigurationIndex': 'myIndex' + apmAgentConfigurationIndex: 'myIndex' }, dynamicIndexPattern: null as any } diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts index 0d9cccb3b56d35..8a2e01c9a78911 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts @@ -16,7 +16,11 @@ import { import { PromiseReturnType } from '../../../../../typings/common'; import { getBucketSize } from '../../../helpers/get_bucket_size'; import { rangeFilter } from '../../../helpers/range_filter'; -import { Setup } from '../../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../../helpers/setup_request'; export type ESResponse = PromiseReturnType; export function timeseriesFetcher({ @@ -28,7 +32,7 @@ export function timeseriesFetcher({ serviceName: string; transactionType: string | undefined; transactionName: string | undefined; - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; }) { const { start, end, uiFiltersES, client, indices } = setup; const { intervalString } = getBucketSize(start, end, 'auto'); diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/index.ts index 6c18ab84cdfabc..96d06bdd3b0e1b 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/get_timeseries_data/index.ts @@ -5,7 +5,11 @@ */ import { getBucketSize } from '../../../helpers/get_bucket_size'; -import { Setup } from '../../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../../helpers/setup_request'; import { timeseriesFetcher } from './fetcher'; import { timeseriesTransformer } from './transform'; @@ -13,7 +17,7 @@ export async function getApmTimeseriesData(options: { serviceName: string; transactionType: string | undefined; transactionName: string | undefined; - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; }) { const { start, end } = options.setup; const { bucketSize } = getBucketSize(start, end, 'auto'); diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/index.ts index c297f3a050f0c6..a6a1a76e19664b 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/charts/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/charts/index.ts @@ -5,7 +5,11 @@ */ import { PromiseReturnType } from '../../../../typings/common'; -import { Setup } from '../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../helpers/setup_request'; import { getAnomalySeries } from './get_anomaly_data'; import { getApmTimeseriesData } from './get_timeseries_data'; import { ApmTimeSeriesResponse } from './get_timeseries_data/transform'; @@ -21,7 +25,7 @@ export async function getTransactionCharts(options: { serviceName: string; transactionType: string | undefined; transactionName: string | undefined; - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; }) { const apmTimeseries = await getApmTimeseriesData(options); const anomalyTimeseries = await getAnomalySeries({ diff --git a/x-pack/legacy/plugins/apm/server/new-platform/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/constants.ts similarity index 55% rename from x-pack/legacy/plugins/apm/server/new-platform/index.ts rename to x-pack/legacy/plugins/apm/server/lib/transactions/constants.ts index 8ad0cbbb811f38..7fae0880489032 100644 --- a/x-pack/legacy/plugins/apm/server/new-platform/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/constants.ts @@ -4,9 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from 'src/core/server'; -import { Plugin } from './plugin'; - -export function plugin(initializerContext: PluginInitializerContext) { - return new Plugin(); -} +export const MINIMUM_BUCKET_SIZE = 15; +export const BUCKET_TARGET_COUNT = 15; diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts index 90d9a925a1f36d..32fa65722869d8 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts @@ -16,7 +16,11 @@ import { TRANSACTION_TYPE } from '../../../../../common/elasticsearch_fieldnames'; import { rangeFilter } from '../../../helpers/range_filter'; -import { Setup } from '../../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../../helpers/setup_request'; export async function bucketFetcher( serviceName: string, @@ -26,7 +30,7 @@ export async function bucketFetcher( traceId: string, distributionMax: number, bucketSize: number, - setup: Setup + setup: Setup & SetupTimeRange & SetupUIFilters ) { const { start, end, uiFiltersES, client, indices } = setup; diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts index 86429986063ed5..90b2fa5894665f 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts @@ -4,7 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Setup } from '../../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../../helpers/setup_request'; import { bucketFetcher } from './fetcher'; import { bucketTransformer } from './transform'; @@ -16,7 +20,7 @@ export async function getBuckets( traceId: string, distributionMax: number, bucketSize: number, - setup: Setup + setup: Setup & SetupTimeRange & SetupUIFilters ) { const response = await bucketFetcher( serviceName, diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts index a54fa9c10de13d..0dfe769c0bbac5 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/get_distribution_max.ts @@ -11,13 +11,17 @@ import { TRANSACTION_NAME, TRANSACTION_TYPE } from '../../../../common/elasticsearch_fieldnames'; -import { Setup } from '../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../helpers/setup_request'; export async function getDistributionMax( serviceName: string, transactionName: string, transactionType: string, - setup: Setup + setup: Setup & SetupTimeRange & SetupUIFilters ) { const { start, end, uiFiltersES, client, indices } = setup; diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/index.ts index 3efa996d609d81..9dd29a06643292 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/distribution/index.ts @@ -5,19 +5,20 @@ */ import { PromiseReturnType } from '../../../../typings/common'; -import { Setup } from '../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../helpers/setup_request'; import { getBuckets } from './get_buckets'; import { getDistributionMax } from './get_distribution_max'; import { roundToNearestFiveOrTen } from '../../helpers/round_to_nearest_five_or_ten'; +import { MINIMUM_BUCKET_SIZE, BUCKET_TARGET_COUNT } from '../constants'; -function getBucketSize(max: number, { config }: Setup) { - const minBucketSize: number = config.get( - 'xpack.apm.minimumBucketSize' - ); - const bucketTargetCount = config.get('xpack.apm.bucketTargetCount'); - const bucketSize = max / bucketTargetCount; +function getBucketSize(max: number) { + const bucketSize = max / BUCKET_TARGET_COUNT; return roundToNearestFiveOrTen( - bucketSize > minBucketSize ? bucketSize : minBucketSize + bucketSize > MINIMUM_BUCKET_SIZE ? bucketSize : MINIMUM_BUCKET_SIZE ); } @@ -37,7 +38,7 @@ export async function getTransactionDistribution({ transactionType: string; transactionId: string; traceId: string; - setup: Setup; + setup: Setup & SetupTimeRange & SetupUIFilters; }) { const distributionMax = await getDistributionMax( serviceName, @@ -50,7 +51,7 @@ export async function getTransactionDistribution({ return { noHits: true, buckets: [], bucketSize: 0 }; } - const bucketSize = getBucketSize(distributionMax, setup); + const bucketSize = getBucketSize(distributionMax); const { buckets, noHits } = await getBuckets( serviceName, transactionName, diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/get_transaction/index.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/get_transaction/index.ts index 652acf773e2e56..56cee04049bd98 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/get_transaction/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/get_transaction/index.ts @@ -11,12 +11,16 @@ import { } from '../../../../common/elasticsearch_fieldnames'; import { Transaction } from '../../../../typings/es_schemas/ui/Transaction'; import { rangeFilter } from '../../helpers/range_filter'; -import { Setup } from '../../helpers/setup_request'; +import { + Setup, + SetupTimeRange, + SetupUIFilters +} from '../../helpers/setup_request'; export async function getTransaction( transactionId: string, traceId: string, - setup: Setup + setup: Setup & SetupTimeRange & SetupUIFilters ) { const { start, end, uiFiltersES, client, indices } = setup; diff --git a/x-pack/legacy/plugins/apm/server/lib/ui_filters/get_environments.ts b/x-pack/legacy/plugins/apm/server/lib/ui_filters/get_environments.ts index 1b9e2ebd2e7577..50c1926d1e4a0b 100644 --- a/x-pack/legacy/plugins/apm/server/lib/ui_filters/get_environments.ts +++ b/x-pack/legacy/plugins/apm/server/lib/ui_filters/get_environments.ts @@ -10,11 +10,14 @@ import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames'; import { rangeFilter } from '../helpers/range_filter'; -import { Setup } from '../helpers/setup_request'; +import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { ENVIRONMENT_NOT_DEFINED } from '../../../common/environment_filter_values'; import { ESFilter } from '../../../typings/elasticsearch'; -export async function getEnvironments(setup: Setup, serviceName?: string) { +export async function getEnvironments( + setup: Setup & SetupTimeRange, + serviceName?: string +) { const { start, end, client, indices } = setup; const filter: ESFilter[] = [ diff --git a/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/get_filter_aggregations.ts b/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/get_filter_aggregations.ts index cc1ebf2d519526..0bf89414e2894e 100644 --- a/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/get_filter_aggregations.ts +++ b/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/get_filter_aggregations.ts @@ -11,7 +11,7 @@ import { getUiFiltersES } from '../../helpers/convert_ui_filters/get_ui_filters_ import { localUIFilters, LocalUIFilterName } from './config'; import { StaticIndexPattern } from '../../../../../../../../src/legacy/core_plugins/data/public'; -export const getFilterAggregations = async ({ +export const getFilterAggregations = ({ indexPattern, uiFilters, projection, @@ -24,49 +24,44 @@ export const getFilterAggregations = async ({ }) => { const mappedFilters = localFilterNames.map(name => localUIFilters[name]); - const aggs = await Promise.all( - mappedFilters.map(async field => { - const filter = await getUiFiltersES( - indexPattern, - omit(uiFilters, field.name) - ); + const aggs = mappedFilters.map(field => { + const filter = getUiFiltersES(indexPattern, omit(uiFilters, field.name)); - const bucketCountAggregation = projection.body.aggs - ? { - aggs: { - bucket_count: { - cardinality: { - field: - projection.body.aggs[Object.keys(projection.body.aggs)[0]] - .terms.field - } + const bucketCountAggregation = projection.body.aggs + ? { + aggs: { + bucket_count: { + cardinality: { + field: + projection.body.aggs[Object.keys(projection.body.aggs)[0]] + .terms.field } } } - : {}; + } + : {}; - return { - [field.name]: { - filter: { - bool: { - filter - } - }, - aggs: { - by_terms: { - terms: { - field: field.fieldName, - order: { - _count: 'desc' as const - } - }, - ...bucketCountAggregation - } + return { + [field.name]: { + filter: { + bool: { + filter + } + }, + aggs: { + by_terms: { + terms: { + field: field.fieldName, + order: { + _count: 'desc' as const + } + }, + ...bucketCountAggregation } } - }; - }) - ); + } + }; + }); const mergedAggregations = Object.assign({}, ...aggs) as Partial< Record diff --git a/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts b/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts index ada41c3aa97c75..524e6ca640f3ef 100644 --- a/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/ui_filters/local_ui_filters/index.ts @@ -33,7 +33,7 @@ export async function getLocalUIFilters({ delete projectionWithoutAggs.body.aggs; - const filterAggregations = await getFilterAggregations({ + const filterAggregations = getFilterAggregations({ indexPattern: dynamicIndexPattern, uiFilters, projection, diff --git a/x-pack/legacy/plugins/apm/server/new-platform/plugin.ts b/x-pack/legacy/plugins/apm/server/new-platform/plugin.ts deleted file mode 100644 index e1cb1774469f2a..00000000000000 --- a/x-pack/legacy/plugins/apm/server/new-platform/plugin.ts +++ /dev/null @@ -1,23 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Server } from 'hapi'; -import { CoreSetup } from 'src/core/server'; -import { makeApmUsageCollector } from '../lib/apm_telemetry'; -import { createApmAgentConfigurationIndex } from '../lib/settings/agent_configuration/create_agent_config_index'; -import { createApmApi } from '../routes/create_apm_api'; - -export interface LegacySetup { - server: Server; -} - -export class Plugin { - public setup(core: CoreSetup, __LEGACY: LegacySetup) { - createApmApi().init(core, __LEGACY); - createApmAgentConfigurationIndex(core, __LEGACY); - makeApmUsageCollector(core, __LEGACY); - } -} diff --git a/x-pack/legacy/plugins/apm/server/routes/create_api/index.test.ts b/x-pack/legacy/plugins/apm/server/routes/create_api/index.test.ts index 18fe547a34cf01..3a74c4377920aa 100644 --- a/x-pack/legacy/plugins/apm/server/routes/create_api/index.test.ts +++ b/x-pack/legacy/plugins/apm/server/routes/create_api/index.test.ts @@ -5,25 +5,49 @@ */ import * as t from 'io-ts'; import { createApi } from './index'; -import { CoreSetup } from 'src/core/server'; +import { CoreSetup, Logger } from 'src/core/server'; import { Params } from '../typings'; -import { LegacySetup } from '../../new-platform/plugin'; - -const getCoreMock = () => (({} as unknown) as CoreSetup); +import { BehaviorSubject } from 'rxjs'; +import { APMConfig } from '../../../../../../plugins/apm/server'; +import { LegacySetup } from '../../../../../../plugins/apm/server/plugin'; + +const getCoreMock = () => { + const get = jest.fn(); + const post = jest.fn(); + const put = jest.fn(); + const createRouter = jest.fn().mockReturnValue({ + get, + post, + put + }); -const getLegacyMock = () => - (({ - server: { - route: jest.fn() + const mock = {} as CoreSetup; + + return { + mock: { + ...mock, + http: { + ...mock.http, + createRouter + } + }, + get, + post, + put, + createRouter, + context: { + config$: new BehaviorSubject({} as APMConfig), + logger: ({ + error: jest.fn() + } as unknown) as Logger, + __LEGACY: {} as LegacySetup } - } as unknown) as LegacySetup & { - server: { route: ReturnType }; - }); + }; +}; describe('createApi', () => { it('registers a route with the server', () => { - const coreMock = getCoreMock(); - const legacySetupMock = getLegacyMock(); + const { mock, context, createRouter, post, get, put } = getCoreMock(); createApi() .add(() => ({ @@ -46,236 +70,249 @@ describe('createApi', () => { }, handler: async () => null })) - .init(coreMock, legacySetupMock); + .init(mock, context); - expect(legacySetupMock.server.route).toHaveBeenCalledTimes(3); + expect(createRouter).toHaveBeenCalledTimes(1); - const firstRoute = legacySetupMock.server.route.mock.calls[0][0]; + expect(get).toHaveBeenCalledTimes(1); + expect(post).toHaveBeenCalledTimes(1); + expect(put).toHaveBeenCalledTimes(1); - expect(firstRoute).toEqual({ - method: 'GET', + expect(get.mock.calls[0][0]).toEqual({ options: { tags: ['access:apm'] }, path: '/foo', - handler: expect.any(Function) + validate: expect.anything() }); - const secondRoute = legacySetupMock.server.route.mock.calls[1][0]; - - expect(secondRoute).toEqual({ - method: 'POST', + expect(post.mock.calls[0][0]).toEqual({ options: { tags: ['access:apm'] }, path: '/bar', - handler: expect.any(Function) + validate: expect.anything() }); - const thirdRoute = legacySetupMock.server.route.mock.calls[2][0]; - - expect(thirdRoute).toEqual({ - method: 'PUT', + expect(put.mock.calls[0][0]).toEqual({ options: { tags: ['access:apm', 'access:apm_write'] }, path: '/baz', - handler: expect.any(Function) + validate: expect.anything() }); }); describe('when validating', () => { const initApi = (params: Params) => { - const core = getCoreMock(); - const legacySetupMock = getLegacyMock(); - const handler = jest.fn(); + const { mock, context, createRouter, get, post } = getCoreMock(); + const handlerMock = jest.fn(); createApi() .add(() => ({ path: '/foo', params, - handler + handler: handlerMock })) - .init(core, legacySetupMock); - - const route = legacySetupMock.server.route.mock.calls[0][0]; - - const routeHandler = route.handler; + .init(mock, context); + + const routeHandler = get.mock.calls[0][1]; + const responseMock = { + ok: jest.fn(), + internalError: jest.fn(), + notFound: jest.fn(), + forbidden: jest.fn(), + badRequest: jest.fn() + }; - route.handler = (requestMock: any) => { - return routeHandler({ - // stub hapi's default values - params: {}, - query: {}, - payload: null, - ...requestMock - }); + const simulate = (requestMock: any) => { + return routeHandler( + {}, + { + // stub default values + params: {}, + query: {}, + body: {}, + ...requestMock + }, + responseMock + ); }; - return { route, handler }; + return { simulate, handlerMock, createRouter, get, post, responseMock }; }; - it('adds a _debug query parameter by default', () => { - const { handler, route } = initApi({}); + it('adds a _debug query parameter by default', async () => { + const { simulate, handlerMock, responseMock } = initApi({}); - expect(() => - route.handler({ - query: { - _debug: 'true' - } - }) - ).not.toThrow(); + await simulate({ query: { _debug: true } }); - expect(handler).toHaveBeenCalledTimes(1); + expect(handlerMock).toHaveBeenCalledTimes(1); - const params = handler.mock.calls[0][1]; + expect(responseMock.ok).toHaveBeenCalled(); - expect(params).toEqual({}); + expect(responseMock.badRequest).not.toHaveBeenCalled(); - expect(() => - route.handler({ - query: { - _debug: 1 - } - }) - ).toThrow(); + const params = handlerMock.mock.calls[0][0].context.params; + + expect(params).toEqual({ + query: { + _debug: true + } + }); + + await simulate({ + query: { + _debug: 1 + } + }); + + expect(responseMock.badRequest).toHaveBeenCalled(); }); - it('throws if any parameters are used but no types are defined', () => { - const { route } = initApi({}); + it('throws if any parameters are used but no types are defined', async () => { + const { simulate, responseMock } = initApi({}); - expect(() => - route.handler({ - query: { - _debug: 'true', - extra: '' - } - }) - ).toThrow(); + await simulate({ + query: { + _debug: true, + extra: '' + } + }); - expect(() => - route.handler({ - payload: { foo: 'bar' } - }) - ).toThrow(); + expect(responseMock.badRequest).toHaveBeenCalledTimes(1); - expect(() => - route.handler({ - params: { - foo: 'bar' - } - }) - ).toThrow(); - }); + await simulate({ + body: { foo: 'bar' } + }); - it('validates path parameters', () => { - const { handler, route } = initApi({ path: t.type({ foo: t.string }) }); + expect(responseMock.badRequest).toHaveBeenCalledTimes(2); - expect(() => - route.handler({ - params: { - foo: 'bar' - } + await simulate({ + params: { + foo: 'bar' + } + }); + + expect(responseMock.badRequest).toHaveBeenCalledTimes(3); + }); + + it('validates path parameters', async () => { + const { simulate, handlerMock, responseMock } = initApi({ + path: t.type({ + foo: t.string }) - ).not.toThrow(); + }); + + await simulate({ + params: { + foo: 'bar' + } + }); - expect(handler).toHaveBeenCalledTimes(1); + expect(handlerMock).toHaveBeenCalledTimes(1); - const params = handler.mock.calls[0][1]; + expect(responseMock.ok).toHaveBeenCalledTimes(1); + expect(responseMock.badRequest).not.toHaveBeenCalled(); + + const params = handlerMock.mock.calls[0][0].context.params; expect(params).toEqual({ path: { foo: 'bar' + }, + query: { + _debug: false } }); - handler.mockClear(); + await simulate({ + params: { + bar: 'foo' + } + }); - expect(() => - route.handler({ - params: { - bar: 'foo' - } - }) - ).toThrow(); + expect(responseMock.badRequest).toHaveBeenCalledTimes(1); - expect(() => - route.handler({ - params: { - foo: 9 - } - }) - ).toThrow(); - - expect(() => - route.handler({ - params: { - foo: 'bar', - extra: '' - } - }) - ).toThrow(); + await simulate({ + params: { + foo: 9 + } + }); + + expect(responseMock.badRequest).toHaveBeenCalledTimes(2); + + await simulate({ + params: { + foo: 'bar', + extra: '' + } + }); + + expect(responseMock.badRequest).toHaveBeenCalledTimes(3); }); - it('validates body parameters', () => { - const { handler, route } = initApi({ body: t.string }); + it('validates body parameters', async () => { + const { simulate, handlerMock, responseMock } = initApi({ + body: t.string + }); - expect(() => - route.handler({ - payload: '' - }) - ).not.toThrow(); + await simulate({ + body: '' + }); - expect(handler).toHaveBeenCalledTimes(1); + expect(handlerMock).toHaveBeenCalledTimes(1); + expect(responseMock.ok).toHaveBeenCalledTimes(1); + expect(responseMock.badRequest).not.toHaveBeenCalled(); - const params = handler.mock.calls[0][1]; + const params = handlerMock.mock.calls[0][0].context.params; expect(params).toEqual({ - body: '' + body: '', + query: { + _debug: false + } }); - handler.mockClear(); + await simulate({ + body: null + }); - expect(() => - route.handler({ - payload: null - }) - ).toThrow(); + expect(responseMock.badRequest).toHaveBeenCalledTimes(1); }); - it('validates query parameters', () => { - const { handler, route } = initApi({ + it('validates query parameters', async () => { + const { simulate, handlerMock, responseMock } = initApi({ query: t.type({ bar: t.string }) }); - expect(() => - route.handler({ - query: { - bar: '', - _debug: 'true' - } - }) - ).not.toThrow(); + await simulate({ + query: { + bar: '', + _debug: true + } + }); - expect(handler).toHaveBeenCalledTimes(1); + expect(handlerMock).toHaveBeenCalledTimes(1); + expect(responseMock.ok).toHaveBeenCalledTimes(1); + expect(responseMock.badRequest).not.toHaveBeenCalled(); - const params = handler.mock.calls[0][1]; + const params = handlerMock.mock.calls[0][0].context.params; expect(params).toEqual({ query: { - bar: '' + bar: '', + _debug: true } }); - handler.mockClear(); + await simulate({ + query: { + bar: '', + foo: '' + } + }); - expect(() => - route.handler({ - query: { - bar: '', - foo: '' - } - }) - ).toThrow(); + expect(responseMock.badRequest).toHaveBeenCalledTimes(1); }); }); }); diff --git a/x-pack/legacy/plugins/apm/server/routes/create_api/index.ts b/x-pack/legacy/plugins/apm/server/routes/create_api/index.ts index 66f28a9bf6d445..2e97b01d0d108b 100644 --- a/x-pack/legacy/plugins/apm/server/routes/create_api/index.ts +++ b/x-pack/legacy/plugins/apm/server/routes/create_api/index.ts @@ -3,13 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { merge, pick, omit, difference } from 'lodash'; +import { pick, difference } from 'lodash'; import Boom from 'boom'; -import { CoreSetup } from 'src/core/server'; -import { Request, ResponseToolkit } from 'hapi'; +import { schema } from '@kbn/config-schema'; import * as t from 'io-ts'; import { PathReporter } from 'io-ts/lib/PathReporter'; import { isLeft } from 'fp-ts/lib/Either'; +import { KibanaResponseFactory } from 'src/core/server'; +import { APMConfig } from '../../../../../../plugins/apm/server'; import { ServerAPI, RouteFactoryFn, @@ -17,10 +18,8 @@ import { Route, Params } from '../typings'; -import { jsonRt } from '../../../common/runtime_types/json_rt'; -import { LegacySetup } from '../../new-platform/plugin'; -const debugRt = t.partial({ _debug: jsonRt.pipe(t.boolean) }); +const debugRt = t.partial({ _debug: t.boolean }); export function createApi() { const factoryFns: Array> = []; @@ -30,17 +29,32 @@ export function createApi() { factoryFns.push(fn); return this as any; }, - init(core: CoreSetup, __LEGACY: LegacySetup) { - const { server } = __LEGACY; + init(core, { config$, logger, __LEGACY }) { + const router = core.http.createRouter(); + + let config = {} as APMConfig; + + config$.subscribe(val => { + config = val; + }); + factoryFns.forEach(fn => { const { params = {}, + path, options = { tags: ['access:apm'] }, - ...route - } = fn(core, __LEGACY) as Route; + method, + handler + } = fn(core) as Route; + + const routerMethod = (method || 'GET').toLowerCase() as + | 'post' + | 'put' + | 'get' + | 'delete'; const bodyRt = params.body; - const fallbackBodyRt = bodyRt || t.null; + const fallbackBodyRt = bodyRt || t.strict({}); const rts = { // add _debug query parameter to all routes @@ -51,65 +65,80 @@ export function createApi() { body: bodyRt && 'props' in bodyRt ? t.exact(bodyRt) : fallbackBodyRt }; - server.route( - merge( - { - options, - method: 'GET' - }, - route, - { - handler: (request: Request, h: ResponseToolkit) => { - const paramMap = { - path: request.params, - body: request.payload, - query: request.query - }; + router[routerMethod]( + { + path, + options, + validate: { + ...(routerMethod === 'get' + ? {} + : { body: schema.object({}, { allowUnknowns: true }) }), + params: schema.object({}, { allowUnknowns: true }), + query: schema.object({}, { allowUnknowns: true }) + } + }, + async (context, request, response) => { + try { + const paramMap = { + path: request.params, + body: request.body, + query: { + _debug: false, + ...request.query + } + }; - const parsedParams = (Object.keys(rts) as Array< - keyof typeof rts - >).reduce((acc, key) => { - const codec = rts[key]; - const value = paramMap[key]; + const parsedParams = (Object.keys(rts) as Array< + keyof typeof rts + >).reduce((acc, key) => { + const codec = rts[key]; + const value = paramMap[key]; - const result = codec.decode(value); + const result = codec.decode(value); - if (isLeft(result)) { - throw Boom.badRequest(PathReporter.report(result)[0]); - } + if (isLeft(result)) { + throw Boom.badRequest(PathReporter.report(result)[0]); + } - const strippedKeys = difference( - Object.keys(value || {}), - Object.keys(result.right || {}) + const strippedKeys = difference( + Object.keys(value || {}), + Object.keys(result.right || {}) + ); + + if (strippedKeys.length) { + throw Boom.badRequest( + `Unknown keys specified: ${strippedKeys}` ); + } + + const parsedValue = result.right; - if (strippedKeys.length) { - throw Boom.badRequest( - `Unknown keys specified: ${strippedKeys}` - ); - } - - // hide _debug from route handlers - const parsedValue = - key === 'query' - ? omit(result.right, '_debug') - : result.right; - - return { - ...acc, - [key]: parsedValue - }; - }, {} as Record); - - return route.handler( - request, + return { + ...acc, + [key]: parsedValue + }; + }, {} as Record); + + const data = await handler({ + request, + context: { + ...context, + __LEGACY, // only return values for parameters that have runtime types - pick(parsedParams, Object.keys(params)), - h - ); + params: pick(parsedParams, ...Object.keys(params), 'query'), + config, + logger + } + }); + + return response.ok({ body: data }); + } catch (error) { + if (Boom.isBoom(error)) { + return convertBoomToKibanaResponse(error, response); } + throw error; } - ) + } ); }); } @@ -117,3 +146,26 @@ export function createApi() { return api; } + +function convertBoomToKibanaResponse( + error: Boom, + response: KibanaResponseFactory +) { + const opts = { body: error.message }; + switch (error.output.statusCode) { + case 404: + return response.notFound(opts); + + case 400: + return response.badRequest(opts); + + case 403: + return response.forbidden(opts); + + default: + return response.custom({ + statusCode: error.output.statusCode, + ...opts + }); + } +} diff --git a/x-pack/legacy/plugins/apm/server/routes/errors.ts b/x-pack/legacy/plugins/apm/server/routes/errors.ts index a315dd10023dcf..0c363b6f8ee720 100644 --- a/x-pack/legacy/plugins/apm/server/routes/errors.ts +++ b/x-pack/legacy/plugins/apm/server/routes/errors.ts @@ -27,10 +27,11 @@ export const errorsRoute = createRoute(core => ({ rangeRt ]) }, - handler: async (req, { query, path }) => { - const setup = await setupRequest(req); - const { serviceName } = path; - const { sortField, sortDirection } = query; + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { params } = context; + const { serviceName } = params.path; + const { sortField, sortDirection } = params.query; return getErrorGroups({ serviceName, @@ -50,9 +51,9 @@ export const errorGroupsRoute = createRoute(() => ({ }), query: t.intersection([uiFiltersRt, rangeRt]) }, - handler: async (req, { path }) => { - const setup = await setupRequest(req); - const { serviceName, groupId } = path; + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { serviceName, groupId } = context.params.path; return getErrorGroup({ serviceName, groupId, setup }); } })); @@ -71,10 +72,11 @@ export const errorDistributionRoute = createRoute(() => ({ rangeRt ]) }, - handler: async (req, { path, query }) => { - const setup = await setupRequest(req); - const { serviceName } = path; - const { groupId } = query; + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { params } = context; + const { serviceName } = params.path; + const { groupId } = params.query; return getErrorDistribution({ serviceName, groupId, setup }); } })); diff --git a/x-pack/legacy/plugins/apm/server/routes/index_pattern.ts b/x-pack/legacy/plugins/apm/server/routes/index_pattern.ts index 5e2b7a378f2bcb..539846430c7f84 100644 --- a/x-pack/legacy/plugins/apm/server/routes/index_pattern.ts +++ b/x-pack/legacy/plugins/apm/server/routes/index_pattern.ts @@ -8,15 +8,15 @@ import { createStaticIndexPattern } from '../lib/index_pattern/create_static_ind import { createRoute } from './create_route'; import { setupRequest } from '../lib/helpers/setup_request'; -export const staticIndexPatternRoute = createRoute((core, { server }) => ({ +export const staticIndexPatternRoute = createRoute(() => ({ method: 'POST', path: '/api/apm/index_pattern/static', - handler: async (req, params, h) => { - const setup = await setupRequest(req); - await createStaticIndexPattern(setup, server); + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + await createStaticIndexPattern(setup, context); // send empty response regardless of outcome - return h.response().code(204); + return undefined; } })); @@ -31,8 +31,8 @@ export const dynamicIndexPatternRoute = createRoute(() => ({ ]) }) }, - handler: async request => { - const { dynamicIndexPattern } = await setupRequest(request); + handler: async ({ context, request }) => { + const { dynamicIndexPattern } = await setupRequest(context, request); return { dynamicIndexPattern }; } })); diff --git a/x-pack/legacy/plugins/apm/server/routes/metrics.ts b/x-pack/legacy/plugins/apm/server/routes/metrics.ts index ef9145b3dcd4a0..74fa625af88021 100644 --- a/x-pack/legacy/plugins/apm/server/routes/metrics.ts +++ b/x-pack/legacy/plugins/apm/server/routes/metrics.ts @@ -27,10 +27,11 @@ export const metricsChartsRoute = createRoute(() => ({ rangeRt ]) }, - handler: async (req, { path, query }) => { - const setup = await setupRequest(req); - const { serviceName } = path; - const { agentName, serviceNodeName } = query; + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { params } = context; + const { serviceName } = params.path; + const { agentName, serviceNodeName } = params.query; return await getMetricsChartDataByAgent({ setup, serviceName, diff --git a/x-pack/legacy/plugins/apm/server/routes/service_nodes.ts b/x-pack/legacy/plugins/apm/server/routes/service_nodes.ts index 285dd5b1f10f5e..33ecbb316d4152 100644 --- a/x-pack/legacy/plugins/apm/server/routes/service_nodes.ts +++ b/x-pack/legacy/plugins/apm/server/routes/service_nodes.ts @@ -17,9 +17,10 @@ export const serviceNodesRoute = createRoute(core => ({ }), query: t.intersection([rangeRt, uiFiltersRt]) }, - handler: async (req, { path }) => { - const setup = await setupRequest(req); - const { serviceName } = path; + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { params } = context; + const { serviceName } = params.path; return getServiceNodes({ setup, diff --git a/x-pack/legacy/plugins/apm/server/routes/services.ts b/x-pack/legacy/plugins/apm/server/routes/services.ts index 4b955c7a6e981f..ea9fdaa2a4aa42 100644 --- a/x-pack/legacy/plugins/apm/server/routes/services.ts +++ b/x-pack/legacy/plugins/apm/server/routes/services.ts @@ -5,6 +5,7 @@ */ import * as t from 'io-ts'; +import Boom from 'boom'; import { AgentName } from '../../typings/es_schemas/ui/fields/Agent'; import { createApmTelementry, @@ -19,13 +20,13 @@ import { createRoute } from './create_route'; import { uiFiltersRt, rangeRt } from './default_api_types'; import { getServiceMap } from '../lib/services/map'; -export const servicesRoute = createRoute((core, { server }) => ({ +export const servicesRoute = createRoute(() => ({ path: '/api/apm/services', params: { query: t.intersection([uiFiltersRt, rangeRt]) }, - handler: async req => { - const setup = await setupRequest(req); + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); const services = await getServices(setup); // Store telemetry data derived from services @@ -33,7 +34,7 @@ export const servicesRoute = createRoute((core, { server }) => ({ ({ agentName }) => agentName as AgentName ); const apmTelemetry = createApmTelementry(agentNames); - storeApmServicesTelemetry(server, apmTelemetry); + storeApmServicesTelemetry(context.__LEGACY.server, apmTelemetry); return services; } @@ -47,9 +48,9 @@ export const serviceAgentNameRoute = createRoute(() => ({ }), query: rangeRt }, - handler: async (req, { path }) => { - const setup = await setupRequest(req); - const { serviceName } = path; + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { serviceName } = context.params.path; return getServiceAgentName(serviceName, setup); } })); @@ -62,9 +63,9 @@ export const serviceTransactionTypesRoute = createRoute(() => ({ }), query: rangeRt }, - handler: async (req, { path }) => { - const setup = await setupRequest(req); - const { serviceName } = path; + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { serviceName } = context.params.path; return getServiceTransactionTypes(serviceName, setup); } })); @@ -78,9 +79,9 @@ export const serviceNodeMetadataRoute = createRoute(() => ({ }), query: t.intersection([uiFiltersRt, rangeRt]) }, - handler: async (req, { path }) => { - const setup = await setupRequest(req); - const { serviceName, serviceNodeName } = path; + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { serviceName, serviceNodeName } = context.params.path; return getServiceNodeMetadata({ setup, serviceName, serviceNodeName }); } })); @@ -90,12 +91,10 @@ export const serviceMapRoute = createRoute(() => ({ params: { query: rangeRt }, - handler: async (request, _response, hapi) => { - const setup = await setupRequest(request); - if (setup.config.get('xpack.apm.serviceMapEnabled')) { + handler: async ({ context }) => { + if (context.config['xpack.apm.servicemapEnabled']) { return getServiceMap(); - } else { - return hapi.response().code(404); } + return new Boom('Not found', { statusCode: 404 }); } })); diff --git a/x-pack/legacy/plugins/apm/server/routes/settings/agent_configuration.ts b/x-pack/legacy/plugins/apm/server/routes/settings/agent_configuration.ts index 2867cef28d952b..b897dfb4b91235 100644 --- a/x-pack/legacy/plugins/apm/server/routes/settings/agent_configuration.ts +++ b/x-pack/legacy/plugins/apm/server/routes/settings/agent_configuration.ts @@ -5,6 +5,7 @@ */ import * as t from 'io-ts'; +import Boom from 'boom'; import { setupRequest } from '../../lib/helpers/setup_request'; import { getServiceNames } from '../../lib/settings/agent_configuration/get_service_names'; import { createOrUpdateConfiguration } from '../../lib/settings/agent_configuration/create_or_update_configuration'; @@ -21,8 +22,8 @@ import { markAppliedByAgent } from '../../lib/settings/agent_configuration/mark_ // get list of configurations export const agentConfigurationRoute = createRoute(core => ({ path: '/api/apm/settings/agent-configuration', - handler: async req => { - const setup = await setupRequest(req); + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); return await listConfigurations({ setup }); } })); @@ -39,9 +40,9 @@ export const deleteAgentConfigurationRoute = createRoute(() => ({ configurationId: t.string }) }, - handler: async (req, { path }) => { - const setup = await setupRequest(req); - const { configurationId } = path; + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { configurationId } = context.params.path; return await deleteConfiguration({ configurationId, setup @@ -53,8 +54,8 @@ export const deleteAgentConfigurationRoute = createRoute(() => ({ export const listAgentConfigurationServicesRoute = createRoute(() => ({ method: 'GET', path: '/api/apm/settings/agent-configuration/services', - handler: async req => { - const setup = await setupRequest(req); + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); return await getServiceNames({ setup }); @@ -84,9 +85,9 @@ export const listAgentConfigurationEnvironmentsRoute = createRoute(() => ({ params: { query: t.partial({ serviceName: t.string }) }, - handler: async (req, { query }) => { - const setup = await setupRequest(req); - const { serviceName } = query; + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { serviceName } = context.params.query; return await getEnvironments({ serviceName, setup }); } })); @@ -97,9 +98,9 @@ export const agentConfigurationAgentNameRoute = createRoute(() => ({ params: { query: t.type({ serviceName: t.string }) }, - handler: async (req, { query }) => { - const setup = await setupRequest(req); - const { serviceName } = query; + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { serviceName } = context.params.query; const agentName = await getAgentNameByService({ serviceName, setup }); return agentName; } @@ -114,9 +115,12 @@ export const createAgentConfigurationRoute = createRoute(() => ({ options: { tags: ['access:apm', 'access:apm_write'] }, - handler: async (req, { body }) => { - const setup = await setupRequest(req); - return await createOrUpdateConfiguration({ configuration: body, setup }); + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + return await createOrUpdateConfiguration({ + configuration: context.params.body, + setup + }); } })); @@ -132,12 +136,12 @@ export const updateAgentConfigurationRoute = createRoute(() => ({ }), body: agentPayloadRt }, - handler: async (req, { path, body }) => { - const setup = await setupRequest(req); - const { configurationId } = path; + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { configurationId } = context.params.path; return await createOrUpdateConfiguration({ configurationId, - configuration: body, + configuration: context.params.body, setup }); } @@ -156,8 +160,9 @@ export const agentConfigurationSearchRoute = createRoute(core => ({ etag: t.string }) }, - handler: async (req, { body }, h) => { - const setup = await setupRequest(req); + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { body } = context.params; const config = await searchConfigurations({ serviceName: body.service.name, environment: body.service.environment, @@ -165,7 +170,7 @@ export const agentConfigurationSearchRoute = createRoute(core => ({ }); if (!config) { - return h.response().code(404); + throw new Boom('Not found', { statusCode: 404 }); } // update `applied_by_agent` field if etags match diff --git a/x-pack/legacy/plugins/apm/server/routes/settings/apm_indices.ts b/x-pack/legacy/plugins/apm/server/routes/settings/apm_indices.ts index 4afcf135a1a76d..b66eb05f6eda50 100644 --- a/x-pack/legacy/plugins/apm/server/routes/settings/apm_indices.ts +++ b/x-pack/legacy/plugins/apm/server/routes/settings/apm_indices.ts @@ -13,28 +13,20 @@ import { import { saveApmIndices } from '../../lib/settings/apm_indices/save_apm_indices'; // get list of apm indices and values -export const apmIndexSettingsRoute = createRoute((core, { server }) => ({ +export const apmIndexSettingsRoute = createRoute(() => ({ method: 'GET', path: '/api/apm/settings/apm-index-settings', - handler: async req => { - const config = server.config(); - const savedObjectsClient = req.server.savedObjects.getScopedSavedObjectsClient( - req - ); - return await getApmIndexSettings({ config, savedObjectsClient }); + handler: async ({ context }) => { + return await getApmIndexSettings({ context }); } })); // get apm indices configuration object -export const apmIndicesRoute = createRoute((core, { server }) => ({ +export const apmIndicesRoute = createRoute(() => ({ method: 'GET', path: '/api/apm/settings/apm-indices', - handler: async req => { - const config = server.config(); - const savedObjectsClient = req.server.savedObjects.getScopedSavedObjectsClient( - req - ); - return await getApmIndices({ config, savedObjectsClient }); + handler: async ({ context }) => { + return await getApmIndices(context); } })); @@ -49,14 +41,11 @@ export const saveApmIndicesRoute = createRoute(() => ({ 'apm_oss.onboardingIndices': t.string, 'apm_oss.spanIndices': t.string, 'apm_oss.transactionIndices': t.string, - 'apm_oss.metricsIndices': t.string, - 'apm_oss.apmAgentConfigurationIndex': t.string + 'apm_oss.metricsIndices': t.string }) }, - handler: async (req, { body }) => { - const savedObjectsClient = req.server.savedObjects.getScopedSavedObjectsClient( - req - ); - return await saveApmIndices(savedObjectsClient, body); + handler: async ({ context, request }) => { + const { body } = context.params; + return await saveApmIndices(context, body); } })); diff --git a/x-pack/legacy/plugins/apm/server/routes/traces.ts b/x-pack/legacy/plugins/apm/server/routes/traces.ts index cd2c86a5c9ca3c..089408a03afe92 100644 --- a/x-pack/legacy/plugins/apm/server/routes/traces.ts +++ b/x-pack/legacy/plugins/apm/server/routes/traces.ts @@ -16,8 +16,8 @@ export const tracesRoute = createRoute(() => ({ params: { query: t.intersection([rangeRt, uiFiltersRt]) }, - handler: async req => { - const setup = await setupRequest(req); + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); return getTransactionGroupList({ type: 'top_traces' }, setup); } })); @@ -30,9 +30,8 @@ export const tracesByIdRoute = createRoute(() => ({ }), query: rangeRt }, - handler: async (req, { path }) => { - const { traceId } = path; - const setup = await setupRequest(req); - return getTrace(traceId, setup); + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + return getTrace(context.params.path.traceId, setup); } })); diff --git a/x-pack/legacy/plugins/apm/server/routes/transaction_groups.ts b/x-pack/legacy/plugins/apm/server/routes/transaction_groups.ts index 269f5fee9738c9..2170a8fbb96924 100644 --- a/x-pack/legacy/plugins/apm/server/routes/transaction_groups.ts +++ b/x-pack/legacy/plugins/apm/server/routes/transaction_groups.ts @@ -29,10 +29,10 @@ export const transactionGroupsRoute = createRoute(() => ({ rangeRt ]) }, - handler: async (req, { path, query }) => { - const { serviceName } = path; - const { transactionType } = query; - const setup = await setupRequest(req); + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { serviceName } = context.params.path; + const { transactionType } = context.params.query; return getTransactionGroupList( { @@ -60,10 +60,10 @@ export const transactionGroupsChartsRoute = createRoute(() => ({ rangeRt ]) }, - handler: async (req, { path, query }) => { - const setup = await setupRequest(req); - const { serviceName } = path; - const { transactionType, transactionName } = query; + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { serviceName } = context.params.path; + const { transactionType, transactionName } = context.params.query; return getTransactionCharts({ serviceName, @@ -93,15 +93,15 @@ export const transactionGroupsDistributionRoute = createRoute(() => ({ rangeRt ]) }, - handler: async (req, { path, query }) => { - const setup = await setupRequest(req); - const { serviceName } = path; + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { serviceName } = context.params.path; const { transactionType, transactionName, transactionId = '', traceId = '' - } = query; + } = context.params.query; return getTransactionDistribution({ serviceName, @@ -131,10 +131,10 @@ export const transactionGroupsBreakdownRoute = createRoute(() => ({ rangeRt ]) }, - handler: async (req, { path, query }) => { - const setup = await setupRequest(req); - const { serviceName } = path; - const { transactionName, transactionType } = query; + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { serviceName } = context.params.path; + const { transactionName, transactionType } = context.params.query; return getTransactionBreakdown({ serviceName, @@ -160,9 +160,9 @@ export const transactionGroupsAvgDurationByBrowser = createRoute(() => ({ rangeRt ]) }, - handler: async (req, { path }) => { - const setup = await setupRequest(req); - const { serviceName } = path; + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { serviceName } = context.params.path; return getTransactionAvgDurationByBrowser({ serviceName, @@ -183,10 +183,10 @@ export const transactionGroupsAvgDurationByCountry = createRoute(() => ({ t.partial({ transactionName: t.string }) ]) }, - handler: async (req, { path, query }) => { - const setup = await setupRequest(req); - const { serviceName } = path; - const { transactionName } = query; + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { serviceName } = context.params.path; + const { transactionName } = context.params.query; return getTransactionAvgDurationByCountry({ serviceName, diff --git a/x-pack/legacy/plugins/apm/server/routes/typings.ts b/x-pack/legacy/plugins/apm/server/routes/typings.ts index 77d96d3677494c..207fe7fe5da339 100644 --- a/x-pack/legacy/plugins/apm/server/routes/typings.ts +++ b/x-pack/legacy/plugins/apm/server/routes/typings.ts @@ -5,11 +5,17 @@ */ import t from 'io-ts'; -import { Request, ResponseToolkit } from 'hapi'; -import { CoreSetup } from 'src/core/server'; +import { + CoreSetup, + KibanaRequest, + RequestHandlerContext, + Logger +} from 'src/core/server'; import { PickByValue, Optional } from 'utility-types'; +import { Observable } from 'rxjs'; +import { Server } from 'hapi'; import { FetchOptions } from '../../public/services/rest/callApi'; -import { LegacySetup } from '../new-platform/plugin'; +import { APMConfig } from '../../../../../plugins/apm/server'; export interface Params { query?: t.HasProps; @@ -37,22 +43,41 @@ export interface Route< options?: { tags: Array<'access:apm' | 'access:apm_write'>; }; - handler: ( - req: Request, - params: DecodeParams, - h: ResponseToolkit - ) => Promise; + handler: (kibanaContext: { + context: APMRequestHandlerContext>; + request: KibanaRequest; + }) => Promise; } +export type APMLegacyServer = Pick & { + usage: { + collectorSet: { + makeUsageCollector: (options: unknown) => unknown; + register: (options: unknown) => unknown; + }; + }; + plugins: { + elasticsearch: Server['plugins']['elasticsearch']; + }; +}; + +export type APMRequestHandlerContext< + TDecodedParams extends { [key in keyof Params]: any } = {} +> = RequestHandlerContext & { + params: { query: { _debug: boolean } } & TDecodedParams; + config: APMConfig; + logger: Logger; + __LEGACY: { + server: APMLegacyServer; + }; +}; + export type RouteFactoryFn< TPath extends string, TMethod extends HttpMethod | undefined, TParams extends Params, TReturn -> = ( - core: CoreSetup, - __LEGACY: LegacySetup -) => Route; +> = (core: CoreSetup) => Route; export interface RouteState { [key: string]: { @@ -83,7 +108,14 @@ export interface ServerAPI { }; } >; - init: (core: CoreSetup, __LEGACY: LegacySetup) => void; + init: ( + core: CoreSetup, + context: { + config$: Observable; + logger: Logger; + __LEGACY: { server: Server }; + } + ) => void; } // without this, TS does not recognize possible existence of `params` in `options` below diff --git a/x-pack/legacy/plugins/apm/server/routes/ui_filters.ts b/x-pack/legacy/plugins/apm/server/routes/ui_filters.ts index d04b49cfe921d7..dcca41c7e00df1 100644 --- a/x-pack/legacy/plugins/apm/server/routes/ui_filters.ts +++ b/x-pack/legacy/plugins/apm/server/routes/ui_filters.ts @@ -6,7 +6,12 @@ import * as t from 'io-ts'; import { omit } from 'lodash'; -import { setupRequest, Setup } from '../lib/helpers/setup_request'; +import { + setupRequest, + Setup, + SetupUIFilters, + SetupTimeRange +} from '../lib/helpers/setup_request'; import { getEnvironments } from '../lib/ui_filters/get_environments'; import { Projection } from '../../common/projections/typings'; import { @@ -35,9 +40,9 @@ export const uiFiltersEnvironmentsRoute = createRoute(() => ({ rangeRt ]) }, - handler: async (req, { query }) => { - const setup = await setupRequest(req); - const { serviceName } = query; + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { serviceName } = context.params.query; return getEnvironments(setup, serviceName); } })); @@ -76,19 +81,19 @@ function createLocalFiltersRoute< return createRoute(() => ({ path, params: { - query: queryRt - ? t.intersection([queryRt, localUiBaseQueryRt]) - : localUiBaseQueryRt + query: t.intersection([localUiBaseQueryRt, queryRt]) }, - handler: async (request, { query }: { query: t.TypeOf }) => { - const setup = await setupRequest(request); + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { query } = context.params; + const { uiFilters, filterNames } = query; const parsedUiFilters = JSON.parse(uiFilters); const projection = getProjection({ query, setup: { ...setup, - uiFiltersES: await getUiFiltersES( + uiFiltersES: getUiFiltersES( setup.dynamicIndexPattern, omit(parsedUiFilters, filterNames) ) @@ -222,5 +227,5 @@ type GetProjection< setup }: { query: t.TypeOf; - setup: Setup; + setup: Setup & SetupUIFilters & SetupTimeRange; }) => TProjection; diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json new file mode 100644 index 00000000000000..8161f6ee06bf8e --- /dev/null +++ b/x-pack/plugins/apm/kibana.json @@ -0,0 +1,14 @@ +{ + "id": "apm", + "server": true, + "version": "8.0.0", + "kibanaVersion": "kibana", + "configPath": [ + "xpack", + "apm" + ], + "ui": false, + "requiredPlugins": [ + "apm_oss" + ] +} diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts new file mode 100644 index 00000000000000..6e1257bc4d1c4f --- /dev/null +++ b/x-pack/plugins/apm/server/index.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { PluginInitializerContext } from 'src/core/server'; +import { APMOSSConfig } from 'src/plugins/apm_oss/server'; +import { APMPlugin } from './plugin'; + +export const config = { + schema: schema.object({ + servicemapEnabled: schema.boolean({ defaultValue: false }), + autocreateApmIndexPattern: schema.boolean({ defaultValue: true }), + 'ui.transactionGroupBucketSize': schema.number({ defaultValue: 100 }), + 'ui.maxTraceItems': schema.number({ defaultValue: 1000 }), + }), +}; + +export type APMXPackConfig = TypeOf; + +export function mergeConfigs(apmOssConfig: APMOSSConfig, apmConfig: APMXPackConfig) { + return { + 'apm_oss.transactionIndices': apmOssConfig.transactionIndices, + 'apm_oss.spanIndices': apmOssConfig.spanIndices, + 'apm_oss.errorIndices': apmOssConfig.errorIndices, + 'apm_oss.metricsIndices': apmOssConfig.metricsIndices, + 'apm_oss.sourcemapIndices': apmOssConfig.sourcemapIndices, + 'apm_oss.onboardingIndices': apmOssConfig.onboardingIndices, + 'apm_oss.indexPattern': apmOssConfig.indexPattern, + 'xpack.apm.servicemapEnabled': apmConfig.servicemapEnabled, + 'xpack.apm.ui.maxTraceItems': apmConfig['ui.maxTraceItems'], + 'xpack.apm.ui.transactionGroupBucketSize': apmConfig['ui.transactionGroupBucketSize'], + 'xpack.apm.autocreateApmIndexPattern': apmConfig.autocreateApmIndexPattern, + }; +} + +export type APMConfig = ReturnType; + +export const plugin = (initContext: PluginInitializerContext) => new APMPlugin(initContext); + +export { APMPlugin } from './plugin'; diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts new file mode 100644 index 00000000000000..b28e00adcc6d18 --- /dev/null +++ b/x-pack/plugins/apm/server/plugin.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { PluginInitializerContext, Plugin, CoreSetup } from 'src/core/server'; +import { Observable, combineLatest, AsyncSubject } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Server } from 'hapi'; +import { once } from 'lodash'; +import { Plugin as APMOSSPlugin } from '../../../../src/plugins/apm_oss/server'; +import { createApmAgentConfigurationIndex } from '../../../legacy/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index'; +import { createApmApi } from '../../../legacy/plugins/apm/server/routes/create_apm_api'; +import { APMConfig, mergeConfigs, APMXPackConfig } from '.'; + +export interface LegacySetup { + server: Server; +} + +export interface APMPluginContract { + config$: Observable; + registerLegacyAPI: (__LEGACY: LegacySetup) => void; +} + +export class APMPlugin implements Plugin { + legacySetup$: AsyncSubject; + constructor(private readonly initContext: PluginInitializerContext) { + this.initContext = initContext; + this.legacySetup$ = new AsyncSubject(); + } + + public setup( + core: CoreSetup, + plugins: { + apm_oss: APMOSSPlugin extends Plugin ? TSetup : never; + } + ) { + const config$ = this.initContext.config.create(); + const logger = this.initContext.logger.get('apm'); + + const mergedConfig$ = combineLatest(plugins.apm_oss.config$, config$).pipe( + map(([apmOssConfig, apmConfig]) => mergeConfigs(apmOssConfig, apmConfig)) + ); + + this.legacySetup$.subscribe(__LEGACY => { + createApmApi().init(core, { config$: mergedConfig$, logger, __LEGACY }); + }); + + combineLatest(mergedConfig$, core.elasticsearch.dataClient$).subscribe( + ([config, dataClient]) => { + createApmAgentConfigurationIndex({ + esClient: dataClient, + config, + }); + } + ); + + return { + config$: mergedConfig$, + registerLegacyAPI: once((__LEGACY: LegacySetup) => { + this.legacySetup$.next(__LEGACY); + this.legacySetup$.complete(); + }), + }; + } + + public start() {} + public stop() {} +} From 345ce4d12572c9bbf0c2e65d0af2a16aed96c8d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?= Date: Thu, 21 Nov 2019 15:57:13 +0100 Subject: [PATCH 6/6] Add `-no-base-path` to readme --- x-pack/legacy/plugins/apm/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/apm/readme.md b/x-pack/legacy/plugins/apm/readme.md index a46b0c2895fcae..c5fc1be2b4d563 100644 --- a/x-pack/legacy/plugins/apm/readme.md +++ b/x-pack/legacy/plugins/apm/readme.md @@ -8,7 +8,7 @@ git clone git@github.com:elastic/kibana.git cd kibana/ yarn kbn bootstrap -yarn start +yarn start --no-base-path ``` #### APM Server, Elasticsearch and data