From 68346eceffe778bcfe10fe2449c10db5a5f5eb7d Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Thu, 21 Nov 2019 14:24:37 +0100 Subject: [PATCH 1/3] 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 1e4a5e6fb58ec..0000000000000 --- 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 0000000000000..0b62aecc1d13f --- /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 65b8792532acf..eab96cc80c883 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 62533b7ae734d..f849b6bf5cdfa 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 9d52a339ccf91..f7dbf992e8728 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 073a6961fdec4..feb63817fe073 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 7783fd9976963..031464c7fdaff 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 580fe04b92087..f535f237c11de 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 1a17970081d9c..5ac83aee52a65 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 33a1cbd4d780b..b8349e5e41ccb 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 1c99637a89fed..b8aa57291dccf 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/3] 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 ab52d56841612..37d96a51d66d2 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 23c9c9ffc94bb..e80bffb5e3c68 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 84da576e8205c..4f9424f30f516 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 577861db38faf..6dc9bc2300e04 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 b7d20526a6b92..e4487af42beaf 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 d16158226579c..1b4bdb881116b 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 469f5355df106..a3da03db71d6e 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 c8b36d84f440e..092bf8daa8f2e 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 6a5229ac826cb..3c39a770377a0 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 0dbe92dcb0da6..27406232dd5d3 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 2412541e8c5c8..ffce162cadde4 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 c9c52400b1f19..f97246bc5a9bf 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 1a186a6514763..cff48144489f0 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 1c877f4f14251..8542c1dcce24d 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 2dd3f370c6d6a..f51ae86b5c9a7 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 3b4952ac81519..690a9cffaa138 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 0000000000000..22b44035d6ca8 --- /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 0000000000000..affd213c29517 --- /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 0000000000000..b0e992318327e --- /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 0000000000000..2e90ff0fe0691 --- /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 c19545eb83a06..1bd534bf74ff7 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 d8af7b3e97ad2..4bf7e1c9c6ba7 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 0000000000000..2f31fafcb74e4 --- /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 0000000000000..43d4bdaf03bc1 --- /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 27f51c1f44cf2..79856c9e0267e 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 d8f7b5091eb8f..2b43dffa8c161 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 0000000000000..c7bec5e5ad347 --- /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 0000000000000..e2707d469a317 --- /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 f2fd55af4f418..b4d46ae9fb3cf 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 8a20337317cfb..6eae233cf5dea 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 3d7e1979d224b..1a00c668833aa 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/3] 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 7e4b6babae449..c9a40975e7b92 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 e000eeb14214e..4ac25720080a9 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",