From 3b97a965ed2c036d4a8cc41ee2c1338b07bbe5c8 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Sat, 21 Aug 2021 07:08:43 -0400 Subject: [PATCH 01/27] [ML] Functional tests - re-enable modules API test suite (#109471) (#109574) https://kibana-ci.elastic.co/job/kibana+flaky-test-suite-runner/1839/ Co-authored-by: Robert Oskamp --- x-pack/test/api_integration/apis/ml/modules/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/test/api_integration/apis/ml/modules/index.ts b/x-pack/test/api_integration/apis/ml/modules/index.ts index ffb1a14a18469..c6a75eccfa4c8 100644 --- a/x-pack/test/api_integration/apis/ml/modules/index.ts +++ b/x-pack/test/api_integration/apis/ml/modules/index.ts @@ -14,8 +14,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { const fleetPackages = ['apache', 'nginx']; - // FLAKY: https://github.com/elastic/kibana/issues/102282 - describe.skip('modules', function () { + describe('modules', function () { before(async () => { // use empty_kibana to make sure the fleet setup is removed correctly after the tests await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); From dd45adeacc8534d73f824a3b8cc3a6a39740f5a9 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 23 Aug 2021 00:08:19 +0100 Subject: [PATCH 02/27] skip flaky suite (#106650) --- x-pack/test/functional/apps/infra/home_page.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index e344f86f89fe8..255f2c49e5621 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -87,7 +87,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - describe('Saved Views', () => { + // FLAKY: https://github.com/elastic/kibana/issues/106650 + describe.skip('Saved Views', () => { before(() => esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs')); after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs')); it('should have save and load controls', async () => { From e31d32ae14ddf3b3fb9fc6f629d685ed18700135 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 23 Aug 2021 00:21:24 +0100 Subject: [PATCH 03/27] skip failing es promotion suites (#109583) --- .../tests/correlations/latency_slow_transactions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/apm_api_integration/tests/correlations/latency_slow_transactions.ts b/x-pack/test/apm_api_integration/tests/correlations/latency_slow_transactions.ts index fc56615b3b13a..dac9ed70bc483 100644 --- a/x-pack/test/apm_api_integration/tests/correlations/latency_slow_transactions.ts +++ b/x-pack/test/apm_api_integration/tests/correlations/latency_slow_transactions.ts @@ -43,7 +43,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { } ); - registry.when( + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/109583 + registry.when.skip( 'correlations latency slow transactions with data and default args', { config: 'trial', archives: ['apm_8.0.0'] }, () => { From f4b98a09dbf3d270ce360318f15047cf1bf0a30f Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 23 Aug 2021 02:24:55 -0400 Subject: [PATCH 04/27] Fix grammar on stack monitoring alerts modal (#109360) (#109585) Pretty sure the rules only get created in the current kibana space, but the message is plural. Switching to singular. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Mat Schaffer --- docs/user/monitoring/kibana-alerts.asciidoc | 2 +- x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user/monitoring/kibana-alerts.asciidoc b/docs/user/monitoring/kibana-alerts.asciidoc index f00a3999ab277..64ba8bf044e4f 100644 --- a/docs/user/monitoring/kibana-alerts.asciidoc +++ b/docs/user/monitoring/kibana-alerts.asciidoc @@ -124,7 +124,7 @@ valid for 30 days. == Alerts and rules [discrete] === Create default rules -This option can be used to create default rules in this kibana spaces. This is +This option can be used to create default rules in this kibana space. This is useful for scenarios when you didn't choose to create these default rules initially or anytime later if the rules were accidentally deleted. diff --git a/x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx b/x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx index 827ce958deb11..780997ca98191 100644 --- a/x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx +++ b/x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx @@ -42,7 +42,7 @@ export const EnableAlertsModal: React.FC = ({ alerts }: Props) => { { id: 'create-alerts', label: i18n.translate('xpack.monitoring.alerts.modal.yesOption', { - defaultMessage: 'Yes (Recommended - create default rules in this kibana spaces)', + defaultMessage: 'Yes (Recommended - create default rules in this kibana space)', }), }, { From f4f5a7035af58ec7599fde88cdcf44438e05eef1 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Mon, 23 Aug 2021 11:41:49 +0200 Subject: [PATCH 05/27] [Discover] Unskip discover tab field data functional tests (#107305) (#109589) --- test/functional/apps/discover/_data_grid.ts | 4 +-- .../apps/discover/_data_grid_doc_table.ts | 2 +- test/functional/apps/discover/_discover.ts | 2 +- test/functional/apps/discover/_doc_table.ts | 2 +- test/functional/apps/discover/_field_data.ts | 3 +- .../discover/_field_data_with_fields_api.ts | 3 +- test/functional/page_objects/discover_page.ts | 30 ++++++++++++++++--- 7 files changed, 33 insertions(+), 13 deletions(-) diff --git a/test/functional/apps/discover/_data_grid.ts b/test/functional/apps/discover/_data_grid.ts index efd97fce3f7f5..4a343fb30384e 100644 --- a/test/functional/apps/discover/_data_grid.ts +++ b/test/functional/apps/discover/_data_grid.ts @@ -47,10 +47,10 @@ export default function ({ await PageObjects.discover.clickFieldListItemAdd('agent'); expect(await getTitles()).to.be('Time (@timestamp) bytes agent'); - await PageObjects.discover.clickFieldListItemAdd('bytes'); + await PageObjects.discover.clickFieldListItemRemove('bytes'); expect(await getTitles()).to.be('Time (@timestamp) agent'); - await PageObjects.discover.clickFieldListItemAdd('agent'); + await PageObjects.discover.clickFieldListItemRemove('agent'); expect(await getTitles()).to.be('Time (@timestamp) Document'); }); }); diff --git a/test/functional/apps/discover/_data_grid_doc_table.ts b/test/functional/apps/discover/_data_grid_doc_table.ts index ec5a51c55265b..9ae6a9086f819 100644 --- a/test/functional/apps/discover/_data_grid_doc_table.ts +++ b/test/functional/apps/discover/_data_grid_doc_table.ts @@ -162,7 +162,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); } // remove the second column - await PageObjects.discover.clickFieldListItemAdd(extraColumns[1]); + await PageObjects.discover.clickFieldListItemRemove(extraColumns[1]); await PageObjects.header.waitUntilLoadingHasFinished(); // test that the second column is no longer there const header = await dataGrid.getHeaderFields(); diff --git a/test/functional/apps/discover/_discover.ts b/test/functional/apps/discover/_discover.ts index 7c97220f36949..87960d1b6a966 100644 --- a/test/functional/apps/discover/_discover.ts +++ b/test/functional/apps/discover/_discover.ts @@ -336,7 +336,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.clickFieldSort('_score', 'Sort Low-High'); const currentUrlWithScore = await browser.getCurrentUrl(); expect(currentUrlWithScore).to.contain('_score'); - await PageObjects.discover.clickFieldListItemAdd('_score'); + await PageObjects.discover.clickFieldListItemRemove('_score'); const currentUrlWithoutScore = await browser.getCurrentUrl(); expect(currentUrlWithoutScore).not.to.contain('_score'); }); diff --git a/test/functional/apps/discover/_doc_table.ts b/test/functional/apps/discover/_doc_table.ts index 09a162e051bf6..f01d6b18fbf01 100644 --- a/test/functional/apps/discover/_doc_table.ts +++ b/test/functional/apps/discover/_doc_table.ts @@ -218,7 +218,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); } // remove the second column - await PageObjects.discover.clickFieldListItemAdd(extraColumns[1]); + await PageObjects.discover.clickFieldListItemRemove(extraColumns[1]); await PageObjects.header.waitUntilLoadingHasFinished(); // test that the second column is no longer there const docHeader = await find.byCssSelector('thead > tr:nth-child(1)'); diff --git a/test/functional/apps/discover/_field_data.ts b/test/functional/apps/discover/_field_data.ts index fc0c0c6a48649..27407e9a0bc4d 100644 --- a/test/functional/apps/discover/_field_data.ts +++ b/test/functional/apps/discover/_field_data.ts @@ -34,8 +34,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await PageObjects.common.navigateToApp('discover'); }); - // FLAKY: https://github.com/elastic/kibana/issues/100437 - describe.skip('field data', function () { + describe('field data', function () { it('search php should show the correct hit count', async function () { const expectedHitCount = '445'; await retry.try(async function () { diff --git a/test/functional/apps/discover/_field_data_with_fields_api.ts b/test/functional/apps/discover/_field_data_with_fields_api.ts index 97c1e678c4a9f..666377ae7f794 100644 --- a/test/functional/apps/discover/_field_data_with_fields_api.ts +++ b/test/functional/apps/discover/_field_data_with_fields_api.ts @@ -34,8 +34,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await PageObjects.common.navigateToApp('discover'); }); - // FLAKY: https://github.com/elastic/kibana/issues/103389 - describe.skip('field data', function () { + describe('field data', function () { it('search php should show the correct hit count', async function () { const expectedHitCount = '445'; await retry.try(async function () { diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index 9ffb206760867..b964566b06927 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -360,17 +360,39 @@ export class DiscoverPageObject extends FtrService { public async clickFieldListItemAdd(field: string) { // a filter check may make sense here, but it should be properly handled to make // it work with the _score and _source fields as well + if (await this.isFieldSelected(field)) { + return; + } await this.clickFieldListItemToggle(field); + const isLegacyDefault = await this.useLegacyTable(); + if (isLegacyDefault) { + await this.retry.waitFor(`field ${field} to be added to classic table`, async () => { + return await this.testSubjects.exists(`docTableHeader-${field}`); + }); + } else { + await this.retry.waitFor(`field ${field} to be added to new table`, async () => { + return await this.testSubjects.exists(`dataGridHeaderCell-${field}`); + }); + } } - public async clickFieldListItemRemove(field: string) { + public async isFieldSelected(field: string) { if (!(await this.testSubjects.exists('fieldList-selected'))) { - return; + return false; } const selectedList = await this.testSubjects.find('fieldList-selected'); - if (await this.testSubjects.descendantExists(`field-${field}`, selectedList)) { - await this.clickFieldListItemToggle(field); + return await this.testSubjects.descendantExists(`field-${field}`, selectedList); + } + + public async clickFieldListItemRemove(field: string) { + if ( + !(await this.testSubjects.exists('fieldList-selected')) || + !(await this.isFieldSelected(field)) + ) { + return; } + + await this.clickFieldListItemToggle(field); } public async clickFieldListItemVisualize(fieldName: string) { From 171f6559a183183bee518e1939df25af88532b68 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 23 Aug 2021 08:16:58 -0400 Subject: [PATCH 06/27] [Flaky test] Application Usage: Wait for chrome to visible (#109405) (#109600) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alejandro Fernández Haro --- .../test_suites/application_usage/index.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/x-pack/test/usage_collection/test_suites/application_usage/index.ts b/x-pack/test/usage_collection/test_suites/application_usage/index.ts index 43d7714dfea1d..fc53c8ddf5ed3 100644 --- a/x-pack/test/usage_collection/test_suites/application_usage/index.ts +++ b/x-pack/test/usage_collection/test_suites/application_usage/index.ts @@ -16,8 +16,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); it('keys in the schema match the registered application IDs', async () => { - await common.navigateToApp('home'); // Navigate to Home to make sure all the appIds are loaded + await common.navigateToApp('home'); // Navigate to Home + await common.isChromeVisible(); // Make sure the page is fully loaded const appIds = await browser.execute(() => window.__applicationIds__); + if (!appIds || !Array.isArray(appIds)) { + throw new Error( + 'Failed to retrieve all the existing applications in Kibana. Did it fail to boot or to navigate to home?' + ); + } try { expect(Object.keys(applicationUsageSchema).sort()).to.eql(appIds.sort()); } catch (err) { From d17d2e7cea5c505d64ade4cdd6ee75bbc106e52c Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 23 Aug 2021 08:22:55 -0400 Subject: [PATCH 07/27] Add multiple namespaces support to PIT search and finder (#109062) (#109603) * initial modifications * change approach for openPointInTime and add tests for spaces wrapper changes * fix and add security wrapper tests * fix export security FTR tests * update generated doc * add tests for PIT finder * NIT * improve doc * nits Co-authored-by: Pierre Gayvallet --- ...rver.savedobjectsopenpointintimeoptions.md | 3 +- ...bjectsopenpointintimeoptions.namespaces.md | 15 ++ .../service/lib/point_in_time_finder.test.ts | 222 +++++++++--------- .../service/lib/point_in_time_finder.ts | 4 +- .../service/saved_objects_client.ts | 11 +- src/core/server/server.api.md | 3 +- ...ecure_saved_objects_client_wrapper.test.ts | 116 +++++++-- .../secure_saved_objects_client_wrapper.ts | 31 +-- .../spaces_saved_objects_client.test.ts | 85 ++++++- .../spaces_saved_objects_client.ts | 63 ++--- .../common/suites/export.ts | 12 +- .../security_and_spaces/apis/export.ts | 2 +- .../security_only/apis/export.ts | 2 +- 13 files changed, 384 insertions(+), 185 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsopenpointintimeoptions.namespaces.md diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsopenpointintimeoptions.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsopenpointintimeoptions.md index 46516be2329e9..fc825e3bf2937 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsopenpointintimeoptions.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsopenpointintimeoptions.md @@ -8,7 +8,7 @@ Signature: ```typescript -export interface SavedObjectsOpenPointInTimeOptions extends SavedObjectsBaseOptions +export interface SavedObjectsOpenPointInTimeOptions ``` ## Properties @@ -16,5 +16,6 @@ export interface SavedObjectsOpenPointInTimeOptions extends SavedObjectsBaseOpti | Property | Type | Description | | --- | --- | --- | | [keepAlive](./kibana-plugin-core-server.savedobjectsopenpointintimeoptions.keepalive.md) | string | Optionally specify how long ES should keep the PIT alive until the next request. Defaults to 5m. | +| [namespaces](./kibana-plugin-core-server.savedobjectsopenpointintimeoptions.namespaces.md) | string[] | An optional list of namespaces to be used when opening the PIT.When the spaces plugin is enabled: - this will default to the user's current space (as determined by the URL) - if specified, the user's current space will be ignored - ['*'] will search across all available spaces | | [preference](./kibana-plugin-core-server.savedobjectsopenpointintimeoptions.preference.md) | string | An optional ES preference value to be used for the query. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsopenpointintimeoptions.namespaces.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsopenpointintimeoptions.namespaces.md new file mode 100644 index 0000000000000..06fb7519d52c2 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsopenpointintimeoptions.namespaces.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsOpenPointInTimeOptions](./kibana-plugin-core-server.savedobjectsopenpointintimeoptions.md) > [namespaces](./kibana-plugin-core-server.savedobjectsopenpointintimeoptions.namespaces.md) + +## SavedObjectsOpenPointInTimeOptions.namespaces property + +An optional list of namespaces to be used when opening the PIT. + +When the spaces plugin is enabled: - this will default to the user's current space (as determined by the URL) - if specified, the user's current space will be ignored - `['*']` will search across all available spaces + +Signature: + +```typescript +namespaces?: string[]; +``` diff --git a/src/core/server/saved_objects/service/lib/point_in_time_finder.test.ts b/src/core/server/saved_objects/service/lib/point_in_time_finder.test.ts index 044bb45269538..160852f9160b7 100644 --- a/src/core/server/saved_objects/service/lib/point_in_time_finder.test.ts +++ b/src/core/server/saved_objects/service/lib/point_in_time_finder.test.ts @@ -7,7 +7,6 @@ */ import { loggerMock, MockedLogger } from '../../../logging/logger.mock'; -import type { SavedObjectsClientContract } from '../../types'; import type { SavedObjectsFindResult } from '../'; import { savedObjectsRepositoryMock } from './repository.mock'; @@ -43,37 +42,67 @@ const mockHits = [ describe('createPointInTimeFinder()', () => { let logger: MockedLogger; - let find: jest.Mocked['find']; - let openPointInTimeForType: jest.Mocked['openPointInTimeForType']; - let closePointInTime: jest.Mocked['closePointInTime']; + let repository: ReturnType; beforeEach(() => { logger = loggerMock.create(); - const mockRepository = savedObjectsRepositoryMock.create(); - find = mockRepository.find; - openPointInTimeForType = mockRepository.openPointInTimeForType; - closePointInTime = mockRepository.closePointInTime; + repository = savedObjectsRepositoryMock.create(); }); describe('#find', () => { - test('throws if a PIT is already open', async () => { - openPointInTimeForType.mockResolvedValueOnce({ + test('opens a PIT with the correct parameters', async () => { + repository.openPointInTimeForType.mockResolvedValueOnce({ id: 'abc123', }); - find.mockResolvedValueOnce({ + repository.find.mockResolvedValue({ total: 2, saved_objects: mockHits, pit_id: 'abc123', per_page: 1, page: 0, }); - find.mockResolvedValueOnce({ - total: 2, - saved_objects: mockHits, - pit_id: 'abc123', - per_page: 1, - page: 1, + + const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { + type: ['visualization'], + search: 'foo*', + perPage: 1, + namespaces: ['ns1', 'ns2'], + }; + + const finder = new PointInTimeFinder(findOptions, { + logger, + client: repository, + }); + + expect(repository.openPointInTimeForType).not.toHaveBeenCalled(); + + await finder.find().next(); + + expect(repository.openPointInTimeForType).toHaveBeenCalledTimes(1); + expect(repository.openPointInTimeForType).toHaveBeenCalledWith(findOptions.type, { + namespaces: findOptions.namespaces, }); + }); + + test('throws if a PIT is already open', async () => { + repository.openPointInTimeForType.mockResolvedValueOnce({ + id: 'abc123', + }); + repository.find + .mockResolvedValueOnce({ + total: 2, + saved_objects: mockHits, + pit_id: 'abc123', + per_page: 1, + page: 0, + }) + .mockResolvedValueOnce({ + total: 2, + saved_objects: mockHits, + pit_id: 'abc123', + per_page: 1, + page: 1, + }); const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { type: ['visualization'], @@ -83,30 +112,25 @@ describe('createPointInTimeFinder()', () => { const finder = new PointInTimeFinder(findOptions, { logger, - client: { - find, - openPointInTimeForType, - closePointInTime, - }, + client: repository, }); await finder.find().next(); - expect(find).toHaveBeenCalledTimes(1); - find.mockClear(); + expect(repository.find).toHaveBeenCalledTimes(1); expect(async () => { await finder.find().next(); }).rejects.toThrowErrorMatchingInlineSnapshot( `"Point In Time has already been opened for this finder instance. Please call \`close()\` before calling \`find()\` again."` ); - expect(find).toHaveBeenCalledTimes(0); + expect(repository.find).toHaveBeenCalledTimes(1); }); test('works with a single page of results', async () => { - openPointInTimeForType.mockResolvedValueOnce({ + repository.openPointInTimeForType.mockResolvedValueOnce({ id: 'abc123', }); - find.mockResolvedValueOnce({ + repository.find.mockResolvedValueOnce({ total: 2, saved_objects: mockHits, pit_id: 'abc123', @@ -121,11 +145,7 @@ describe('createPointInTimeFinder()', () => { const finder = new PointInTimeFinder(findOptions, { logger, - client: { - find, - openPointInTimeForType, - closePointInTime, - }, + client: repository, }); const hits: SavedObjectsFindResult[] = []; for await (const result of finder.find()) { @@ -133,10 +153,10 @@ describe('createPointInTimeFinder()', () => { } expect(hits.length).toBe(2); - expect(openPointInTimeForType).toHaveBeenCalledTimes(1); - expect(closePointInTime).toHaveBeenCalledTimes(1); - expect(find).toHaveBeenCalledTimes(1); - expect(find).toHaveBeenCalledWith( + expect(repository.openPointInTimeForType).toHaveBeenCalledTimes(1); + expect(repository.closePointInTime).toHaveBeenCalledTimes(1); + expect(repository.find).toHaveBeenCalledTimes(1); + expect(repository.find).toHaveBeenCalledWith( expect.objectContaining({ pit: expect.objectContaining({ id: 'abc123', keepAlive: '2m' }), sortField: 'updated_at', @@ -147,24 +167,25 @@ describe('createPointInTimeFinder()', () => { }); test('works with multiple pages of results', async () => { - openPointInTimeForType.mockResolvedValueOnce({ + repository.openPointInTimeForType.mockResolvedValueOnce({ id: 'abc123', }); - find.mockResolvedValueOnce({ - total: 2, - saved_objects: [mockHits[0]], - pit_id: 'abc123', - per_page: 1, - page: 0, - }); - find.mockResolvedValueOnce({ - total: 2, - saved_objects: [mockHits[1]], - pit_id: 'abc123', - per_page: 1, - page: 0, - }); - find.mockResolvedValueOnce({ + repository.find + .mockResolvedValueOnce({ + total: 2, + saved_objects: [mockHits[0]], + pit_id: 'abc123', + per_page: 1, + page: 0, + }) + .mockResolvedValueOnce({ + total: 2, + saved_objects: [mockHits[1]], + pit_id: 'abc123', + per_page: 1, + page: 0, + }); + repository.find.mockResolvedValueOnce({ total: 2, saved_objects: [], per_page: 1, @@ -180,11 +201,7 @@ describe('createPointInTimeFinder()', () => { const finder = new PointInTimeFinder(findOptions, { logger, - client: { - find, - openPointInTimeForType, - closePointInTime, - }, + client: repository, }); const hits: SavedObjectsFindResult[] = []; for await (const result of finder.find()) { @@ -192,12 +209,12 @@ describe('createPointInTimeFinder()', () => { } expect(hits.length).toBe(2); - expect(openPointInTimeForType).toHaveBeenCalledTimes(1); - expect(closePointInTime).toHaveBeenCalledTimes(1); + expect(repository.openPointInTimeForType).toHaveBeenCalledTimes(1); + expect(repository.closePointInTime).toHaveBeenCalledTimes(1); // called 3 times since we need a 3rd request to check if we // are done paginating through results. - expect(find).toHaveBeenCalledTimes(3); - expect(find).toHaveBeenCalledWith( + expect(repository.find).toHaveBeenCalledTimes(3); + expect(repository.find).toHaveBeenCalledWith( expect.objectContaining({ pit: expect.objectContaining({ id: 'abc123', keepAlive: '2m' }), sortField: 'updated_at', @@ -210,10 +227,10 @@ describe('createPointInTimeFinder()', () => { describe('#close', () => { test('calls closePointInTime with correct ID', async () => { - openPointInTimeForType.mockResolvedValueOnce({ + repository.openPointInTimeForType.mockResolvedValueOnce({ id: 'test', }); - find.mockResolvedValueOnce({ + repository.find.mockResolvedValueOnce({ total: 1, saved_objects: [mockHits[0]], pit_id: 'test', @@ -229,11 +246,7 @@ describe('createPointInTimeFinder()', () => { const finder = new PointInTimeFinder(findOptions, { logger, - client: { - find, - openPointInTimeForType, - closePointInTime, - }, + client: repository, }); const hits: SavedObjectsFindResult[] = []; for await (const result of finder.find()) { @@ -241,28 +254,28 @@ describe('createPointInTimeFinder()', () => { await finder.close(); } - expect(closePointInTime).toHaveBeenCalledWith('test'); + expect(repository.closePointInTime).toHaveBeenCalledWith('test'); }); test('causes generator to stop', async () => { - openPointInTimeForType.mockResolvedValueOnce({ + repository.openPointInTimeForType.mockResolvedValueOnce({ id: 'test', }); - find.mockResolvedValueOnce({ + repository.find.mockResolvedValueOnce({ total: 2, saved_objects: [mockHits[0]], pit_id: 'test', per_page: 1, page: 0, }); - find.mockResolvedValueOnce({ + repository.find.mockResolvedValueOnce({ total: 2, saved_objects: [mockHits[1]], pit_id: 'test', per_page: 1, page: 0, }); - find.mockResolvedValueOnce({ + repository.find.mockResolvedValueOnce({ total: 2, saved_objects: [], per_page: 1, @@ -278,11 +291,7 @@ describe('createPointInTimeFinder()', () => { const finder = new PointInTimeFinder(findOptions, { logger, - client: { - find, - openPointInTimeForType, - closePointInTime, - }, + client: repository, }); const hits: SavedObjectsFindResult[] = []; for await (const result of finder.find()) { @@ -290,15 +299,15 @@ describe('createPointInTimeFinder()', () => { await finder.close(); } - expect(closePointInTime).toHaveBeenCalledTimes(1); + expect(repository.closePointInTime).toHaveBeenCalledTimes(1); expect(hits.length).toBe(1); }); test('is called if `find` throws an error', async () => { - openPointInTimeForType.mockResolvedValueOnce({ + repository.openPointInTimeForType.mockResolvedValueOnce({ id: 'test', }); - find.mockRejectedValueOnce(new Error('oops')); + repository.find.mockRejectedValueOnce(new Error('oops')); const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { type: ['visualization'], @@ -308,11 +317,7 @@ describe('createPointInTimeFinder()', () => { const finder = new PointInTimeFinder(findOptions, { logger, - client: { - find, - openPointInTimeForType, - closePointInTime, - }, + client: repository, }); const hits: SavedObjectsFindResult[] = []; try { @@ -323,27 +328,28 @@ describe('createPointInTimeFinder()', () => { // intentionally empty } - expect(closePointInTime).toHaveBeenCalledWith('test'); + expect(repository.closePointInTime).toHaveBeenCalledWith('test'); }); test('finder can be reused after closing', async () => { - openPointInTimeForType.mockResolvedValueOnce({ + repository.openPointInTimeForType.mockResolvedValueOnce({ id: 'abc123', }); - find.mockResolvedValueOnce({ - total: 2, - saved_objects: mockHits, - pit_id: 'abc123', - per_page: 1, - page: 0, - }); - find.mockResolvedValueOnce({ - total: 2, - saved_objects: mockHits, - pit_id: 'abc123', - per_page: 1, - page: 1, - }); + repository.find + .mockResolvedValueOnce({ + total: 2, + saved_objects: mockHits, + pit_id: 'abc123', + per_page: 1, + page: 0, + }) + .mockResolvedValueOnce({ + total: 2, + saved_objects: mockHits, + pit_id: 'abc123', + per_page: 1, + page: 1, + }); const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { type: ['visualization'], @@ -353,11 +359,7 @@ describe('createPointInTimeFinder()', () => { const finder = new PointInTimeFinder(findOptions, { logger, - client: { - find, - openPointInTimeForType, - closePointInTime, - }, + client: repository, }); const findA = finder.find(); @@ -370,9 +372,9 @@ describe('createPointInTimeFinder()', () => { expect((await findA.next()).done).toBe(true); expect((await findB.next()).done).toBe(true); - expect(openPointInTimeForType).toHaveBeenCalledTimes(2); - expect(find).toHaveBeenCalledTimes(2); - expect(closePointInTime).toHaveBeenCalledTimes(2); + expect(repository.openPointInTimeForType).toHaveBeenCalledTimes(2); + expect(repository.find).toHaveBeenCalledTimes(2); + expect(repository.closePointInTime).toHaveBeenCalledTimes(2); }); }); }); diff --git a/src/core/server/saved_objects/service/lib/point_in_time_finder.ts b/src/core/server/saved_objects/service/lib/point_in_time_finder.ts index f0ed943c585e5..d11be250ad0a9 100644 --- a/src/core/server/saved_objects/service/lib/point_in_time_finder.ts +++ b/src/core/server/saved_objects/service/lib/point_in_time_finder.ts @@ -139,7 +139,9 @@ export class PointInTimeFinder private async open() { try { - const { id } = await this.#client.openPointInTimeForType(this.#findOptions.type); + const { id } = await this.#client.openPointInTimeForType(this.#findOptions.type, { + namespaces: this.#findOptions.namespaces, + }); this.#pitId = id; this.#open = true; } catch (e) { diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts index 00d47d8d1fb03..1564df2969ecc 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.ts @@ -334,7 +334,7 @@ export interface SavedObjectsResolveResponse { /** * @public */ -export interface SavedObjectsOpenPointInTimeOptions extends SavedObjectsBaseOptions { +export interface SavedObjectsOpenPointInTimeOptions { /** * Optionally specify how long ES should keep the PIT alive until the next request. Defaults to `5m`. */ @@ -343,6 +343,15 @@ export interface SavedObjectsOpenPointInTimeOptions extends SavedObjectsBaseOpti * An optional ES preference value to be used for the query. */ preference?: string; + /** + * An optional list of namespaces to be used when opening the PIT. + * + * When the spaces plugin is enabled: + * - this will default to the user's current space (as determined by the URL) + * - if specified, the user's current space will be ignored + * - `['*']` will search across all available spaces + */ + namespaces?: string[]; } /** diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 47455e0c14316..1bd59dfc7fdb1 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -2460,8 +2460,9 @@ export interface SavedObjectsMigrationVersion { export type SavedObjectsNamespaceType = 'single' | 'multiple' | 'multiple-isolated' | 'agnostic'; // @public (undocumented) -export interface SavedObjectsOpenPointInTimeOptions extends SavedObjectsBaseOptions { +export interface SavedObjectsOpenPointInTimeOptions { keepAlive?: string; + namespaces?: string[]; preference?: string; } diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts index e5a2340aba3f0..2f622d9e8a0e1 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts @@ -779,17 +779,6 @@ describe('#find', () => { ); }); - test(`throws BadRequestError when searching across namespaces when pit is provided`, async () => { - const options = { - type: [type1, type2], - pit: { id: 'abc123' }, - namespaces: ['some-ns', 'another-ns'], - }; - await expect(client.find(options)).rejects.toThrowErrorMatchingInlineSnapshot( - `"_find across namespaces is not permitted when using the \`pit\` option."` - ); - }); - test(`checks privileges for user, actions, and namespaces`, async () => { const options = { type: [type1, type2], namespaces }; await expectPrivilegeCheck(client.find, { options }, namespaces); @@ -884,7 +873,7 @@ describe('#openPointInTimeForType', () => { const apiCallReturnValue = Symbol(); clientOpts.baseClient.openPointInTimeForType.mockReturnValue(apiCallReturnValue as any); - const options = { namespace }; + const options = { namespaces: [namespace] }; const result = await expectSuccess(client.openPointInTimeForType, { type, options }); expect(result).toBe(apiCallReturnValue); }); @@ -892,18 +881,113 @@ describe('#openPointInTimeForType', () => { test(`adds audit event when successful`, async () => { const apiCallReturnValue = Symbol(); clientOpts.baseClient.openPointInTimeForType.mockReturnValue(apiCallReturnValue as any); - const options = { namespace }; + const options = { namespaces: [namespace] }; await expectSuccess(client.openPointInTimeForType, { type, options }); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); expectAuditEvent('saved_object_open_point_in_time', 'unknown'); }); - test(`adds audit event when not successful`, async () => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); - await expect(() => client.openPointInTimeForType(type, { namespace })).rejects.toThrow(); + test(`throws an error when unauthorized`, async () => { + clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( + getMockCheckPrivilegesFailure + ); + const options = { namespaces: [namespace] }; + await expect(() => client.openPointInTimeForType(type, options)).rejects.toThrowError( + 'unauthorized' + ); + }); + + test(`adds audit event when unauthorized`, async () => { + clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( + getMockCheckPrivilegesFailure + ); + const options = { namespaces: [namespace] }; + await expect(() => client.openPointInTimeForType(type, options)).rejects.toThrow(); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); expectAuditEvent('saved_object_open_point_in_time', 'failure'); }); + + test(`filters types based on authorization`, async () => { + clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockResolvedValue({ + hasAllRequested: false, + username: USERNAME, + privileges: { + kibana: [ + { + resource: 'some-ns', + privilege: 'mock-saved_object:foo/open_point_in_time', + authorized: true, + }, + { + resource: 'some-ns', + privilege: 'mock-saved_object:bar/open_point_in_time', + authorized: true, + }, + { + resource: 'some-ns', + privilege: 'mock-saved_object:baz/open_point_in_time', + authorized: false, + }, + { + resource: 'some-ns', + privilege: 'mock-saved_object:qux/open_point_in_time', + authorized: false, + }, + { + resource: 'another-ns', + privilege: 'mock-saved_object:foo/open_point_in_time', + authorized: true, + }, + { + resource: 'another-ns', + privilege: 'mock-saved_object:bar/open_point_in_time', + authorized: false, + }, + { + resource: 'another-ns', + privilege: 'mock-saved_object:baz/open_point_in_time', + authorized: true, + }, + { + resource: 'another-ns', + privilege: 'mock-saved_object:qux/open_point_in_time', + authorized: false, + }, + { + resource: 'forbidden-ns', + privilege: 'mock-saved_object:foo/open_point_in_time', + authorized: false, + }, + { + resource: 'forbidden-ns', + privilege: 'mock-saved_object:bar/open_point_in_time', + authorized: false, + }, + { + resource: 'forbidden-ns', + privilege: 'mock-saved_object:baz/open_point_in_time', + authorized: false, + }, + { + resource: 'forbidden-ns', + privilege: 'mock-saved_object:qux/open_point_in_time', + authorized: false, + }, + ], + }, + }); + + await client.openPointInTimeForType(['foo', 'bar', 'baz', 'qux'], { + namespaces: ['some-ns', 'another-ns', 'forbidden-ns'], + }); + + expect(clientOpts.baseClient.openPointInTimeForType).toHaveBeenCalledWith( + ['foo', 'bar', 'baz'], + { + namespaces: ['some-ns', 'another-ns', 'forbidden-ns'], + } + ); + }); }); describe('#closePointInTime', () => { diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts index a3bd215211983..6f2b8d28a5601 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts @@ -29,7 +29,7 @@ import type { SavedObjectsUpdateOptions, } from 'src/core/server'; -import { SavedObjectsUtils } from '../../../../../src/core/server'; +import { SavedObjectsErrorHelpers, SavedObjectsUtils } from '../../../../../src/core/server'; import { ALL_SPACES_ID, UNKNOWN_SPACE } from '../../common/constants'; import type { AuditLogger, SecurityAuditLogger } from '../audit'; import { SavedObjectAction, savedObjectEvent } from '../audit'; @@ -75,10 +75,12 @@ interface LegacyEnsureAuthorizedResult { status: 'fully_authorized' | 'partially_authorized' | 'unauthorized'; typeMap: Map; } + interface LegacyEnsureAuthorizedTypeResult { authorizedSpaces: string[]; isGloballyAuthorized?: boolean; } + export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContract { private readonly actions: Actions; private readonly legacyAuditLogger: PublicMethodsOf; @@ -236,11 +238,6 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra `_find across namespaces is not permitted when the Spaces plugin is disabled.` ); } - if (options.pit && Array.isArray(options.namespaces) && options.namespaces.length > 1) { - throw this.errors.createBadRequestError( - '_find across namespaces is not permitted when using the `pit` option.' - ); - } const args = { options }; const { status, typeMap } = await this.legacyEnsureAuthorized( @@ -508,22 +505,27 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra type: string | string[], options: SavedObjectsOpenPointInTimeOptions ) { - try { - const args = { type, options }; - await this.legacyEnsureAuthorized(type, 'open_point_in_time', options?.namespace, { + const args = { type, options }; + const { status, typeMap } = await this.legacyEnsureAuthorized( + type, + 'open_point_in_time', + options?.namespaces, + { args, // Partial authorization is acceptable in this case because this method is only designed // to be used with `find`, which already allows for partial authorization. requireFullAuthorization: false, - }); - } catch (error) { + } + ); + + if (status === 'unauthorized') { this.auditLogger.log( savedObjectEvent({ action: SavedObjectAction.OPEN_POINT_IN_TIME, - error, + error: new Error(status), }) ); - throw error; + throw SavedObjectsErrorHelpers.decorateForbiddenError(new Error(status)); } this.auditLogger.log( @@ -533,7 +535,8 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra }) ); - return await this.baseClient.openPointInTimeForType(type, options); + const allowedTypes = [...typeMap.keys()]; // only allow the user to open a PIT against indices for type(s) they are authorized to access + return await this.baseClient.openPointInTimeForType(allowedTypes, options); } public async closePointInTime(id: string, options?: SavedObjectsClosePointInTimeOptions) { diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts index 56bfe71b581ed..b94113436d7ad 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts @@ -533,27 +533,94 @@ const ERROR_NAMESPACE_SPECIFIED = 'Spaces currently determines the namespaces'; }); describe('#openPointInTimeForType', () => { - test(`throws error if options.namespace is specified`, async () => { - const { client } = createSpacesSavedObjectsClient(); + test(`throws error if if user is unauthorized in this space`, async () => { + const { client, baseClient, spacesService } = createSpacesSavedObjectsClient(); + const spacesClient = spacesClientMock.create(); + spacesClient.getAll.mockResolvedValue([]); + spacesService.createSpacesClient.mockReturnValue(spacesClient); - await expect(client.openPointInTimeForType('foo', { namespace: 'bar' })).rejects.toThrow( - ERROR_NAMESPACE_SPECIFIED + await expect( + client.openPointInTimeForType('foo', { namespaces: ['bar'] }) + ).rejects.toThrowError('Bad Request'); + + expect(baseClient.openPointInTimeForType).not.toHaveBeenCalled(); + }); + + test(`throws error if if user is unauthorized in any space`, async () => { + const { client, baseClient, spacesService } = createSpacesSavedObjectsClient(); + const spacesClient = spacesClientMock.create(); + spacesClient.getAll.mockRejectedValue(Boom.unauthorized()); + spacesService.createSpacesClient.mockReturnValue(spacesClient); + + await expect( + client.openPointInTimeForType('foo', { namespaces: ['bar'] }) + ).rejects.toThrowError('Bad Request'); + + expect(baseClient.openPointInTimeForType).not.toHaveBeenCalled(); + }); + + test(`filters options.namespaces based on authorization`, async () => { + const { client, baseClient, spacesService } = createSpacesSavedObjectsClient(); + const expectedReturnValue = { id: 'abc123' }; + baseClient.openPointInTimeForType.mockReturnValue(Promise.resolve(expectedReturnValue)); + + const spacesClient = spacesService.createSpacesClient( + null as any + ) as jest.Mocked; + spacesClient.getAll.mockImplementation(() => + Promise.resolve([ + { id: 'ns-1', name: '', disabledFeatures: [] }, + { id: 'ns-2', name: '', disabledFeatures: [] }, + ]) ); + + const options = Object.freeze({ namespaces: ['ns-1', 'ns-3'] }); + const actualReturnValue = await client.openPointInTimeForType(['foo', 'bar'], options); + + expect(actualReturnValue).toBe(expectedReturnValue); + expect(baseClient.openPointInTimeForType).toHaveBeenCalledWith(['foo', 'bar'], { + namespaces: ['ns-1'], + }); + expect(spacesClient.getAll).toHaveBeenCalledWith({ purpose: 'findSavedObjects' }); }); - test(`supplements options with the current namespace`, async () => { + test(`translates options.namespaces: ['*']`, async () => { + const { client, baseClient, spacesService } = createSpacesSavedObjectsClient(); + const expectedReturnValue = { id: 'abc123' }; + baseClient.openPointInTimeForType.mockReturnValue(Promise.resolve(expectedReturnValue)); + + const spacesClient = spacesService.createSpacesClient( + null as any + ) as jest.Mocked; + spacesClient.getAll.mockImplementation(() => + Promise.resolve([ + { id: 'ns-1', name: '', disabledFeatures: [] }, + { id: 'ns-2', name: '', disabledFeatures: [] }, + ]) + ); + + const options = Object.freeze({ namespaces: ['*'] }); + const actualReturnValue = await client.openPointInTimeForType(['foo', 'bar'], options); + + expect(actualReturnValue).toBe(expectedReturnValue); + expect(baseClient.openPointInTimeForType).toHaveBeenCalledWith(['foo', 'bar'], { + namespaces: ['ns-1', 'ns-2'], + }); + expect(spacesClient.getAll).toHaveBeenCalledWith({ purpose: 'findSavedObjects' }); + }); + + test(`supplements options with the current namespace if unspecified`, async () => { const { client, baseClient } = createSpacesSavedObjectsClient(); const expectedReturnValue = { id: 'abc123' }; baseClient.openPointInTimeForType.mockReturnValue(Promise.resolve(expectedReturnValue)); - const options = Object.freeze({ foo: 'bar' }); - // @ts-expect-error + const options = Object.freeze({ keepAlive: '2m' }); const actualReturnValue = await client.openPointInTimeForType('foo', options); expect(actualReturnValue).toBe(expectedReturnValue); expect(baseClient.openPointInTimeForType).toHaveBeenCalledWith('foo', { - foo: 'bar', - namespace: currentSpace.expectedNamespace, + keepAlive: '2m', + namespaces: [currentSpace.expectedNamespace ?? DEFAULT_SPACE_ID], }); }); }); diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts index e344aa8cecf07..9c51f22e280d8 100644 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts +++ b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts @@ -30,7 +30,7 @@ import type { SavedObjectsUpdateOptions, } from 'src/core/server'; -import { SavedObjectsUtils } from '../../../../../src/core/server'; +import { SavedObjectsErrorHelpers, SavedObjectsUtils } from '../../../../../src/core/server'; import { ALL_SPACES_ID } from '../../common/constants'; import { spaceIdToNamespace } from '../lib/utils/namespace'; import type { ISpacesClient } from '../spaces_client'; @@ -175,32 +175,19 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ public async find(options: SavedObjectsFindOptions) { - throwErrorIfNamespaceSpecified(options); - - let namespaces = options.namespaces; - if (namespaces) { - try { - const availableSpaces = await this.spacesClient.getAll({ purpose: 'findSavedObjects' }); - if (namespaces.includes(ALL_SPACES_ID)) { - namespaces = availableSpaces.map((space) => space.id); - } else { - namespaces = namespaces.filter((namespace) => - availableSpaces.some((space) => space.id === namespace) - ); - } - if (namespaces.length === 0) { - // return empty response, since the user is unauthorized in this space (or these spaces), but we don't return forbidden errors for `find` operations - return SavedObjectsUtils.createEmptyFindResponse(options); - } - } catch (err) { - if (Boom.isBoom(err) && err.output.payload.statusCode === 403) { - // return empty response, since the user is unauthorized in any space, but we don't return forbidden errors for `find` operations - return SavedObjectsUtils.createEmptyFindResponse(options); - } - throw err; + let namespaces: string[]; + try { + namespaces = await this.getSearchableSpaces(options.namespaces); + } catch (err) { + if (Boom.isBoom(err) && err.output.payload.statusCode === 403) { + // return empty response, since the user is unauthorized in any space, but we don't return forbidden errors for `find` operations + return SavedObjectsUtils.createEmptyFindResponse(options); } - } else { - namespaces = [this.spaceId]; + throw err; + } + if (namespaces.length === 0) { + // return empty response, since the user is unauthorized in this space (or these spaces), but we don't return forbidden errors for `find` operations + return SavedObjectsUtils.createEmptyFindResponse(options); } return await this.client.find({ @@ -396,10 +383,15 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { type: string | string[], options: SavedObjectsOpenPointInTimeOptions = {} ) { - throwErrorIfNamespaceSpecified(options); + const namespaces = await this.getSearchableSpaces(options.namespaces); + if (namespaces.length === 0) { + // throw bad request if no valid spaces were found. + throw SavedObjectsErrorHelpers.createBadRequestError(); + } + return await this.client.openPointInTimeForType(type, { ...options, - namespace: spaceIdToNamespace(this.spaceId), + namespaces, }); } @@ -446,4 +438,19 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { ...dependencies, }); } + + private async getSearchableSpaces(namespaces?: string[]): Promise { + if (namespaces) { + const availableSpaces = await this.spacesClient.getAll({ purpose: 'findSavedObjects' }); + if (namespaces.includes(ALL_SPACES_ID)) { + return availableSpaces.map((space) => space.id); + } else { + return namespaces.filter((namespace) => + availableSpaces.some((space) => space.id === namespace) + ); + } + } else { + return [this.spaceId]; + } + } } diff --git a/x-pack/test/saved_object_api_integration/common/suites/export.ts b/x-pack/test/saved_object_api_integration/common/suites/export.ts index ea2f321458c22..77eb93a4107a5 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/export.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/export.ts @@ -155,8 +155,16 @@ export function exportTestSuiteFactory(esArchiver: any, supertest: SuperTest Date: Mon, 23 Aug 2021 15:21:50 +0200 Subject: [PATCH 08/27] [Saved Search Embeddable] Do not set source field when reading fields from source (#109069) (#109611) * [Saved Search Embeddable] Do not set source if reading fields from source enabled * Extract functionality to a helper function and added unit tests * Fix unit test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../helpers/update_search_source.test.ts | 33 +++++++++++++++++++ .../helpers/update_search_source.ts | 33 +++++++++++++++++++ .../embeddable/saved_search_embeddable.tsx | 31 +++++++---------- 3 files changed, 77 insertions(+), 20 deletions(-) create mode 100644 src/plugins/discover/public/application/embeddable/helpers/update_search_source.test.ts create mode 100644 src/plugins/discover/public/application/embeddable/helpers/update_search_source.ts diff --git a/src/plugins/discover/public/application/embeddable/helpers/update_search_source.test.ts b/src/plugins/discover/public/application/embeddable/helpers/update_search_source.test.ts new file mode 100644 index 0000000000000..f3edc523f4464 --- /dev/null +++ b/src/plugins/discover/public/application/embeddable/helpers/update_search_source.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { createSearchSourceMock } from '../../../../../data/common/search/search_source/mocks'; +import { updateSearchSource } from './update_search_source'; +import { indexPatternMock } from '../../../__mocks__/index_pattern'; +import { SortOrder } from '../../../saved_searches/types'; + +describe('updateSearchSource', () => { + const defaults = { + sampleSize: 50, + defaultSort: 'asc', + }; + + it('updates a given search source', async () => { + const searchSource = createSearchSourceMock({}); + updateSearchSource(searchSource, indexPatternMock, [] as SortOrder[], false, defaults); + expect(searchSource.getField('fields')).toBe(undefined); + // does not explicitly request fieldsFromSource when not using fields API + expect(searchSource.getField('fieldsFromSource')).toBe(undefined); + }); + + it('updates a given search source with the usage of the new fields api', async () => { + const searchSource = createSearchSourceMock({}); + updateSearchSource(searchSource, indexPatternMock, [] as SortOrder[], true, defaults); + expect(searchSource.getField('fields')).toEqual([{ field: '*', include_unmapped: 'true' }]); + expect(searchSource.getField('fieldsFromSource')).toBe(undefined); + }); +}); diff --git a/src/plugins/discover/public/application/embeddable/helpers/update_search_source.ts b/src/plugins/discover/public/application/embeddable/helpers/update_search_source.ts new file mode 100644 index 0000000000000..1d6c29d65ca85 --- /dev/null +++ b/src/plugins/discover/public/application/embeddable/helpers/update_search_source.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { IndexPattern, ISearchSource } from '../../../../../data/common'; +import { getSortForSearchSource } from '../../apps/main/components/doc_table'; +import { SortPairArr } from '../../apps/main/components/doc_table/lib/get_sort'; + +export const updateSearchSource = ( + searchSource: ISearchSource, + indexPattern: IndexPattern | undefined, + sort: (SortPairArr[] & string[][]) | undefined, + useNewFieldsApi: boolean, + defaults: { + sampleSize: number; + defaultSort: string; + } +) => { + const { sampleSize, defaultSort } = defaults; + searchSource.setField('size', sampleSize); + searchSource.setField('sort', getSortForSearchSource(sort, indexPattern, defaultSort)); + if (useNewFieldsApi) { + searchSource.removeField('fieldsFromSource'); + const fields: Record = { field: '*', include_unmapped: 'true' }; + searchSource.setField('fields', [fields]); + } else { + searchSource.removeField('fields'); + } +}; diff --git a/src/plugins/discover/public/application/embeddable/saved_search_embeddable.tsx b/src/plugins/discover/public/application/embeddable/saved_search_embeddable.tsx index 1981f0228d2c7..362f5b9276c65 100644 --- a/src/plugins/discover/public/application/embeddable/saved_search_embeddable.tsx +++ b/src/plugins/discover/public/application/embeddable/saved_search_embeddable.tsx @@ -42,8 +42,9 @@ import { handleSourceColumnState } from '../angular/helpers'; import { DiscoverGridProps } from '../components/discover_grid/discover_grid'; import { DiscoverGridSettings } from '../components/discover_grid/types'; import { DocTableProps } from '../apps/main/components/doc_table/doc_table_wrapper'; -import { getDefaultSort, getSortForSearchSource } from '../apps/main/components/doc_table'; +import { getDefaultSort } from '../apps/main/components/doc_table'; import { SortOrder } from '../apps/main/components/doc_table/components/table_header/helpers'; +import { updateSearchSource } from './helpers/update_search_source'; export type SearchProps = Partial & Partial & { @@ -143,26 +144,16 @@ export class SavedSearchEmbeddable if (this.abortController) this.abortController.abort(); this.abortController = new AbortController(); - searchSource.setField('size', this.services.uiSettings.get(SAMPLE_SIZE_SETTING)); - searchSource.setField( - 'sort', - getSortForSearchSource( - this.searchProps!.sort, - this.searchProps!.indexPattern, - this.services.uiSettings.get(SORT_DEFAULT_ORDER_SETTING) - ) - ); - if (useNewFieldsApi) { - searchSource.removeField('fieldsFromSource'); - const fields: Record = { field: '*', include_unmapped: 'true' }; - searchSource.setField('fields', [fields]); - } else { - searchSource.removeField('fields'); - if (this.searchProps.indexPattern) { - const fieldNames = this.searchProps.indexPattern.fields.map((field) => field.name); - searchSource.setField('fieldsFromSource', fieldNames); + updateSearchSource( + searchSource, + this.searchProps!.indexPattern, + this.searchProps!.sort, + useNewFieldsApi, + { + sampleSize: this.services.uiSettings.get(SAMPLE_SIZE_SETTING), + defaultSort: this.services.uiSettings.get(SORT_DEFAULT_ORDER_SETTING), } - } + ); // Log request to inspector this.inspectorAdapters.requests!.reset(); From b3120d9d3ca7bebc314092d5e393a5d19959a3eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Mon, 23 Aug 2021 15:24:05 +0200 Subject: [PATCH 09/27] [ILM] Fixed _meta field failing server validation (#109295) (#109613) * [ILM] Fixed the bug with built-in policies not saving because of the _meta field * [ILM] Added PR review suggestions Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../features/request_flyout.test.ts | 40 ++++++++++++++++++ .../common/types/policies.ts | 1 + .../components/policy_json_flyout.tsx | 41 ++++++++++--------- .../api/policies/register_create_route.ts | 17 ++++---- .../index_lifecycle_management/policies.js | 32 +++++++++++++++ 5 files changed, 103 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/request_flyout.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/request_flyout.test.ts index 86bf36984b9fd..e49a1c5c94f90 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/request_flyout.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/request_flyout.test.ts @@ -136,4 +136,44 @@ describe(' request flyout', () => { expect(json).toBe(expected); }); + + test('renders _meta field', async () => { + const defaultPolicy = getDefaultHotPhasePolicy(); + const policyWithMetaField = { + ...defaultPolicy, + policy: { + ...defaultPolicy.policy, + _meta: { + description: 'test meta description', + someObject: { + test: 'test', + }, + }, + }, + }; + httpRequestsMockHelpers.setLoadPolicies([policyWithMetaField]); + + await act(async () => { + testBed = await setupRequestFlyoutTestBed(); + }); + + const { component, actions } = testBed; + component.update(); + + await actions.openRequestFlyout(); + + const json = actions.getRequestJson(); + const expected = `PUT _ilm/policy/${policyWithMetaField.name}\n${JSON.stringify( + { + policy: { + phases: { ...policyWithMetaField.policy.phases }, + _meta: { ...policyWithMetaField.policy._meta }, + }, + }, + null, + 2 + )}`; + + expect(json).toBe(expected); + }); }); diff --git a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts index 76b38eacba2d1..3a338c80fa56c 100644 --- a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts +++ b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts @@ -18,6 +18,7 @@ export type PhaseExceptDelete = keyof Omit; export interface SerializedPolicy { name: string; phases: Phases; + _meta?: Record; } export interface Phases { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx index f510090323e1f..ae7b1ebaffc02 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx @@ -29,6 +29,7 @@ import { useFormContext, useFormData } from '../../../../shared_imports'; import { i18nTexts } from '../i18n_texts'; import { FormInternal } from '../types'; +type PolicyJson = Omit; interface Props { close: () => void; policyName: string; @@ -37,48 +38,50 @@ interface Props { /** * Ensure that the JSON we get from the from has phases in the correct order. */ -const prettifyFormJson = (policy: SerializedPolicy): SerializedPolicy => ({ - ...policy, - phases: { - hot: policy.phases.hot, - warm: policy.phases.warm, - cold: policy.phases.cold, - frozen: policy.phases.frozen, - delete: policy.phases.delete, - }, -}); +const prettifyFormJson = (policy: SerializedPolicy): PolicyJson => { + return { + phases: { + hot: policy.phases.hot, + warm: policy.phases.warm, + cold: policy.phases.cold, + frozen: policy.phases.frozen, + delete: policy.phases.delete, + }, + _meta: policy._meta, + }; +}; export const PolicyJsonFlyout: React.FunctionComponent = ({ policyName, close }) => { /** * policy === undefined: we are checking validity * policy === null: we have determined the policy is invalid - * policy === {@link SerializedPolicy} we have determined the policy is valid + * policy === {@link PolicyJson} we have determined the policy is valid */ - const [policy, setPolicy] = useState(undefined); + const [policyJson, setPolicyJson] = useState(undefined); const { validate: validateForm, getErrors } = useFormContext(); const [, getFormData] = useFormData(); const updatePolicy = useCallback(async () => { - setPolicy(undefined); + setPolicyJson(undefined); const isFormValid = await validateForm(); const errorMessages = getErrors(); const isOnlyMissingPolicyName = errorMessages.length === 1 && errorMessages[0] === i18nTexts.editPolicy.errors.policyNameRequiredMessage; if (isFormValid || isOnlyMissingPolicyName) { - setPolicy(prettifyFormJson(getFormData())); + setPolicyJson(prettifyFormJson(getFormData())); } else { - setPolicy(null); + setPolicyJson(null); } - }, [setPolicy, getFormData, validateForm, getErrors]); + }, [setPolicyJson, getFormData, validateForm, getErrors]); useEffect(() => { updatePolicy(); }, [updatePolicy]); let content: React.ReactNode; - switch (policy) { + switch (policyJson) { case undefined: content = ; break; @@ -100,12 +103,10 @@ export const PolicyJsonFlyout: React.FunctionComponent = ({ policyName, c ); break; default: - const { phases } = policy; - const json = JSON.stringify( { policy: { - phases, + ...policyJson, }, }, null, diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts index 7a4795f8e370b..bc27a3b909c85 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts @@ -11,12 +11,12 @@ import { ElasticsearchClient } from 'kibana/server'; import { RouteDependencies } from '../../../types'; import { addBasePath } from '../../../services'; -async function createPolicy(client: ElasticsearchClient, name: string, phases: any): Promise { - const body = { - policy: { - phases, - }, - }; +async function createPolicy( + client: ElasticsearchClient, + name: string, + policy: Omit +): Promise { + const body = { policy }; const options = { ignore: [404], }; @@ -40,6 +40,7 @@ const bodySchema = schema.object({ frozen: schema.maybe(schema.any()), delete: schema.maybe(schema.any()), }), + _meta: schema.maybe(schema.any()), }); export function registerCreateRoute({ @@ -51,10 +52,10 @@ export function registerCreateRoute({ { path: addBasePath('/policies'), validate: { body: bodySchema } }, license.guardApiRoute(async (context, request, response) => { const body = request.body as typeof bodySchema.type; - const { name, phases } = body; + const { name, ...rest } = body; try { - await createPolicy(context.core.elasticsearch.client.asCurrentUser, name, phases); + await createPolicy(context.core.elasticsearch.client.asCurrentUser, name, rest); return response.ok(); } catch (error) { return handleEsError({ error, response }); diff --git a/x-pack/test/api_integration/apis/management/index_lifecycle_management/policies.js b/x-pack/test/api_integration/apis/management/index_lifecycle_management/policies.js index 1f5f28744dd98..8e29604a0bf62 100644 --- a/x-pack/test/api_integration/apis/management/index_lifecycle_management/policies.js +++ b/x-pack/test/api_integration/apis/management/index_lifecycle_management/policies.js @@ -119,6 +119,38 @@ export default function ({ getService }) { }); }); + describe('edit', () => { + it('keeps _meta field intact', async () => { + const policyName = 'edit-meta-test-policy'; + const policy = { + ...getPolicyPayload(policyName), + _meta: { description: 'test policy with _meta field' }, + }; + + // Update the policy (uses the same route as create) + await createPolicy(policy).expect(200); + + // only update warm phase timing, not deleting or changing _meta field + const editedPolicy = { + ...policy, + phases: { + ...policy.phases, + warm: { + ...policy.phases.warm, + min_age: '2d', + }, + }, + }; + + await createPolicy(editedPolicy).expect(200); + + const { body } = await loadPolicies(); + const loadedPolicy = body.find((p) => p.name === policyName); + // Make sure the edited policy still has _meta field + expect(loadedPolicy.policy._meta).to.eql(editedPolicy._meta); + }); + }); + describe('delete', () => { it('should delete the policy created', async () => { const policy = getPolicyPayload('delete-test-policy'); From 4a14a94942e798b8753639fb82d9cb06cbb0d8f4 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Mon, 23 Aug 2021 09:25:40 -0400 Subject: [PATCH 10/27] [Upgrade Assistant] Cleanup scss (#109524) --- .../public/application/_index.scss | 1 - .../public/application/components/_index.scss | 2 -- .../components/es_deprecations/_index.scss | 1 - .../deprecation_types/_index.scss | 1 - .../deprecation_types/reindex/_index.scss | 1 - .../reindex/flyout/_index.scss | 1 - .../reindex/flyout/step_progress.tsx | 2 ++ .../components/overview/_index.scss | 2 -- ...stats_panel.scss => _fix_issues_step.scss} | 0 .../overview/fix_issues_step/_index.scss | 2 -- .../fix_issues_step/fix_issues_step.tsx | 2 ++ .../no_deprecations/_no_deprecations.scss | 3 --- .../no_deprecations/no_deprecations.tsx | 25 +++++++++++-------- .../overview/fix_logs_step/_index.scss | 1 - .../deprecation_logging_toggle.tsx | 2 ++ .../upgrade_assistant/public/index.scss | 1 - .../plugins/upgrade_assistant/public/index.ts | 1 - 17 files changed, 20 insertions(+), 28 deletions(-) delete mode 100644 x-pack/plugins/upgrade_assistant/public/application/_index.scss delete mode 100644 x-pack/plugins/upgrade_assistant/public/application/components/_index.scss delete mode 100644 x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/_index.scss delete mode 100644 x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/_index.scss delete mode 100644 x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/_index.scss delete mode 100644 x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/_index.scss delete mode 100644 x-pack/plugins/upgrade_assistant/public/application/components/overview/_index.scss rename x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/{_stats_panel.scss => _fix_issues_step.scss} (100%) delete mode 100644 x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/_index.scss delete mode 100644 x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/no_deprecations/_no_deprecations.scss delete mode 100644 x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/_index.scss delete mode 100644 x-pack/plugins/upgrade_assistant/public/index.scss diff --git a/x-pack/plugins/upgrade_assistant/public/application/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/_index.scss deleted file mode 100644 index 841415620d691..0000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'components/index'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/_index.scss deleted file mode 100644 index 8f900ca8dc055..0000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/components/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import 'es_deprecations/index'; -@import 'overview/index'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/_index.scss deleted file mode 100644 index 4865e977f5261..0000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'deprecation_types/index'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/_index.scss deleted file mode 100644 index c3e842941a250..0000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'reindex/index'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/_index.scss deleted file mode 100644 index 4cd55614ab4e6..0000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'flyout/index'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/_index.scss deleted file mode 100644 index 1c9fd599b13a8..0000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'step_progress'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/step_progress.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/step_progress.tsx index 0973f721a5372..d5947426aee3d 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/step_progress.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/step_progress.tsx @@ -10,6 +10,8 @@ import React, { Fragment, ReactNode } from 'react'; import { EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; +import './_step_progress.scss'; + type STATUS = 'incomplete' | 'inProgress' | 'complete' | 'failed' | 'paused' | 'cancelled'; const StepStatus: React.FunctionComponent<{ status: STATUS; idx: number }> = ({ status, idx }) => { diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/overview/_index.scss deleted file mode 100644 index 3febfe77b3cf7..0000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import 'fix_issues_step/index'; -@import 'fix_logs_step/index'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/_stats_panel.scss b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/_fix_issues_step.scss similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/_stats_panel.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/_fix_issues_step.scss diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/_index.scss deleted file mode 100644 index 7eea518d5698e..0000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import 'stats_panel'; -@import 'no_deprecations/no_deprecations'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/fix_issues_step.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/fix_issues_step.tsx index 12f26a1ea3430..f235075350e66 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/fix_issues_step.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/fix_issues_step.tsx @@ -14,6 +14,8 @@ import type { EuiStepProps } from '@elastic/eui/src/components/steps/step'; import { ESDeprecationStats } from './es_stats'; import { KibanaDeprecationStats } from './kibana_stats'; +import './_fix_issues_step.scss'; + const i18nTexts = { reviewStepTitle: i18n.translate('xpack.upgradeAssistant.overview.fixIssuesStepTitle', { defaultMessage: 'Review deprecated settings and resolve issues', diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/no_deprecations/_no_deprecations.scss b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/no_deprecations/_no_deprecations.scss deleted file mode 100644 index 0697efbd6ee37..0000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/no_deprecations/_no_deprecations.scss +++ /dev/null @@ -1,3 +0,0 @@ -.upgRenderSuccessMessage { - margin-top: $euiSizeL; -} diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/no_deprecations/no_deprecations.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/no_deprecations/no_deprecations.tsx index 06fea677aa0a5..2f034d461a24f 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/no_deprecations/no_deprecations.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/no_deprecations/no_deprecations.tsx @@ -7,7 +7,7 @@ import React, { FunctionComponent } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiText, EuiIcon } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiIcon, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; const i18nTexts = { @@ -21,15 +21,18 @@ const i18nTexts = { export const NoDeprecations: FunctionComponent = () => { return ( - - - - - - - {i18nTexts.noDeprecationsText} - - - + <> + + + + + + + + {i18nTexts.noDeprecationsText} + + + + ); }; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/_index.scss deleted file mode 100644 index 2299c08a4ac31..0000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'deprecation_logging_toggle/deprecation_logging_toggle'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecation_logging_toggle/deprecation_logging_toggle.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecation_logging_toggle/deprecation_logging_toggle.tsx index 42b9f073a52f1..6de099fe05ef5 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecation_logging_toggle/deprecation_logging_toggle.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecation_logging_toggle/deprecation_logging_toggle.tsx @@ -23,6 +23,8 @@ import { i18n } from '@kbn/i18n'; import { ResponseError } from '../../../../lib/api'; import { DeprecationLoggingPreviewProps } from '../../../types'; +import './_deprecation_logging_toggle.scss'; + const i18nTexts = { fetchErrorMessage: i18n.translate( 'xpack.upgradeAssistant.overview.deprecationLogs.fetchErrorMessage', diff --git a/x-pack/plugins/upgrade_assistant/public/index.scss b/x-pack/plugins/upgrade_assistant/public/index.scss deleted file mode 100644 index 9bd47b6473372..0000000000000 --- a/x-pack/plugins/upgrade_assistant/public/index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './application/index'; diff --git a/x-pack/plugins/upgrade_assistant/public/index.ts b/x-pack/plugins/upgrade_assistant/public/index.ts index a4091bcb3e1ab..e338b9c044f68 100644 --- a/x-pack/plugins/upgrade_assistant/public/index.ts +++ b/x-pack/plugins/upgrade_assistant/public/index.ts @@ -5,7 +5,6 @@ * 2.0. */ -import './index.scss'; import { PluginInitializerContext } from 'src/core/public'; import { UpgradeAssistantUIPlugin } from './plugin'; From 418e449f3060f6274413b340d5118c4c28f599a3 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 23 Aug 2021 09:52:41 -0400 Subject: [PATCH 11/27] [ML] Fix UI inconsistencies in APM Failed transaction correlations (#109187) (#109626) - Show the same empty state in the correlations table - Add "Correlations" title above the table - Add EuiSpacer between the sections before and after progress - Update the copy within the beta badge title=Failed transaction correlations description=Failed transaction correlations is not GA... - Remove s size from the beta badge - Move the help popover to the top of the panel (similar to the Latency correlations tab) - Move the Cancel/Refresh option to the right of the progress bar (similar to the Latency correlations tab) - When the correlation job is running the correlations tab should show a loading state similar to the latency correlations table - Indicate in the table headers Score is sorted by default - Add sortability to both Latency and failed transactions correlations table - Refactor to prevent duplicate code/components like Log, progress bar - Fix alignments of the tab content header (previously navigating from one Trace samples tab to Latency correlation tabs will cause a minor jump in the header, or the titles within the same tab were not the same size ) - Remove the event.outcome as a field candidate (because event.outcome: failure would always be significant in this case) - Shrink the column width for impact (previously it was at 116px) - Added badge for High, medium, low [APM] Correlations: Show impact levels (high, medium, low) as colored badges indicating their severity - Fix license prompt text - Functional tests for the new tab - Make the p value & error rate columns visible only when esInspect mode is enabled Co-authored-by: Quynh Nguyen <43350163+qn895@users.noreply.github.com> --- .../failure_correlations/types.ts | 3 + .../common/utils/formatters/datetime.test.ts | 28 ++ .../app/correlations/correlations_log.tsx | 38 ++ .../app/correlations/correlations_table.tsx | 26 +- .../cross_cluster_search_warning.tsx | 33 ++ .../app/correlations/empty_state_prompt.tsx | 49 ++ .../failed_transactions_correlations.tsx | 426 ++++++++++-------- ...transactions_correlations_help_popover.tsx | 18 +- .../app/correlations/latency_correlations.tsx | 239 ++++------ .../app/correlations/progress_controls.tsx | 77 ++++ ...nsactions_correlation_impact_label.test.ts | 42 +- ...d_transactions_correlation_impact_label.ts | 17 +- .../distribution/index.tsx | 5 +- .../failed_transactions_correlations_tab.tsx | 2 +- ...ailed_transactions_correlations_fetcher.ts | 6 +- .../async_search_service.ts | 10 +- .../queries/query_failure_correlation.ts | 19 +- .../translations/translations/ja-JP.json | 4 - .../translations/translations/zh-CN.json | 4 - .../common/utils/parse_b_fetch.ts | 15 + .../tests/correlations/failed_transactions.ts | 238 ++++++++++ .../{latency_ml.ts => latency.ts} | 10 +- .../test/apm_api_integration/tests/index.ts | 9 +- .../failed_transaction_correlations.ts | 155 +++++++ .../functional/apps/apm/correlations/index.ts | 3 +- .../apm/correlations/latency_correlations.ts | 6 +- 26 files changed, 1074 insertions(+), 408 deletions(-) create mode 100644 x-pack/plugins/apm/public/components/app/correlations/correlations_log.tsx create mode 100644 x-pack/plugins/apm/public/components/app/correlations/cross_cluster_search_warning.tsx create mode 100644 x-pack/plugins/apm/public/components/app/correlations/empty_state_prompt.tsx create mode 100644 x-pack/plugins/apm/public/components/app/correlations/progress_controls.tsx create mode 100644 x-pack/test/apm_api_integration/common/utils/parse_b_fetch.ts create mode 100644 x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts rename x-pack/test/apm_api_integration/tests/correlations/{latency_ml.ts => latency.ts} (97%) create mode 100644 x-pack/test/functional/apps/apm/correlations/failed_transaction_correlations.ts diff --git a/x-pack/plugins/apm/common/search_strategies/failure_correlations/types.ts b/x-pack/plugins/apm/common/search_strategies/failure_correlations/types.ts index 08e05d46ba013..2b0d2b5642e0c 100644 --- a/x-pack/plugins/apm/common/search_strategies/failure_correlations/types.ts +++ b/x-pack/plugins/apm/common/search_strategies/failure_correlations/types.ts @@ -15,6 +15,9 @@ export interface FailedTransactionsCorrelationValue { pValue: number | null; fieldName: string; fieldValue: string; + normalizedScore: number; + failurePercentage: number; + successPercentage: number; } export type FailureCorrelationImpactThreshold = typeof FAILED_TRANSACTIONS_IMPACT_THRESHOLD[keyof typeof FAILED_TRANSACTIONS_IMPACT_THRESHOLD]; diff --git a/x-pack/plugins/apm/common/utils/formatters/datetime.test.ts b/x-pack/plugins/apm/common/utils/formatters/datetime.test.ts index 9efb7184f3927..54e74330c0604 100644 --- a/x-pack/plugins/apm/common/utils/formatters/datetime.test.ts +++ b/x-pack/plugins/apm/common/utils/formatters/datetime.test.ts @@ -165,6 +165,34 @@ describe('date time formatters', () => { 'Dec 1, 2019, 13:00:00.000 (UTC+1)' ); }); + + it('milliseconds', () => { + moment.tz.setDefault('Europe/Copenhagen'); + expect(asAbsoluteDateTime(1559390400000, 'milliseconds')).toBe( + 'Jun 1, 2019, 14:00:00.000 (UTC+2)' + ); + }); + + it('seconds', () => { + moment.tz.setDefault('Europe/Copenhagen'); + expect(asAbsoluteDateTime(1559390400000, 'seconds')).toBe( + 'Jun 1, 2019, 14:00:00 (UTC+2)' + ); + }); + + it('minutes', () => { + moment.tz.setDefault('Europe/Copenhagen'); + expect(asAbsoluteDateTime(1559390400000, 'minutes')).toBe( + 'Jun 1, 2019, 14:00 (UTC+2)' + ); + }); + + it('hours', () => { + moment.tz.setDefault('Europe/Copenhagen'); + expect(asAbsoluteDateTime(1559390400000, 'hours')).toBe( + 'Jun 1, 2019, 14 (UTC+2)' + ); + }); }); describe('getDateDifference', () => { it('milliseconds', () => { diff --git a/x-pack/plugins/apm/public/components/app/correlations/correlations_log.tsx b/x-pack/plugins/apm/public/components/app/correlations/correlations_log.tsx new file mode 100644 index 0000000000000..2115918a71415 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/correlations/correlations_log.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiAccordion, EuiCode, EuiPanel } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { asAbsoluteDateTime } from '../../../../common/utils/formatters'; + +interface Props { + logMessages: string[]; +} +export function CorrelationsLog({ logMessages }: Props) { + return ( + + + {logMessages.map((logMessage, i) => { + const [timestamp, message] = logMessage.split(': '); + return ( +

+ + {asAbsoluteDateTime(timestamp)} {message} + +

+ ); + })} +
+
+ ); +} diff --git a/x-pack/plugins/apm/public/components/app/correlations/correlations_table.tsx b/x-pack/plugins/apm/public/components/app/correlations/correlations_table.tsx index 28f671183ed87..f7e62b76a61c0 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/correlations_table.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/correlations_table.tsx @@ -9,10 +9,12 @@ import React, { useCallback, useMemo, useState } from 'react'; import { debounce } from 'lodash'; import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import type { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types'; +import type { Criteria } from '@elastic/eui/src/components/basic_table/basic_table'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { useUiTracker } from '../../../../../observability/public'; import { useTheme } from '../../../hooks/use_theme'; -import { CorrelationsTerm } from '../../../../common/search_strategies/failure_correlations/types'; +import type { CorrelationsTerm } from '../../../../common/search_strategies/failure_correlations/types'; const PAGINATION_SIZE_OPTIONS = [5, 10, 20, 50]; @@ -29,6 +31,8 @@ interface Props { selectedTerm?: { fieldName: string; fieldValue: string }; onFilter?: () => void; columns: Array>; + onTableChange: (c: Criteria) => void; + sorting?: EuiTableSortingType; } export function CorrelationsTable({ @@ -37,6 +41,8 @@ export function CorrelationsTable({ setSelectedSignificantTerm, columns, selectedTerm, + onTableChange, + sorting, }: Props) { const euiTheme = useTheme(); const trackApmEvent = useUiTracker({ app: 'apm' }); @@ -67,12 +73,17 @@ export function CorrelationsTable({ }; }, [pageIndex, pageSize, significantTerms]); - const onTableChange = useCallback(({ page }) => { - const { index, size } = page; + const onChange = useCallback( + (tableSettings) => { + const { index, size } = tableSettings.page; - setPageIndex(index); - setPageSize(size); - }, []); + setPageIndex(index); + setPageSize(size); + + onTableChange(tableSettings); + }, + [onTableChange] + ); return ( ({ }; }} pagination={pagination} - onChange={onTableChange} + onChange={onChange} + sorting={sorting} /> ); } diff --git a/x-pack/plugins/apm/public/components/app/correlations/cross_cluster_search_warning.tsx b/x-pack/plugins/apm/public/components/app/correlations/cross_cluster_search_warning.tsx new file mode 100644 index 0000000000000..9d5ca09ad3add --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/correlations/cross_cluster_search_warning.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiCallOut } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; + +export function CrossClusterSearchCompatibilityWarning({ + version, +}: { + version: string; +}) { + return ( + +

+ {i18n.translate('xpack.apm.correlations.ccsWarningCalloutBody', { + defaultMessage: + 'Data for the correlation analysis could not be fully retrieved. This feature is supported only for {version} and later versions.', + values: { version }, + })} +

+
+ ); +} diff --git a/x-pack/plugins/apm/public/components/app/correlations/empty_state_prompt.tsx b/x-pack/plugins/apm/public/components/app/correlations/empty_state_prompt.tsx new file mode 100644 index 0000000000000..57e57a526baff --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/correlations/empty_state_prompt.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiEmptyPrompt, EuiSpacer, EuiText } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +export function CorrelationsEmptyStatePrompt() { + return ( + <> + + +

+ {i18n.translate('xpack.apm.correlations.noCorrelationsTitle', { + defaultMessage: 'No significant correlations', + })} +

+ + } + body={ + <> + +

+ +

+

+ +

+
+ + } + /> + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx index 4fdd908b6faf6..1cc115861a594 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx @@ -5,45 +5,46 @@ * 2.0. */ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { - EuiCallOut, - EuiCode, - EuiAccordion, - EuiPanel, EuiBasicTableColumn, - EuiButton, EuiFlexGroup, EuiFlexItem, - EuiProgress, EuiSpacer, - EuiText, - EuiBadge, EuiIcon, EuiLink, EuiTitle, EuiBetaBadge, + EuiBadge, + EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; import { useHistory } from 'react-router-dom'; +import { orderBy } from 'lodash'; +import type { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types'; +import type { Direction } from '@elastic/eui/src/services/sort/sort_direction'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { CorrelationsTable } from './correlations_table'; import { enableInspectEsQueries } from '../../../../../observability/public'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { FailedTransactionsCorrelationsHelpPopover } from './failed_transactions_correlations_help_popover'; -import { FailedTransactionsCorrelationValue } from '../../../../common/search_strategies/failure_correlations/types'; import { ImpactBar } from '../../shared/ImpactBar'; import { isErrorMessage } from './utils/is_error_message'; -import { Summary } from '../../shared/Summary'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { getFailedTransactionsCorrelationImpactLabel } from './utils/get_failed_transactions_correlation_impact_label'; import { createHref, push } from '../../shared/Links/url_helpers'; import { useUiTracker } from '../../../../../observability/public'; import { useFailedTransactionsCorrelationsFetcher } from '../../../hooks/use_failed_transactions_correlations_fetcher'; -import { SearchServiceParams } from '../../../../common/search_strategies/correlations/types'; import { useApmParams } from '../../../hooks/use_apm_params'; +import { CorrelationsLog } from './correlations_log'; +import { CorrelationsEmptyStatePrompt } from './empty_state_prompt'; +import { CrossClusterSearchCompatibilityWarning } from './cross_cluster_search_warning'; +import { CorrelationsProgressControls } from './progress_controls'; +import type { SearchServiceParams } from '../../../../common/search_strategies/correlations/types'; +import type { FailedTransactionsCorrelationValue } from '../../../../common/search_strategies/failure_correlations/types'; +import { Summary } from '../../shared/Summary'; +import { asPercent } from '../../../../common/utils/formatters'; export function FailedTransactionsCorrelations({ onFilter, @@ -64,7 +65,7 @@ export function FailedTransactionsCorrelations({ const { urlParams } = useUrlParams(); const { transactionName, start, end } = urlParams; - const displayLog = uiSettings.get(enableInspectEsQueries); + const inspectEnabled = uiSettings.get(enableInspectEsQueries); const searchServicePrams: SearchServiceParams = { environment, @@ -76,7 +77,7 @@ export function FailedTransactionsCorrelations({ end, }; - const result = useFailedTransactionsCorrelationsFetcher(searchServicePrams); + const result = useFailedTransactionsCorrelationsFetcher(); const { ccsWarning, @@ -87,10 +88,20 @@ export function FailedTransactionsCorrelations({ startFetch, cancelFetch, } = result; + + const startFetchHandler = useCallback(() => { + startFetch(searchServicePrams); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [environment, serviceName, kuery, start, end]); + // start fetching on load // we want this effect to execute exactly once after the component mounts useEffect(() => { - startFetch(); + if (isRunning) { + cancelFetch(); + } + + startFetchHandler(); return () => { // cancel any running async partial request when unmounting the component @@ -98,7 +109,7 @@ export function FailedTransactionsCorrelations({ cancelFetch(); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [startFetchHandler]); const [ selectedSignificantTerm, @@ -118,10 +129,83 @@ export function FailedTransactionsCorrelations({ const failedTransactionsCorrelationsColumns: Array< EuiBasicTableColumn - > = useMemo( - () => [ + > = useMemo(() => { + const percentageColumns: Array< + EuiBasicTableColumn + > = inspectEnabled + ? [ + { + width: '100px', + field: 'failurePercentage', + name: ( + + <> + {i18n.translate( + 'xpack.apm.correlations.failedTransactions.correlationsTable.failurePercentageLabel', + { + defaultMessage: 'Failure %', + } + )} + + + + ), + render: (failurePercentage: number) => + asPercent(failurePercentage, 1), + sortable: true, + }, + { + field: 'successPercentage', + width: '100px', + name: ( + + <> + {i18n.translate( + 'xpack.apm.correlations.failedTransactions.correlationsTable.successPercentageLabel', + { + defaultMessage: 'Success %', + } + )} + + + + ), + + render: (successPercentage: number) => + asPercent(successPercentage, 1), + sortable: true, + }, + ] + : []; + return [ { - width: '116px', + width: '80px', field: 'normalizedScore', name: ( <> @@ -140,6 +224,7 @@ export function FailedTransactionsCorrelations({ ); }, + sortable: true, }, { width: '116px', @@ -154,7 +239,13 @@ export function FailedTransactionsCorrelations({ )} ), - render: getFailedTransactionsCorrelationImpactLabel, + render: (pValue: number) => { + const label = getFailedTransactionsCorrelationImpactLabel(pValue); + return label ? ( + {label.impact} + ) : null; + }, + sortable: true, }, { field: 'fieldName', @@ -162,6 +253,7 @@ export function FailedTransactionsCorrelations({ 'xpack.apm.correlations.failedTransactions.correlationsTable.fieldNameLabel', { defaultMessage: 'Field name' } ), + sortable: true, }, { field: 'key', @@ -170,7 +262,9 @@ export function FailedTransactionsCorrelations({ { defaultMessage: 'Field value' } ), render: (fieldValue: string) => String(fieldValue).slice(0, 50), + sortable: true, }, + ...percentageColumns, { width: '100px', actions: [ @@ -188,9 +282,7 @@ export function FailedTransactionsCorrelations({ onClick: (term: FailedTransactionsCorrelationValue) => { push(history, { query: { - kuery: `${term.fieldName}:"${encodeURIComponent( - term.fieldValue - )}"`, + kuery: `${term.fieldName}:"${term.fieldValue}"`, }, }); onFilter(); @@ -211,9 +303,7 @@ export function FailedTransactionsCorrelations({ onClick: (term: FailedTransactionsCorrelationValue) => { push(history, { query: { - kuery: `not ${term.fieldName}:"${encodeURIComponent( - term.fieldValue - )}"`, + kuery: `not ${term.fieldName}:"${term.fieldValue}"`, }, }); onFilter(); @@ -231,9 +321,7 @@ export function FailedTransactionsCorrelations({ @@ -243,9 +331,7 @@ export function FailedTransactionsCorrelations({ @@ -255,9 +341,8 @@ export function FailedTransactionsCorrelations({ ); }, }, - ], - [history, onFilter, trackApmEvent] - ); + ] as Array>; + }, [history, onFilter, trackApmEvent, inspectEnabled]); useEffect(() => { if (isErrorMessage(error)) { @@ -273,100 +358,124 @@ export function FailedTransactionsCorrelations({ }); } }, [error, notifications.toasts]); + + const [sortField, setSortField] = useState< + keyof FailedTransactionsCorrelationValue + >('normalizedScore'); + const [sortDirection, setSortDirection] = useState('desc'); + + const onTableChange = useCallback(({ sort }) => { + const { field: currentSortField, direction: currentSortDirection } = sort; + + setSortField(currentSortField); + setSortDirection(currentSortDirection); + }, []); + + const { sorting, correlationTerms } = useMemo(() => { + if (!Array.isArray(result.values)) { + return { correlationTerms: [], sorting: undefined }; + } + const orderedTerms = orderBy( + result.values, + // The smaller the p value the higher the impact + // So we want to sort by the normalized score here + // which goes from 0 -> 1 + sortField === 'pValue' ? 'normalizedScore' : sortField, + sortDirection + ); + return { + correlationTerms: orderedTerms, + sorting: { + sort: { + field: sortField, + direction: sortDirection, + }, + } as EuiTableSortingType, + }; + }, [result?.values, sortField, sortDirection]); + return ( - <> - - - -
- {i18n.translate( - 'xpack.apm.correlations.failedTransactions.panelTitle', +
+ + + + +
+ {i18n.translate( + 'xpack.apm.correlations.failedTransactions.panelTitle', + { + defaultMessage: 'Failed transactions', + } + )} +
+
+
+ + + - - + title={i18n.translate( + 'xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsBetaTitle', + { + defaultMessage: 'Failed transaction correlations', + } + )} + tooltipContent={i18n.translate( + 'xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsBetaDescription', + { + defaultMessage: + 'Failed transaction correlations is not GA. Please help us by reporting any bugs.', + } + )} + /> +
+ - + - + - - - {!isRunning && ( - - - - )} - {isRunning && ( - - - + + + + + {i18n.translate( + 'xpack.apm.correlations.failedTransactions.tableTitle', + { + defaultMessage: 'Correlations', + } )} - - - - - - - - - - - - - - - - - - {selectedTerm?.pValue != null ? ( + + + + + + + + {ccsWarning && ( + <> + + + + )} + + {inspectEnabled && + selectedTerm?.pValue != null && + (isRunning || correlationTerms.length > 0) ? ( <> {`p-value: ${selectedTerm.pValue.toPrecision(3)}`}, ]} /> - ) : null} - - columns={failedTransactionsCorrelationsColumns} - significantTerms={result?.values} - status={FETCH_STATUS.SUCCESS} - setSelectedSignificantTerm={setSelectedSignificantTerm} - selectedTerm={selectedTerm} - /> - {ccsWarning && ( - <> - - -

- {i18n.translate( - 'xpack.apm.correlations.failedTransactions.ccsWarningCalloutBody', - { - defaultMessage: - 'Data for the correlation analysis could not be fully retrieved. This feature is supported only for 7.15 and later versions.', - } - )} -

-
- - )} - {log.length > 0 && displayLog && ( - - - {log.map((d, i) => { - const splitItem = d.split(': '); - return ( -

- - {splitItem[0]} {splitItem[1]} - -

- ); - })} -
-
- )} - +
+ {(isRunning || correlationTerms.length > 0) && ( + + columns={failedTransactionsCorrelationsColumns} + significantTerms={correlationTerms} + status={isRunning ? FETCH_STATUS.LOADING : FETCH_STATUS.SUCCESS} + setSelectedSignificantTerm={setSelectedSignificantTerm} + selectedTerm={selectedTerm} + onTableChange={onTableChange} + sorting={sorting} + /> + )} + {correlationTerms.length < 1 && (progress === 1 || !isRunning) && ( + + )} +
+ {inspectEnabled && } +
); } diff --git a/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations_help_popover.tsx b/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations_help_popover.tsx index bebc889cc4ed9..e66101d619224 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations_help_popover.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations_help_popover.tsx @@ -18,6 +18,7 @@ export function FailedTransactionsCorrelationsHelpPopover() { anchorPosition="leftUp" button={ { setIsPopoverOpen((prevIsPopoverOpen) => !prevIsPopoverOpen); }} @@ -25,25 +26,28 @@ export function FailedTransactionsCorrelationsHelpPopover() { } closePopover={() => setIsPopoverOpen(false)} isOpen={isPopoverOpen} - title={i18n.translate('xpack.apm.correlations.failurePopoverTitle', { - defaultMessage: 'Failure correlations', - })} + title={i18n.translate( + 'xpack.apm.correlations.failedTransactions.helpPopover.title', + { + defaultMessage: 'Failed transaction correlations', + } + )} >

diff --git a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx index 6d6e56184e254..ed47d49948fba 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/latency_correlations.tsx @@ -8,24 +8,18 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { - EuiCallOut, - EuiCode, - EuiEmptyPrompt, - EuiAccordion, - EuiPanel, EuiIcon, EuiBasicTableColumn, - EuiButton, EuiFlexGroup, EuiFlexItem, - EuiProgress, EuiSpacer, - EuiText, EuiTitle, EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { Direction } from '@elastic/eui/src/services/sort/sort_direction'; +import { orderBy } from 'lodash'; +import { EuiTableSortingType } from '@elastic/eui/src/components/basic_table/table_types'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; @@ -42,6 +36,10 @@ import { useApmServiceContext } from '../../../context/apm_service/use_apm_servi import { LatencyCorrelationsHelpPopover } from './latency_correlations_help_popover'; import { useApmParams } from '../../../hooks/use_apm_params'; import { isErrorMessage } from './utils/is_error_message'; +import { CorrelationsLog } from './correlations_log'; +import { CorrelationsEmptyStatePrompt } from './empty_state_prompt'; +import { CrossClusterSearchCompatibilityWarning } from './cross_cluster_search_warning'; +import { CorrelationsProgressControls } from './progress_controls'; const DEFAULT_PERCENTILE_THRESHOLD = 95; @@ -133,15 +131,19 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { setSelectedSignificantTerm, ] = useState(null); - let selectedHistogram = histograms.length > 0 ? histograms[0] : undefined; + const selectedHistogram = useMemo(() => { + let selected = histograms.length > 0 ? histograms[0] : undefined; + + if (histograms.length > 0 && selectedSignificantTerm !== null) { + selected = histograms.find( + (h) => + h.field === selectedSignificantTerm.fieldName && + h.value === selectedSignificantTerm.fieldValue + ); + } + return selected; + }, [histograms, selectedSignificantTerm]); - if (histograms.length > 0 && selectedSignificantTerm !== null) { - selectedHistogram = histograms.find( - (h) => - h.field === selectedSignificantTerm.fieldName && - h.value === selectedSignificantTerm.fieldValue - ); - } const history = useHistory(); const trackApmEvent = useUiTracker({ app: 'apm' }); @@ -181,6 +183,7 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { render: (correlation: number) => { return
{asPreciseDecimal(correlation, 2)}
; }, + sortable: true, }, { field: 'fieldName', @@ -188,6 +191,7 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { 'xpack.apm.correlations.latencyCorrelations.correlationsTable.fieldNameLabel', { defaultMessage: 'Field name' } ), + sortable: true, }, { field: 'fieldValue', @@ -196,6 +200,7 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { { defaultMessage: 'Field value' } ), render: (fieldValue: string) => String(fieldValue).slice(0, 50), + sortable: true, }, { width: '100px', @@ -214,9 +219,7 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { onClick: (term: MlCorrelationsTerms) => { push(history, { query: { - kuery: `${term.fieldName}:"${encodeURIComponent( - term.fieldValue - )}"`, + kuery: `${term.fieldName}:"${term.fieldValue}"`, }, }); onFilter(); @@ -237,9 +240,7 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { onClick: (term: MlCorrelationsTerms) => { push(history, { query: { - kuery: `not ${term.fieldName}:"${encodeURIComponent( - term.fieldValue - )}"`, + kuery: `not ${term.fieldName}:"${term.fieldValue}"`, }, }); onFilter(); @@ -256,17 +257,46 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { [history, onFilter, trackApmEvent] ); - const histogramTerms: MlCorrelationsTerms[] = useMemo(() => { - return histograms.map((d) => { - return { - fieldName: d.field, - fieldValue: d.value, - ksTest: d.ksTest, - correlation: d.correlation, - duplicatedFields: d.duplicatedFields, - }; - }); - }, [histograms]); + const [sortField, setSortField] = useState( + 'correlation' + ); + const [sortDirection, setSortDirection] = useState('desc'); + + const onTableChange = useCallback(({ sort }) => { + const { field: currentSortField, direction: currentSortDirection } = sort; + + setSortField(currentSortField); + setSortDirection(currentSortDirection); + }, []); + + const { histogramTerms, sorting } = useMemo(() => { + if (!Array.isArray(histograms)) { + return { histogramTerms: [], sorting: undefined }; + } + const orderedTerms = orderBy( + histograms.map((d) => { + return { + fieldName: d.field, + fieldValue: d.value, + ksTest: d.ksTest, + correlation: d.correlation, + duplicatedFields: d.duplicatedFields, + }; + }), + sortField, + sortDirection + ); + + return { + histogramTerms: orderedTerms, + sorting: { + sort: { + field: sortField, + direction: sortDirection, + }, + } as EuiTableSortingType, + }; + }, [histograms, sortField, sortDirection]); return (
@@ -300,88 +330,34 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { - +
{i18n.translate( 'xpack.apm.correlations.latencyCorrelations.tableTitle', { defaultMessage: 'Correlations', } )} - +
- - - - - - - - - - - - - - - {!isRunning && ( - - - - )} - {isRunning && ( - - - - )} - - + + {ccsWarning && ( <> - -

- {i18n.translate( - 'xpack.apm.correlations.latencyCorrelations.ccsWarningCalloutBody', - { - defaultMessage: - 'Data for the correlation analysis could not be fully retrieved. This feature is supported only for 7.14 and later versions.', - } - )} -

-
+ )} + +
{(isRunning || histogramTerms.length > 0) && ( @@ -397,70 +373,15 @@ export function LatencyCorrelations({ onFilter }: { onFilter: () => void }) { } : undefined } + onTableChange={onTableChange} + sorting={sorting} /> )} {histogramTerms.length < 1 && (progress === 1 || !isRunning) && ( - <> - - -

- {i18n.translate( - 'xpack.apm.correlations.latencyCorrelations.noCorrelationsTitle', - { - defaultMessage: 'No significant correlations', - } - )} -

- - } - body={ - <> - - - - {/* Another EuiText element to enforce a line break */} - - - - - } - /> - + )}
- {log.length > 0 && displayLog && ( - - - {log.map((d, i) => { - const splitItem = d.split(': '); - return ( -

- - {splitItem[0]} {splitItem[1]} - -

- ); - })} -
-
- )} + {displayLog && }
); } diff --git a/x-pack/plugins/apm/public/components/app/correlations/progress_controls.tsx b/x-pack/plugins/apm/public/components/app/correlations/progress_controls.tsx new file mode 100644 index 0000000000000..a581313d6a5d5 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/correlations/progress_controls.tsx @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiProgress, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; + +export function CorrelationsProgressControls({ + progress, + onRefresh, + onCancel, + isRunning, +}: { + progress: number; + onRefresh: () => void; + onCancel: () => void; + isRunning: boolean; +}) { + return ( + + + + + + + + + + + + + + + {!isRunning && ( + + + + )} + {isRunning && ( + + + + )} + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/correlations/utils/get_failed_transactions_correlation_impact_label.test.ts b/x-pack/plugins/apm/public/components/app/correlations/utils/get_failed_transactions_correlation_impact_label.test.ts index d133ed1060ebe..edb7c8c16e267 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/utils/get_failed_transactions_correlation_impact_label.test.ts +++ b/x-pack/plugins/apm/public/components/app/correlations/utils/get_failed_transactions_correlation_impact_label.test.ts @@ -8,6 +8,20 @@ import { getFailedTransactionsCorrelationImpactLabel } from './get_failed_transactions_correlation_impact_label'; import { FAILED_TRANSACTIONS_IMPACT_THRESHOLD } from '../../../../../common/search_strategies/failure_correlations/constants'; +const EXPECTED_RESULT = { + HIGH: { + impact: FAILED_TRANSACTIONS_IMPACT_THRESHOLD.HIGH, + color: 'danger', + }, + MEDIUM: { + impact: FAILED_TRANSACTIONS_IMPACT_THRESHOLD.MEDIUM, + color: 'warning', + }, + LOW: { + impact: FAILED_TRANSACTIONS_IMPACT_THRESHOLD.LOW, + color: 'default', + }, +}; describe('getFailedTransactionsCorrelationImpactLabel', () => { it('returns null if value is invalid ', () => { expect(getFailedTransactionsCorrelationImpactLabel(-0.03)).toBe(null); @@ -21,32 +35,32 @@ describe('getFailedTransactionsCorrelationImpactLabel', () => { }); it('returns High if value is within [0, 1e-6) ', () => { - expect(getFailedTransactionsCorrelationImpactLabel(0)).toBe( - FAILED_TRANSACTIONS_IMPACT_THRESHOLD.HIGH + expect(getFailedTransactionsCorrelationImpactLabel(0)).toStrictEqual( + EXPECTED_RESULT.HIGH ); - expect(getFailedTransactionsCorrelationImpactLabel(1e-7)).toBe( - FAILED_TRANSACTIONS_IMPACT_THRESHOLD.HIGH + expect(getFailedTransactionsCorrelationImpactLabel(1e-7)).toStrictEqual( + EXPECTED_RESULT.HIGH ); }); it('returns Medium if value is within [1e-6, 1e-3) ', () => { - expect(getFailedTransactionsCorrelationImpactLabel(1e-6)).toBe( - FAILED_TRANSACTIONS_IMPACT_THRESHOLD.MEDIUM + expect(getFailedTransactionsCorrelationImpactLabel(1e-6)).toStrictEqual( + EXPECTED_RESULT.MEDIUM ); - expect(getFailedTransactionsCorrelationImpactLabel(1e-5)).toBe( - FAILED_TRANSACTIONS_IMPACT_THRESHOLD.MEDIUM + expect(getFailedTransactionsCorrelationImpactLabel(1e-5)).toStrictEqual( + EXPECTED_RESULT.MEDIUM ); - expect(getFailedTransactionsCorrelationImpactLabel(1e-4)).toBe( - FAILED_TRANSACTIONS_IMPACT_THRESHOLD.MEDIUM + expect(getFailedTransactionsCorrelationImpactLabel(1e-4)).toStrictEqual( + EXPECTED_RESULT.MEDIUM ); }); it('returns Low if value is within [1e-3, 0.02) ', () => { - expect(getFailedTransactionsCorrelationImpactLabel(1e-3)).toBe( - FAILED_TRANSACTIONS_IMPACT_THRESHOLD.LOW + expect(getFailedTransactionsCorrelationImpactLabel(1e-3)).toStrictEqual( + EXPECTED_RESULT.LOW ); - expect(getFailedTransactionsCorrelationImpactLabel(0.009)).toBe( - FAILED_TRANSACTIONS_IMPACT_THRESHOLD.LOW + expect(getFailedTransactionsCorrelationImpactLabel(0.009)).toStrictEqual( + EXPECTED_RESULT.LOW ); }); }); diff --git a/x-pack/plugins/apm/public/components/app/correlations/utils/get_failed_transactions_correlation_impact_label.ts b/x-pack/plugins/apm/public/components/app/correlations/utils/get_failed_transactions_correlation_impact_label.ts index af64c50617019..5a806aba5371e 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/utils/get_failed_transactions_correlation_impact_label.ts +++ b/x-pack/plugins/apm/public/components/app/correlations/utils/get_failed_transactions_correlation_impact_label.ts @@ -10,14 +10,23 @@ import { FAILED_TRANSACTIONS_IMPACT_THRESHOLD } from '../../../../../common/sear export function getFailedTransactionsCorrelationImpactLabel( pValue: number -): FailureCorrelationImpactThreshold | null { +): { impact: FailureCorrelationImpactThreshold; color: string } | null { // The lower the p value, the higher the impact if (pValue >= 0 && pValue < 1e-6) - return FAILED_TRANSACTIONS_IMPACT_THRESHOLD.HIGH; + return { + impact: FAILED_TRANSACTIONS_IMPACT_THRESHOLD.HIGH, + color: 'danger', + }; if (pValue >= 1e-6 && pValue < 0.001) - return FAILED_TRANSACTIONS_IMPACT_THRESHOLD.MEDIUM; + return { + impact: FAILED_TRANSACTIONS_IMPACT_THRESHOLD.MEDIUM, + color: 'warning', + }; if (pValue >= 0.001 && pValue < 0.02) - return FAILED_TRANSACTIONS_IMPACT_THRESHOLD.LOW; + return { + impact: FAILED_TRANSACTIONS_IMPACT_THRESHOLD.LOW, + color: 'default', + }; return null; } diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx index 2506ac69f7aa2..86bef9917daf6 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx @@ -28,6 +28,9 @@ import { useApmParams } from '../../../../hooks/use_apm_params'; import { isErrorMessage } from '../../correlations/utils/is_error_message'; const DEFAULT_PERCENTILE_THRESHOLD = 95; +// Enforce min height so it's consistent across all tabs on the same level +// to prevent "flickering" behavior +const MIN_TAB_TITLE_HEIGHT = 56; type Selection = [number, number]; @@ -147,7 +150,7 @@ export function TransactionDistribution({ return (
- +
diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/failed_transactions_correlations_tab.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/failed_transactions_correlations_tab.tsx index 8743b8f3ea811..af66f818309a0 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/failed_transactions_correlations_tab.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/failed_transactions_correlations_tab.tsx @@ -46,7 +46,7 @@ function FailedTransactionsCorrelationsTab({ onFilter }: TabContentProps) { text={i18n.translate( 'xpack.apm.failedTransactionsCorrelations.licenseCheckText', { - defaultMessage: `To use the failed transactions correlations feature, you must be subscribed to an Elastic Platinum license.`, + defaultMessage: `To use the failed transaction correlations feature, you must be subscribed to an Elastic Platinum license. With it, you'll be able to discover which attributes are contributing to failed transactions.`, } )} /> diff --git a/x-pack/plugins/apm/public/hooks/use_failed_transactions_correlations_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_failed_transactions_correlations_fetcher.ts index 3841419e860fc..add00968f0444 100644 --- a/x-pack/plugins/apm/public/hooks/use_failed_transactions_correlations_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_failed_transactions_correlations_fetcher.ts @@ -38,9 +38,7 @@ interface FailedTransactionsCorrelationsFetcherState { total: number; } -export const useFailedTransactionsCorrelationsFetcher = ( - params: Omit -) => { +export const useFailedTransactionsCorrelationsFetcher = () => { const { services: { data }, } = useKibana(); @@ -74,7 +72,7 @@ export const useFailedTransactionsCorrelationsFetcher = ( })); } - const startFetch = () => { + const startFetch = (params: SearchServiceParams) => { setFetchState((prevState) => ({ ...prevState, error: undefined, diff --git a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/async_search_service.ts b/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/async_search_service.ts index 9afe9d916b38e..89fcda926d547 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/async_search_service.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/async_search_service.ts @@ -15,6 +15,7 @@ import { fetchTransactionDurationFieldCandidates } from '../correlations/queries import type { SearchServiceFetchParams } from '../../../../common/search_strategies/correlations/types'; import { fetchFailedTransactionsCorrelationPValues } from './queries/query_failure_correlation'; import { ERROR_CORRELATION_THRESHOLD } from './constants'; +import { EVENT_OUTCOME } from '../../../../common/elasticsearch_fieldnames'; export const asyncErrorCorrelationSearchServiceProvider = ( esClient: ElasticsearchClient, @@ -35,10 +36,11 @@ export const asyncErrorCorrelationSearchServiceProvider = ( includeFrozen, }; - const { fieldCandidates } = await fetchTransactionDurationFieldCandidates( - esClient, - params - ); + const { + fieldCandidates: candidates, + } = await fetchTransactionDurationFieldCandidates(esClient, params); + + const fieldCandidates = candidates.filter((t) => !(t === EVENT_OUTCOME)); addLogMessage(`Identified ${fieldCandidates.length} fieldCandidates.`); diff --git a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/queries/query_failure_correlation.ts b/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/queries/query_failure_correlation.ts index 22424d68f07ff..81fe6697d1fb1 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/queries/query_failure_correlation.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/failed_transactions_correlations/queries/query_failure_correlation.ts @@ -75,14 +75,15 @@ export const fetchFailedTransactionsCorrelationPValues = async ( ); } - const result = (resp.body.aggregations - .failure_p_value as estypes.AggregationsMultiBucketAggregate<{ + const overallResult = resp.body.aggregations + .failure_p_value as estypes.AggregationsSignificantTermsAggregate<{ key: string; doc_count: number; bg_count: number; score: number; - }>).buckets.map((b) => { - const score = b.score; + }>; + const result = overallResult.buckets.map((bucket) => { + const score = bucket.score; // Scale the score into a value from 0 - 1 // using a concave piecewise linear function in -log(p-value) @@ -92,11 +93,17 @@ export const fetchFailedTransactionsCorrelationPValues = async ( 0.25 * Math.min(Math.max((score - 13.816) / 101.314, 0), 1); return { - ...b, + ...bucket, fieldName, - fieldValue: b.key, + fieldValue: bucket.key, pValue: Math.exp(-score), normalizedScore, + // Percentage of time the term appears in failed transactions + failurePercentage: bucket.doc_count / overallResult.doc_count, + // Percentage of time the term appears in successful transactions + successPercentage: + (bucket.bg_count - bucket.doc_count) / + (overallResult.bg_count - overallResult.doc_count), }; }); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 90c2913f3cd98..b5b7e35c4a9b4 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -5518,7 +5518,6 @@ "xpack.apm.correlations.customize.fieldPlaceholder": "オプションを選択または作成", "xpack.apm.correlations.customize.thresholdLabel": "しきい値", "xpack.apm.correlations.customize.thresholdPercentile": "{percentile}パーセンタイル", - "xpack.apm.correlations.latencyCorrelations.cancelButtonTitle": "キャンセル", "xpack.apm.correlations.latencyCorrelations.correlationsTable.actionsLabel": "フィルター", "xpack.apm.correlations.latencyCorrelations.correlationsTable.correlationColumnDescription": "サービスの遅延に対するフィールドの影響。0~1の範囲。", "xpack.apm.correlations.latencyCorrelations.correlationsTable.correlationLabel": "相関関係", @@ -5529,9 +5528,6 @@ "xpack.apm.correlations.latencyCorrelations.correlationsTable.filterDescription": "値でフィルタリング", "xpack.apm.correlations.latencyCorrelations.correlationsTable.filterLabel": "フィルター", "xpack.apm.correlations.latencyCorrelations.errorTitle": "相関関係の取得中にエラーが発生しました", - "xpack.apm.correlations.latencyCorrelations.progressAriaLabel": "進捗", - "xpack.apm.correlations.latencyCorrelations.progressTitle": "進捗状況: {progress}%", - "xpack.apm.correlations.latencyCorrelations.refreshButtonTitle": "更新", "xpack.apm.csm.breakdownFilter.browser": "ブラウザー", "xpack.apm.csm.breakdownFilter.device": "デバイス", "xpack.apm.csm.breakdownFilter.location": "場所", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index fae942e60c39b..1a21d16c80f03 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -5543,7 +5543,6 @@ "xpack.apm.correlations.customize.fieldPlaceholder": "选择或创建选项", "xpack.apm.correlations.customize.thresholdLabel": "阈值", "xpack.apm.correlations.customize.thresholdPercentile": "第 {percentile} 个百分位数", - "xpack.apm.correlations.latencyCorrelations.cancelButtonTitle": "取消", "xpack.apm.correlations.latencyCorrelations.correlationsTable.actionsLabel": "筛选", "xpack.apm.correlations.latencyCorrelations.correlationsTable.correlationColumnDescription": "字段对服务延迟的影响,范围从 0 到 1。", "xpack.apm.correlations.latencyCorrelations.correlationsTable.correlationLabel": "相关性", @@ -5554,9 +5553,6 @@ "xpack.apm.correlations.latencyCorrelations.correlationsTable.filterDescription": "按值筛选", "xpack.apm.correlations.latencyCorrelations.correlationsTable.filterLabel": "筛选", "xpack.apm.correlations.latencyCorrelations.errorTitle": "提取关联性时发生错误", - "xpack.apm.correlations.latencyCorrelations.progressAriaLabel": "进度", - "xpack.apm.correlations.latencyCorrelations.progressTitle": "进度:{progress}%", - "xpack.apm.correlations.latencyCorrelations.refreshButtonTitle": "刷新", "xpack.apm.csm.breakdownFilter.browser": "浏览器", "xpack.apm.csm.breakdownFilter.device": "设备", "xpack.apm.csm.breakdownFilter.location": "位置", diff --git a/x-pack/test/apm_api_integration/common/utils/parse_b_fetch.ts b/x-pack/test/apm_api_integration/common/utils/parse_b_fetch.ts new file mode 100644 index 0000000000000..79ea70f7199f9 --- /dev/null +++ b/x-pack/test/apm_api_integration/common/utils/parse_b_fetch.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import request from 'superagent'; + +export function parseBfetchResponse(resp: request.Response): Array> { + return resp.text + .trim() + .split('\n') + .map((item) => JSON.parse(item)); +} diff --git a/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts b/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts new file mode 100644 index 0000000000000..2d014b3ee1e6b --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts @@ -0,0 +1,238 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; +import { PartialSearchRequest } from '../../../../plugins/apm/server/lib/search_strategies/correlations/search_strategy'; +import { parseBfetchResponse } from '../../common/utils/parse_b_fetch'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const retry = getService('retry'); + const supertest = getService('supertest'); + + const getRequestBody = () => { + const partialSearchRequest: PartialSearchRequest = { + params: { + environment: 'ENVIRONMENT_ALL', + start: '2020', + end: '2021', + kuery: '', + }, + }; + + return { + batch: [ + { + request: partialSearchRequest, + options: { strategy: 'apmFailedTransactionsCorrelationsSearchStrategy' }, + }, + ], + }; + }; + + registry.when('on trial license without data', { config: 'trial', archives: [] }, () => { + it('queries the search strategy and returns results', async () => { + const intialResponse = await supertest + .post(`/internal/bsearch`) + .set('kbn-xsrf', 'foo') + .send(getRequestBody()); + + expect(intialResponse.status).to.eql( + 200, + `Expected status to be '200', got '${intialResponse.status}'` + ); + expect(intialResponse.body).to.eql( + {}, + `Expected response body to be an empty object, actual response is in the text attribute. Got: '${JSON.stringify( + intialResponse.body + )}'` + ); + + const body = parseBfetchResponse(intialResponse)[0]; + + expect(typeof body.result).to.be('object'); + const { result } = body; + + expect(typeof result?.id).to.be('string'); + + // pass on id for follow up queries + const searchStrategyId = result.id; + + // follow up request body including search strategy ID + const reqBody = getRequestBody(); + reqBody.batch[0].request.id = searchStrategyId; + + let followUpResponse: Record = {}; + + // continues querying until the search strategy finishes + await retry.waitForWithTimeout( + 'search strategy eventually completes and returns full results', + 5000, + async () => { + const response = await supertest + .post(`/internal/bsearch`) + .set('kbn-xsrf', 'foo') + .send(reqBody); + + followUpResponse = parseBfetchResponse(response)[0]; + + return ( + followUpResponse?.result?.isRunning === false || followUpResponse?.error !== undefined + ); + } + ); + + expect(followUpResponse?.error).to.eql( + undefined, + `search strategy should not return an error, got: ${JSON.stringify( + followUpResponse?.error + )}` + ); + + const followUpResult = followUpResponse.result; + expect(followUpResult?.isRunning).to.eql(false, 'search strategy should not be running'); + expect(followUpResult?.isPartial).to.eql( + false, + 'search strategy result should not be partial' + ); + expect(followUpResult?.id).to.eql( + searchStrategyId, + 'search strategy id should match original id' + ); + expect(followUpResult?.isRestored).to.eql( + true, + 'search strategy response should be restored' + ); + expect(followUpResult?.loaded).to.eql(100, 'loaded state should be 100'); + expect(followUpResult?.total).to.eql(100, 'total state should be 100'); + + expect(typeof followUpResult?.rawResponse).to.be('object'); + + const { rawResponse: finalRawResponse } = followUpResult; + + expect(typeof finalRawResponse?.took).to.be('number'); + + expect(finalRawResponse?.values.length).to.eql( + 0, + `Expected 0 identified correlations, got ${finalRawResponse?.values.length}.` + ); + }); + }); + + registry.when('on trial license with data', { config: 'trial', archives: ['8.0.0'] }, () => { + it('queries the search strategy and returns results', async () => { + const intialResponse = await supertest + .post(`/internal/bsearch`) + .set('kbn-xsrf', 'foo') + .send(getRequestBody()); + + expect(intialResponse.status).to.eql( + 200, + `Expected status to be '200', got '${intialResponse.status}'` + ); + expect(intialResponse.body).to.eql( + {}, + `Expected response body to be an empty object, actual response is in the text attribute. Got: '${JSON.stringify( + intialResponse.body + )}'` + ); + + const body = parseBfetchResponse(intialResponse)[0]; + + expect(typeof body.result).to.be('object'); + const { result } = body; + + expect(typeof result?.id).to.be('string'); + + // pass on id for follow up queries + const searchStrategyId = result.id; + + // follow up request body including search strategy ID + const reqBody = getRequestBody(); + reqBody.batch[0].request.id = searchStrategyId; + + let followUpResponse: Record = {}; + + // continues querying until the search strategy finishes + await retry.waitForWithTimeout( + 'search strategy eventually completes and returns full results', + 5000, + async () => { + const response = await supertest + .post(`/internal/bsearch`) + .set('kbn-xsrf', 'foo') + .send(reqBody); + + followUpResponse = parseBfetchResponse(response)[0]; + + return ( + followUpResponse?.result?.isRunning === false || followUpResponse?.error !== undefined + ); + } + ); + + expect(followUpResponse?.error).to.eql( + undefined, + `search strategy should not return an error, got: ${JSON.stringify( + followUpResponse?.error + )}` + ); + + const followUpResult = followUpResponse.result; + expect(followUpResult?.isRunning).to.eql(false, 'search strategy should not be running'); + expect(followUpResult?.isPartial).to.eql( + false, + 'search strategy result should not be partial' + ); + expect(followUpResult?.id).to.eql( + searchStrategyId, + 'search strategy id should match original id' + ); + expect(followUpResult?.isRestored).to.eql( + true, + 'search strategy response should be restored' + ); + expect(followUpResult?.loaded).to.eql(100, 'loaded state should be 100'); + expect(followUpResult?.total).to.eql(100, 'total state should be 100'); + + expect(typeof followUpResult?.rawResponse).to.be('object'); + + const { rawResponse: finalRawResponse } = followUpResult; + + expect(typeof finalRawResponse?.took).to.be('number'); + expect(finalRawResponse?.percentileThresholdValue).to.be(undefined); + expect(finalRawResponse?.overallHistogram).to.be(undefined); + + expect(finalRawResponse?.values.length).to.eql( + 43, + `Expected 43 identified correlations, got ${finalRawResponse?.values.length}.` + ); + + expect(finalRawResponse?.log.map((d: string) => d.split(': ')[1])).to.eql([ + 'Identified 68 fieldCandidates.', + 'Identified correlations for 68 fields out of 68 candidates.', + 'Identified 43 significant correlations relating to failed transactions.', + ]); + + const sortedCorrelations = finalRawResponse?.values.sort(); + const correlation = sortedCorrelations[0]; + + expect(typeof correlation).to.be('object'); + expect(correlation?.key).to.be('HTTP 5xx'); + expect(correlation?.doc_count).to.be(31); + expect(correlation?.score).to.be(100.17736139032642); + expect(correlation?.bg_count).to.be(60); + expect(correlation?.fieldName).to.be('transaction.result'); + expect(correlation?.fieldValue).to.be('HTTP 5xx'); + expect(typeof correlation?.pValue).to.be('number'); + expect(typeof correlation?.normalizedScore).to.be('number'); + expect(typeof correlation?.failurePercentage).to.be('number'); + expect(typeof correlation?.successPercentage).to.be('number'); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/correlations/latency_ml.ts b/x-pack/test/apm_api_integration/tests/correlations/latency.ts similarity index 97% rename from x-pack/test/apm_api_integration/tests/correlations/latency_ml.ts rename to x-pack/test/apm_api_integration/tests/correlations/latency.ts index e41a830735a89..32ca71694626f 100644 --- a/x-pack/test/apm_api_integration/tests/correlations/latency_ml.ts +++ b/x-pack/test/apm_api_integration/tests/correlations/latency.ts @@ -6,18 +6,10 @@ */ import expect from '@kbn/expect'; -import request from 'superagent'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { registry } from '../../common/registry'; - import { PartialSearchRequest } from '../../../../plugins/apm/server/lib/search_strategies/correlations/search_strategy'; - -function parseBfetchResponse(resp: request.Response): Array> { - return resp.text - .trim() - .split('\n') - .map((item) => JSON.parse(item)); -} +import { parseBfetchResponse } from '../../common/utils/parse_b_fetch'; export default function ApiTest({ getService }: FtrProviderContext) { const retry = getService('retry'); diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts index 0e76a4ed86688..c8a57bc613a92 100644 --- a/x-pack/test/apm_api_integration/tests/index.ts +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -28,12 +28,17 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte loadTestFile(require.resolve('./alerts/rule_registry')); }); + // correlations describe('correlations/latency_slow_transactions', function () { loadTestFile(require.resolve('./correlations/latency_slow_transactions')); }); - describe('correlations/latency_ml', function () { - loadTestFile(require.resolve('./correlations/latency_ml')); + describe('correlations/failed_transactions', function () { + loadTestFile(require.resolve('./correlations/failed_transactions')); + }); + + describe('correlations/latency', function () { + loadTestFile(require.resolve('./correlations/latency')); }); describe('correlations/latency_overall', function () { diff --git a/x-pack/test/functional/apps/apm/correlations/failed_transaction_correlations.ts b/x-pack/test/functional/apps/apm/correlations/failed_transaction_correlations.ts new file mode 100644 index 0000000000000..969a543e5f97d --- /dev/null +++ b/x-pack/test/functional/apps/apm/correlations/failed_transaction_correlations.ts @@ -0,0 +1,155 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const find = getService('find'); + const retry = getService('retry'); + const spacesService = getService('spaces'); + const PageObjects = getPageObjects(['common', 'error', 'timePicker', 'security']); + const testSubjects = getService('testSubjects'); + const appsMenu = getService('appsMenu'); + + const testData = { + correlationsTab: 'Failed transaction correlations', + serviceName: 'opbeans-go', + transactionsTab: 'Transactions', + transaction: 'GET /api/stats', + }; + + describe('failed transactions correlations', () => { + describe('space with no features disabled', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/infra/8.0.0/metrics_and_apm'); + await spacesService.create({ + id: 'custom_space', + name: 'custom_space', + disabledFeatures: [], + }); + }); + + after(async () => { + await spacesService.delete('custom_space'); + }); + + it('shows apm navlink', async () => { + await PageObjects.common.navigateToApp('home', { + basePath: '/s/custom_space', + }); + const navLinks = (await appsMenu.readLinks()).map((link) => link.text); + expect(navLinks).to.contain('APM'); + }); + + it('can navigate to APM app', async () => { + await PageObjects.common.navigateToApp('apm'); + + await retry.try(async () => { + await testSubjects.existOrFail('apmMainContainer', { + timeout: 10000, + }); + + const apmMainContainerText = await testSubjects.getVisibleTextAll('apmMainContainer'); + const apmMainContainerTextItems = apmMainContainerText[0].split('\n'); + expect(apmMainContainerTextItems).to.contain('No services found'); + }); + }); + + it('sets the timePicker to return data', async () => { + await PageObjects.timePicker.timePickerExists(); + + const fromTime = 'Jul 29, 2019 @ 00:00:00.000'; + const toTime = 'Jul 30, 2019 @ 00:00:00.000'; + await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + + await retry.try(async () => { + const apmMainContainerText = await testSubjects.getVisibleTextAll('apmMainContainer'); + const apmMainContainerTextItems = apmMainContainerText[0].split('\n'); + + expect(apmMainContainerTextItems).to.not.contain('No services found'); + + expect(apmMainContainerTextItems).to.contain('opbeans-go'); + expect(apmMainContainerTextItems).to.contain('opbeans-node'); + expect(apmMainContainerTextItems).to.contain('opbeans-ruby'); + expect(apmMainContainerTextItems).to.contain('opbeans-python'); + expect(apmMainContainerTextItems).to.contain('opbeans-dotnet'); + expect(apmMainContainerTextItems).to.contain('opbeans-java'); + + expect(apmMainContainerTextItems).to.contain('development'); + + const items = await testSubjects.findAll('apmServiceListAppLink'); + expect(items.length).to.be(6); + }); + }); + + it(`navigates to the 'opbeans-go' service overview page`, async function () { + await find.clickByDisplayedLinkText(testData.serviceName); + + await retry.try(async () => { + const apmMainTemplateHeaderServiceName = await testSubjects.getVisibleTextAll( + 'apmMainTemplateHeaderServiceName' + ); + expect(apmMainTemplateHeaderServiceName).to.contain(testData.serviceName); + }); + }); + + it('navigates to the transactions tab', async function () { + await find.clickByDisplayedLinkText(testData.transactionsTab); + + await retry.try(async () => { + const apmMainContainerText = await testSubjects.getVisibleTextAll('apmMainContainer'); + const apmMainContainerTextItems = apmMainContainerText[0].split('\n'); + + expect(apmMainContainerTextItems).to.contain(testData.transaction); + }); + }); + + it(`navigates to the 'GET /api/stats' transactions`, async function () { + await find.clickByDisplayedLinkText(testData.transaction); + + await retry.try(async () => { + const apmMainContainerText = await testSubjects.getVisibleTextAll('apmMainContainer'); + const apmMainContainerTextItems = apmMainContainerText[0].split('\n'); + + expect(apmMainContainerTextItems).to.contain(testData.transaction); + expect(apmMainContainerTextItems).to.contain(testData.correlationsTab); + }); + }); + + it('shows the failed transactions correlations tab', async function () { + await testSubjects.click('apmFailedTransactionsCorrelationsTabButton'); + + await retry.try(async () => { + await testSubjects.existOrFail('apmFailedTransactionsCorrelationsTabContent'); + }); + }); + + it('loads the failed transactions correlations results', async function () { + await retry.try(async () => { + const apmFailedTransactionsCorrelationsTabTitle = await testSubjects.getVisibleText( + 'apmFailedTransactionsCorrelationsTabTitle' + ); + expect(apmFailedTransactionsCorrelationsTabTitle).to.be('Failed transactions'); + + // Assert that the data fully loaded to 100% + const apmFailedTransactionsCorrelationsProgressTitle = await testSubjects.getVisibleText( + 'apmCorrelationsProgressTitle' + ); + expect(apmFailedTransactionsCorrelationsProgressTitle).to.be('Progress: 100%'); + + // Assert that results for the given service didn't find any correlations + const apmCorrelationsTable = await testSubjects.getVisibleText('apmCorrelationsTable'); + expect(apmCorrelationsTable).to.be( + 'No significant correlations\nCorrelations will only be identified if they have significant impact.\nTry selecting another time range or remove any added filter.' + ); + }); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/apm/correlations/index.ts b/x-pack/test/functional/apps/apm/correlations/index.ts index ae5f594e54400..abf74910f4e8d 100644 --- a/x-pack/test/functional/apps/apm/correlations/index.ts +++ b/x-pack/test/functional/apps/apm/correlations/index.ts @@ -9,7 +9,8 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('correlations', function () { - this.tags('skipFirefox'); + this.tags(['skipFirefox', 'apm']); loadTestFile(require.resolve('./latency_correlations')); + loadTestFile(require.resolve('./failed_transaction_correlations')); }); } diff --git a/x-pack/test/functional/apps/apm/correlations/latency_correlations.ts b/x-pack/test/functional/apps/apm/correlations/latency_correlations.ts index 3f743c378b7c3..af3633798133b 100644 --- a/x-pack/test/functional/apps/apm/correlations/latency_correlations.ts +++ b/x-pack/test/functional/apps/apm/correlations/latency_correlations.ts @@ -144,10 +144,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('loads the correlation results', async function () { await retry.try(async () => { // Assert that the data fully loaded to 100% - const apmCorrelationsLatencyCorrelationsProgressTitle = await testSubjects.getVisibleText( - 'apmCorrelationsLatencyCorrelationsProgressTitle' + const apmLatencyCorrelationsProgressTitle = await testSubjects.getVisibleText( + 'apmCorrelationsProgressTitle' ); - expect(apmCorrelationsLatencyCorrelationsProgressTitle).to.be('Progress: 100%'); + expect(apmLatencyCorrelationsProgressTitle).to.be('Progress: 100%'); // Assert that the Correlations Chart and its header are present const apmCorrelationsLatencyCorrelationsChartTitle = await testSubjects.getVisibleText( From 94bdff6e8a167429ea79e8a85c59834094f429da Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Mon, 23 Aug 2021 16:58:32 +0300 Subject: [PATCH 12/27] [Canvas] Expression tagcloud (#108036) (#109627) * Added `expression_tagcloud` plugin. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> # Conflicts: # .github/CODEOWNERS # packages/kbn-optimizer/limits.yml --- .i18nrc.json | 1 + docs/developer/plugin-list.asciidoc | 4 + jest.config.js | 1 + packages/kbn-optimizer/limits.yml | 2 +- src/dev/typescript/projects.ts | 1 + .../expression_tagcloud/.i18nrc.json | 6 + .../expression_tagcloud/README.md | 9 + .../expression_tagcloud/common/constants.ts} | 8 +- .../tagcloud_function.test.ts.snap} | 2 +- .../common/expression_functions/index.ts | 13 ++ .../tagcloud_function.test.ts} | 8 +- .../expression_functions/tagcloud_function.ts | 164 ++++++++++++++++++ .../expression_tagcloud/common/index.ts | 9 + .../common/types/expression_functions.ts | 61 +++++++ .../common/types/expression_renderers.ts | 13 ++ .../expression_tagcloud/common/types/index.ts | 9 + .../expression_tagcloud/jest.config.js | 13 ++ .../expression_tagcloud/kibana.json | 15 ++ .../public/components/index.ts | 9 + .../public/components/tag_cloud.scss | 0 .../components/tagcloud_component.test.tsx} | 8 +- .../public/components/tagcloud_component.tsx} | 13 +- .../public/expression_renderers/index.ts | 9 + .../tagcloud_renderer.tsx | 61 +++++++ .../expression_tagcloud/public/index.ts | 15 ++ .../expression_tagcloud/public/plugin.ts | 51 ++++++ .../expression_tagcloud/public/services.ts | 22 +++ .../expression_tagcloud/server/index.ts | 13 ++ .../expression_tagcloud/server/plugin.ts | 34 ++++ .../expression_tagcloud/tsconfig.json | 24 +++ src/plugins/vis_type_tagcloud/kibana.json | 2 +- .../vis_type_tagcloud/public/plugin.ts | 29 +--- .../vis_type_tagcloud/public/tag_cloud_fn.ts | 143 --------------- .../public/tag_cloud_vis_renderer.tsx | 47 ----- .../vis_type_tagcloud/public/to_ast.ts | 3 +- src/plugins/vis_type_tagcloud/public/types.ts | 6 - src/plugins/vis_type_tagcloud/tsconfig.json | 1 - .../snapshots/baseline/partial_test_1.json | 2 +- .../snapshots/baseline/tagcloud_all_data.json | 2 +- .../snapshots/baseline/tagcloud_fontsize.json | 2 +- .../baseline/tagcloud_invalid_data.json | 2 +- .../baseline/tagcloud_metric_data.json | 2 +- .../snapshots/baseline/tagcloud_options.json | 2 +- .../snapshots/session/partial_test_1.json | 2 +- .../snapshots/session/tagcloud_all_data.json | 2 +- .../snapshots/session/tagcloud_fontsize.json | 2 +- .../session/tagcloud_invalid_data.json | 2 +- .../session/tagcloud_metric_data.json | 2 +- .../snapshots/session/tagcloud_options.json | 2 +- .../translations/translations/ja-JP.json | 16 +- .../translations/translations/zh-CN.json | 16 +- 51 files changed, 610 insertions(+), 275 deletions(-) create mode 100755 src/plugins/chart_expressions/expression_tagcloud/.i18nrc.json create mode 100755 src/plugins/chart_expressions/expression_tagcloud/README.md rename src/plugins/{vis_type_tagcloud/public/services.ts => chart_expressions/expression_tagcloud/common/constants.ts} (57%) rename src/plugins/{vis_type_tagcloud/public/__snapshots__/tag_cloud_fn.test.ts.snap => chart_expressions/expression_tagcloud/common/expression_functions/__snapshots__/tagcloud_function.test.ts.snap} (98%) create mode 100644 src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/index.ts rename src/plugins/{vis_type_tagcloud/public/tag_cloud_fn.test.ts => chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.test.ts} (82%) create mode 100644 src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.ts create mode 100755 src/plugins/chart_expressions/expression_tagcloud/common/index.ts create mode 100644 src/plugins/chart_expressions/expression_tagcloud/common/types/expression_functions.ts create mode 100644 src/plugins/chart_expressions/expression_tagcloud/common/types/expression_renderers.ts create mode 100644 src/plugins/chart_expressions/expression_tagcloud/common/types/index.ts create mode 100644 src/plugins/chart_expressions/expression_tagcloud/jest.config.js create mode 100755 src/plugins/chart_expressions/expression_tagcloud/kibana.json create mode 100644 src/plugins/chart_expressions/expression_tagcloud/public/components/index.ts rename src/plugins/{vis_type_tagcloud => chart_expressions/expression_tagcloud}/public/components/tag_cloud.scss (100%) rename src/plugins/{vis_type_tagcloud/public/components/tag_cloud_chart.test.tsx => chart_expressions/expression_tagcloud/public/components/tagcloud_component.test.tsx} (93%) rename src/plugins/{vis_type_tagcloud/public/components/tag_cloud_chart.tsx => chart_expressions/expression_tagcloud/public/components/tagcloud_component.tsx} (93%) create mode 100644 src/plugins/chart_expressions/expression_tagcloud/public/expression_renderers/index.ts create mode 100644 src/plugins/chart_expressions/expression_tagcloud/public/expression_renderers/tagcloud_renderer.tsx create mode 100644 src/plugins/chart_expressions/expression_tagcloud/public/index.ts create mode 100644 src/plugins/chart_expressions/expression_tagcloud/public/plugin.ts create mode 100644 src/plugins/chart_expressions/expression_tagcloud/public/services.ts create mode 100644 src/plugins/chart_expressions/expression_tagcloud/server/index.ts create mode 100644 src/plugins/chart_expressions/expression_tagcloud/server/plugin.ts create mode 100644 src/plugins/chart_expressions/expression_tagcloud/tsconfig.json delete mode 100644 src/plugins/vis_type_tagcloud/public/tag_cloud_fn.ts delete mode 100644 src/plugins/vis_type_tagcloud/public/tag_cloud_vis_renderer.tsx diff --git a/.i18nrc.json b/.i18nrc.json index 6c0f8d75c84b3..6369637479478 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -25,6 +25,7 @@ "expressionRepeatImage": "src/plugins/expression_repeat_image", "expressionRevealImage": "src/plugins/expression_reveal_image", "expressionShape": "src/plugins/expression_shape", + "expressionTagcloud": "src/plugins/chart_expressions/expression_tagcloud", "inputControl": "src/plugins/input_control_vis", "inspector": "src/plugins/inspector", "inspectorViews": "src/legacy/core_plugins/inspector_views", diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 8199a5995d368..393e5efed4516 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -113,6 +113,10 @@ for use in their own application. |Expression Shape plugin adds a shape function to the expression plugin and an associated renderer. The renderer will display the given shape with selected decorations. +|{kib-repo}blob/{branch}/src/plugins/chart_expressions/expression_tagcloud/README.md[expressionTagcloud] +|Expression Tagcloud plugin adds a tagcloud renderer and function to the expression plugin. The renderer will display the Wordcloud chart. + + |{kib-repo}blob/{branch}/src/plugins/field_formats/README.md[fieldFormats] |Index pattern fields formatters diff --git a/jest.config.js b/jest.config.js index 6cb23b279925e..09532dc28bbb2 100644 --- a/jest.config.js +++ b/jest.config.js @@ -13,6 +13,7 @@ module.exports = { '/packages/*/jest.config.js', '/src/*/jest.config.js', '/src/plugins/*/jest.config.js', + '/src/plugins/chart_expressions/*/jest.config.js', '/src/plugins/vis_types/*/jest.config.js', '/test/*/jest.config.js', '/x-pack/plugins/*/jest.config.js', diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index b405fbbe8fafc..4dbb4ad51fa81 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -119,4 +119,4 @@ pageLoadAssetSize: expressionImage: 19288 expressionMetric: 22238 expressionShape: 34008 - + expressionTagcloud: 27505 diff --git a/src/dev/typescript/projects.ts b/src/dev/typescript/projects.ts index 58234be1317a7..e3d8185e73e55 100644 --- a/src/dev/typescript/projects.ts +++ b/src/dev/typescript/projects.ts @@ -70,6 +70,7 @@ export const PROJECTS = [ ...findProjects('packages/*/tsconfig.json'), ...findProjects('src/plugins/*/tsconfig.json'), + ...findProjects('src/plugins/chart_expressions/*/tsconfig.json'), ...findProjects('src/plugins/vis_types/*/tsconfig.json'), ...findProjects('x-pack/plugins/*/tsconfig.json'), ...findProjects('examples/*/tsconfig.json'), diff --git a/src/plugins/chart_expressions/expression_tagcloud/.i18nrc.json b/src/plugins/chart_expressions/expression_tagcloud/.i18nrc.json new file mode 100755 index 0000000000000..df4e39309f98e --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/.i18nrc.json @@ -0,0 +1,6 @@ +{ + "prefix": "expressionTagcloud", + "paths": { + "expressionTagcloud": "." + } +} diff --git a/src/plugins/chart_expressions/expression_tagcloud/README.md b/src/plugins/chart_expressions/expression_tagcloud/README.md new file mode 100755 index 0000000000000..ae7635ffe0173 --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/README.md @@ -0,0 +1,9 @@ +# expressionTagcloud + +Expression Tagcloud plugin adds a `tagcloud` renderer and function to the expression plugin. The renderer will display the `Wordcloud` chart. + +--- + +## Development + +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/vis_type_tagcloud/public/services.ts b/src/plugins/chart_expressions/expression_tagcloud/common/constants.ts similarity index 57% rename from src/plugins/vis_type_tagcloud/public/services.ts rename to src/plugins/chart_expressions/expression_tagcloud/common/constants.ts index abec36c4aae7b..3d834448a94ef 100644 --- a/src/plugins/vis_type_tagcloud/public/services.ts +++ b/src/plugins/chart_expressions/expression_tagcloud/common/constants.ts @@ -6,9 +6,7 @@ * Side Public License, v 1. */ -import { createGetterSetter } from '../../kibana_utils/public'; -import { DataPublicPluginStart } from '../../data/public'; +export const PLUGIN_ID = 'expressionTagcloud'; +export const PLUGIN_NAME = 'expressionTagcloud'; -export const [getFormatService, setFormatService] = createGetterSetter< - DataPublicPluginStart['fieldFormats'] ->('data.fieldFormats'); +export const EXPRESSION_NAME = 'tagcloud'; diff --git a/src/plugins/vis_type_tagcloud/public/__snapshots__/tag_cloud_fn.test.ts.snap b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/__snapshots__/tagcloud_function.test.ts.snap similarity index 98% rename from src/plugins/vis_type_tagcloud/public/__snapshots__/tag_cloud_fn.test.ts.snap rename to src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/__snapshots__/tagcloud_function.test.ts.snap index 2888d7637546c..56b24f0ae004f 100644 --- a/src/plugins/vis_type_tagcloud/public/__snapshots__/tag_cloud_fn.test.ts.snap +++ b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/__snapshots__/tagcloud_function.test.ts.snap @@ -22,7 +22,7 @@ Object { exports[`interpreter/functions#tagcloud returns an object with the correct structure 1`] = ` Object { - "as": "tagloud_vis", + "as": "tagcloud", "type": "render", "value": Object { "syncColors": false, diff --git a/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/index.ts b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/index.ts new file mode 100644 index 0000000000000..5df32e3991edc --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { tagcloudFunction } from './tagcloud_function'; + +export const functions = [tagcloudFunction]; + +export { tagcloudFunction }; diff --git a/src/plugins/vis_type_tagcloud/public/tag_cloud_fn.test.ts b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.test.ts similarity index 82% rename from src/plugins/vis_type_tagcloud/public/tag_cloud_fn.test.ts rename to src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.test.ts index 1671c0b01a666..2c6e021b5107a 100644 --- a/src/plugins/vis_type_tagcloud/public/tag_cloud_fn.test.ts +++ b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.test.ts @@ -6,13 +6,13 @@ * Side Public License, v 1. */ -import { createTagCloudFn } from './tag_cloud_fn'; +import { tagcloudFunction } from './tagcloud_function'; -import { functionWrapper } from '../../expressions/common/expression_functions/specs/tests/utils'; -import { Datatable } from '../../expressions/common/expression_types/specs'; +import { functionWrapper } from '../../../../expressions/common/expression_functions/specs/tests/utils'; +import { Datatable } from '../../../../expressions/common/expression_types/specs'; describe('interpreter/functions#tagcloud', () => { - const fn = functionWrapper(createTagCloudFn()); + const fn = functionWrapper(tagcloudFunction()); const context = { type: 'datatable', rows: [{ 'col-0-1': 0 }], diff --git a/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.ts b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.ts new file mode 100644 index 0000000000000..c3553c4660ce9 --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/common/expression_functions/tagcloud_function.ts @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { i18n } from '@kbn/i18n'; + +import { prepareLogTable, Dimension } from '../../../../visualizations/common/prepare_log_table'; +import { TagCloudVisParams } from '../types'; +import { ExpressionTagcloudFunction } from '../types'; +import { EXPRESSION_NAME } from '../constants'; + +const strings = { + help: i18n.translate('expressionTagcloud.functions.tagcloudHelpText', { + defaultMessage: 'Tagcloud visualization.', + }), + args: { + scale: i18n.translate('expressionTagcloud.functions.tagcloud.args.scaleHelpText', { + defaultMessage: 'Scale to determine font size of a word', + }), + orientation: i18n.translate('expressionTagcloud.functions.tagcloud.args.orientationHelpText', { + defaultMessage: 'Orientation of words inside tagcloud', + }), + minFontSize: i18n.translate('expressionTagcloud.functions.tagcloud.args.minFontSizeHelpText', { + defaultMessage: 'Min font size', + }), + maxFontSize: i18n.translate('expressionTagcloud.functions.tagcloud.args.maxFontSizeHelpText', { + defaultMessage: 'Max font size', + }), + showLabel: i18n.translate('expressionTagcloud.functions.tagcloud.args.showLabelHelpText', { + defaultMessage: 'Show chart label', + }), + palette: i18n.translate('expressionTagcloud.functions.tagcloud.args.paletteHelpText', { + defaultMessage: 'Defines the chart palette name', + }), + metric: i18n.translate('expressionTagcloud.functions.tagcloud.args.metricHelpText', { + defaultMessage: 'metric dimension configuration', + }), + bucket: i18n.translate('expressionTagcloud.functions.tagcloud.args.bucketHelpText', { + defaultMessage: 'bucket dimension configuration', + }), + }, + dimension: { + tags: i18n.translate('expressionTagcloud.functions.tagcloud.dimension.tags', { + defaultMessage: 'Tags', + }), + tagSize: i18n.translate('expressionTagcloud.functions.tagcloud.dimension.tagSize', { + defaultMessage: 'Tag size', + }), + }, +}; + +export const errors = { + invalidPercent: (percent: number) => + new Error( + i18n.translate('expressionTagcloud.functions.tagcloud.invalidPercentErrorMessage', { + defaultMessage: "Invalid value: '{percent}'. Percentage must be between 0 and 1", + values: { + percent, + }, + }) + ), + invalidImageUrl: (imageUrl: string) => + new Error( + i18n.translate('expressionTagcloud.functions.tagcloud.invalidImageUrl', { + defaultMessage: "Invalid image url: '{imageUrl}'.", + values: { + imageUrl, + }, + }) + ), +}; + +export const tagcloudFunction: ExpressionTagcloudFunction = () => { + const { help, args: argHelp, dimension } = strings; + + return { + name: EXPRESSION_NAME, + type: 'render', + inputTypes: ['datatable'], + help, + args: { + scale: { + types: ['string'], + default: 'linear', + options: ['linear', 'log', 'square root'], + help: argHelp.scale, + }, + orientation: { + types: ['string'], + default: 'single', + options: ['single', 'right angled', 'multiple'], + help: argHelp.orientation, + }, + minFontSize: { + types: ['number'], + default: 18, + help: argHelp.minFontSize, + }, + maxFontSize: { + types: ['number'], + default: 72, + help: argHelp.maxFontSize, + }, + showLabel: { + types: ['boolean'], + default: true, + help: argHelp.showLabel, + }, + palette: { + types: ['string'], + help: argHelp.palette, + default: 'default', + }, + metric: { + types: ['vis_dimension'], + help: argHelp.metric, + required: true, + }, + bucket: { + types: ['vis_dimension'], + help: argHelp.bucket, + }, + }, + fn(input, args, handlers) { + const visParams = { + scale: args.scale, + orientation: args.orientation, + minFontSize: args.minFontSize, + maxFontSize: args.maxFontSize, + showLabel: args.showLabel, + metric: args.metric, + ...(args.bucket && { + bucket: args.bucket, + }), + palette: { + type: 'palette', + name: args.palette, + }, + } as TagCloudVisParams; + + if (handlers?.inspectorAdapters?.tables) { + const argsTable: Dimension[] = [[[args.metric], dimension.tagSize]]; + if (args.bucket) { + argsTable.push([[args.bucket], dimension.tags]); + } + const logTable = prepareLogTable(input, argsTable); + handlers.inspectorAdapters.tables.logDatatable('default', logTable); + } + return { + type: 'render', + as: EXPRESSION_NAME, + value: { + visData: input, + visType: EXPRESSION_NAME, + visParams, + syncColors: handlers?.isSyncColorsEnabled?.() ?? false, + }, + }; + }, + }; +}; diff --git a/src/plugins/chart_expressions/expression_tagcloud/common/index.ts b/src/plugins/chart_expressions/expression_tagcloud/common/index.ts new file mode 100755 index 0000000000000..d8989abcc3d6f --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/common/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './constants'; diff --git a/src/plugins/chart_expressions/expression_tagcloud/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_tagcloud/common/types/expression_functions.ts new file mode 100644 index 0000000000000..b1aba30380b59 --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/common/types/expression_functions.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { PaletteOutput } from '../../../../charts/common'; +import { + Datatable, + ExpressionFunctionDefinition, + ExpressionValueRender, + SerializedFieldFormat, +} from '../../../../expressions'; +import { ExpressionValueVisDimension } from '../../../../visualizations/common'; +import { EXPRESSION_NAME } from '../constants'; + +interface Dimension { + accessor: number; + format: { + id?: string; + params?: SerializedFieldFormat; + }; +} + +interface TagCloudCommonParams { + scale: 'linear' | 'log' | 'square root'; + orientation: 'single' | 'right angled' | 'multiple'; + minFontSize: number; + maxFontSize: number; + showLabel: boolean; +} + +export interface TagCloudVisConfig extends TagCloudCommonParams { + metric: ExpressionValueVisDimension; + bucket?: ExpressionValueVisDimension; +} + +export interface TagCloudVisParams extends TagCloudCommonParams { + palette: PaletteOutput; + metric: Dimension; + bucket?: Dimension; +} + +export interface TagcloudRendererConfig { + visType: typeof EXPRESSION_NAME; + visData: Datatable; + visParams: TagCloudVisParams; + syncColors: boolean; +} + +interface Arguments extends TagCloudVisConfig { + palette: string; +} + +export type ExpressionTagcloudFunction = () => ExpressionFunctionDefinition< + 'tagcloud', + Datatable, + Arguments, + ExpressionValueRender +>; diff --git a/src/plugins/chart_expressions/expression_tagcloud/common/types/expression_renderers.ts b/src/plugins/chart_expressions/expression_tagcloud/common/types/expression_renderers.ts new file mode 100644 index 0000000000000..d426aa061a2ab --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/common/types/expression_renderers.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ChartsPluginSetup } from '../../../../charts/public'; + +export interface TagCloudTypeProps { + palettes: ChartsPluginSetup['palettes']; +} diff --git a/src/plugins/chart_expressions/expression_tagcloud/common/types/index.ts b/src/plugins/chart_expressions/expression_tagcloud/common/types/index.ts new file mode 100644 index 0000000000000..ec934e7affe88 --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/common/types/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +export * from './expression_functions'; +export * from './expression_renderers'; diff --git a/src/plugins/chart_expressions/expression_tagcloud/jest.config.js b/src/plugins/chart_expressions/expression_tagcloud/jest.config.js new file mode 100644 index 0000000000000..c88c150d6f649 --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../../', + roots: ['/src/plugins/chart_expressions/expression_tagcloud'], +}; diff --git a/src/plugins/chart_expressions/expression_tagcloud/kibana.json b/src/plugins/chart_expressions/expression_tagcloud/kibana.json new file mode 100755 index 0000000000000..26d5ef9750e60 --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/kibana.json @@ -0,0 +1,15 @@ +{ + "id": "expressionTagcloud", + "version": "1.0.0", + "kibanaVersion": "kibana", + "server": true, + "ui": true, + "requiredPlugins": ["expressions", "visualizations", "charts", "presentationUtil", "fieldFormats"], + "requiredBundles": ["kibanaUtils"], + "optionalPlugins": [], + "owner": { + "name": "Kibana App", + "githubTeam": "kibana-app" + }, + "description": "Expression Tagcloud plugin adds a `tagcloud` renderer and function to the expression plugin. The renderer will display the `Wordcloud` chart." +} diff --git a/src/plugins/chart_expressions/expression_tagcloud/public/components/index.ts b/src/plugins/chart_expressions/expression_tagcloud/public/components/index.ts new file mode 100644 index 0000000000000..2ebc3d586d903 --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/public/components/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './tagcloud_component'; diff --git a/src/plugins/vis_type_tagcloud/public/components/tag_cloud.scss b/src/plugins/chart_expressions/expression_tagcloud/public/components/tag_cloud.scss similarity index 100% rename from src/plugins/vis_type_tagcloud/public/components/tag_cloud.scss rename to src/plugins/chart_expressions/expression_tagcloud/public/components/tag_cloud.scss diff --git a/src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.test.tsx b/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.test.tsx similarity index 93% rename from src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.test.tsx rename to src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.test.tsx index b4d4e70d5ffe3..542a9c1cd9bf7 100644 --- a/src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.test.tsx +++ b/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.test.tsx @@ -7,12 +7,12 @@ */ import React from 'react'; import { Wordcloud, Settings } from '@elastic/charts'; -import { chartPluginMock } from '../../../charts/public/mocks'; -import type { Datatable } from '../../../expressions/public'; +import { chartPluginMock } from '../../../../charts/public/mocks'; +import type { Datatable } from '../../../../expressions/public'; import { mount } from 'enzyme'; import { findTestSubject } from '@elastic/eui/lib/test'; -import TagCloudChart, { TagCloudChartProps } from './tag_cloud_chart'; -import { TagCloudVisParams } from '../types'; +import TagCloudChart, { TagCloudChartProps } from './tagcloud_component'; +import { TagCloudVisParams } from '../../common/types'; jest.mock('../services', () => ({ getFormatService: jest.fn(() => { diff --git a/src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.tsx b/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.tsx similarity index 93% rename from src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.tsx rename to src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.tsx index b89fe2fa90ede..163a2e8ce38ac 100644 --- a/src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.tsx +++ b/src/plugins/chart_expressions/expression_tagcloud/public/components/tagcloud_component.tsx @@ -11,16 +11,16 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { throttle } from 'lodash'; import { EuiIconTip, EuiResizeObserver } from '@elastic/eui'; import { Chart, Settings, Wordcloud, RenderChangeListener } from '@elastic/charts'; -import type { PaletteRegistry } from '../../../charts/public'; -import type { IInterpreterRenderHandlers } from '../../../expressions/public'; +import type { PaletteRegistry } from '../../../../charts/public'; +import type { IInterpreterRenderHandlers } from '../../../../expressions/public'; import { getFormatService } from '../services'; -import { TagCloudVisRenderValue } from '../tag_cloud_fn'; +import { TagcloudRendererConfig } from '../../common/types'; import './tag_cloud.scss'; const MAX_TAG_COUNT = 200; -export type TagCloudChartProps = TagCloudVisRenderValue & { +export type TagCloudChartProps = TagcloudRendererConfig & { fireEvent: IInterpreterRenderHandlers['event']; renderComplete: IInterpreterRenderHandlers['done']; palettesRegistry: PaletteRegistry; @@ -204,7 +204,7 @@ export const TagCloudChart = ({ color="warning" content={ } @@ -218,7 +218,7 @@ export const TagCloudChart = ({ color="warning" content={ } @@ -231,6 +231,5 @@ export const TagCloudChart = ({ ); }; -// default export required for React.Lazy // eslint-disable-next-line import/no-default-export export { TagCloudChart as default }; diff --git a/src/plugins/chart_expressions/expression_tagcloud/public/expression_renderers/index.ts b/src/plugins/chart_expressions/expression_tagcloud/public/expression_renderers/index.ts new file mode 100644 index 0000000000000..4819430cda791 --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/public/expression_renderers/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { tagcloudRenderer } from './tagcloud_renderer'; diff --git a/src/plugins/chart_expressions/expression_tagcloud/public/expression_renderers/tagcloud_renderer.tsx b/src/plugins/chart_expressions/expression_tagcloud/public/expression_renderers/tagcloud_renderer.tsx new file mode 100644 index 0000000000000..58e177dac6775 --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/public/expression_renderers/tagcloud_renderer.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React, { lazy } from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { I18nProvider } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { ExpressionRenderDefinition } from '../../../../expressions/common'; +import { VisualizationContainer } from '../../../../visualizations/public'; +import { withSuspense } from '../../../../presentation_util/public'; +import { TagcloudRendererConfig } from '../../common/types'; +import { ExpressioTagcloudRendererDependencies } from '../plugin'; +import { EXPRESSION_NAME } from '../../common'; + +export const strings = { + getDisplayName: () => + i18n.translate('expressionTagcloud.renderer.tagcloud.displayName', { + defaultMessage: 'Tag Cloud visualization', + }), + getHelpDescription: () => + i18n.translate('expressionTagcloud.renderer.tagcloud.helpDescription', { + defaultMessage: 'Render a tag cloud', + }), +}; + +const LazyTagcloudComponent = lazy(() => import('../components/tagcloud_component')); +const TagcloudComponent = withSuspense(LazyTagcloudComponent); + +export const tagcloudRenderer: ( + deps: ExpressioTagcloudRendererDependencies +) => ExpressionRenderDefinition = ({ palettes }) => ({ + name: EXPRESSION_NAME, + displayName: strings.getDisplayName(), + help: strings.getHelpDescription(), + reuseDomNode: true, + render: async (domNode, config, handlers) => { + handlers.onDestroy(() => { + unmountComponentAtNode(domNode); + }); + const palettesRegistry = await palettes.getPalettes(); + + render( + + + + + , + domNode + ); + }, +}); diff --git a/src/plugins/chart_expressions/expression_tagcloud/public/index.ts b/src/plugins/chart_expressions/expression_tagcloud/public/index.ts new file mode 100644 index 0000000000000..8d170c49b6633 --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/public/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ExpressionTagcloudPlugin } from './plugin'; + +export type { ExpressionTagcloudPluginSetup, ExpressionTagcloudPluginStart } from './plugin'; + +export function plugin() { + return new ExpressionTagcloudPlugin(); +} diff --git a/src/plugins/chart_expressions/expression_tagcloud/public/plugin.ts b/src/plugins/chart_expressions/expression_tagcloud/public/plugin.ts new file mode 100644 index 0000000000000..7cbc9ac7c6706 --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/public/plugin.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CoreSetup, CoreStart, Plugin } from '../../../../core/public'; +import { ExpressionsStart, ExpressionsSetup } from '../../../expressions/public'; +import { ChartsPluginSetup } from '../../../charts/public'; +import { tagcloudRenderer } from './expression_renderers'; +import { tagcloudFunction } from '../common/expression_functions'; +import { FieldFormatsStart } from '../../../field_formats/public'; +import { setFormatService } from './services'; + +interface SetupDeps { + expressions: ExpressionsSetup; + charts: ChartsPluginSetup; +} + +/** @internal */ +export interface ExpressioTagcloudRendererDependencies { + palettes: ChartsPluginSetup['palettes']; +} + +interface StartDeps { + expression: ExpressionsStart; + fieldFormats: FieldFormatsStart; +} + +export type ExpressionTagcloudPluginSetup = void; +export type ExpressionTagcloudPluginStart = void; + +export class ExpressionTagcloudPlugin + implements + Plugin { + public setup(core: CoreSetup, { expressions, charts }: SetupDeps): ExpressionTagcloudPluginSetup { + const rendererDependencies: ExpressioTagcloudRendererDependencies = { + palettes: charts.palettes, + }; + expressions.registerFunction(tagcloudFunction); + expressions.registerRenderer(tagcloudRenderer(rendererDependencies)); + } + + public start(core: CoreStart, { fieldFormats }: StartDeps): ExpressionTagcloudPluginStart { + setFormatService(fieldFormats); + } + + public stop() {} +} diff --git a/src/plugins/chart_expressions/expression_tagcloud/public/services.ts b/src/plugins/chart_expressions/expression_tagcloud/public/services.ts new file mode 100644 index 0000000000000..541e46a847260 --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/public/services.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { createGetterSetter } from '../../../kibana_utils/public'; +import { FieldFormatsStart } from '../../../field_formats/public'; + +export const [getFormatService, setFormatService] = createGetterSetter( + 'fieldFormats' +); diff --git a/src/plugins/chart_expressions/expression_tagcloud/server/index.ts b/src/plugins/chart_expressions/expression_tagcloud/server/index.ts new file mode 100644 index 0000000000000..c944168271314 --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/server/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ExpressionTagcloudPlugin } from './plugin'; + +export function plugin() { + return new ExpressionTagcloudPlugin(); +} diff --git a/src/plugins/chart_expressions/expression_tagcloud/server/plugin.ts b/src/plugins/chart_expressions/expression_tagcloud/server/plugin.ts new file mode 100644 index 0000000000000..03dd05db25fa7 --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/server/plugin.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CoreSetup, CoreStart, Plugin } from '../../../../core/public'; +import { ExpressionsServerStart, ExpressionsServerSetup } from '../../../expressions/server'; +import { tagcloudFunction } from '../common/expression_functions'; + +interface SetupDeps { + expressions: ExpressionsServerSetup; +} + +interface StartDeps { + expression: ExpressionsServerStart; +} + +export type ExpressionTagcloudPluginSetup = void; +export type ExpressionTagcloudPluginStart = void; + +export class ExpressionTagcloudPlugin + implements + Plugin { + public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionTagcloudPluginSetup { + expressions.registerFunction(tagcloudFunction); + } + + public start(core: CoreStart): ExpressionTagcloudPluginStart {} + + public stop() {} +} diff --git a/src/plugins/chart_expressions/expression_tagcloud/tsconfig.json b/src/plugins/chart_expressions/expression_tagcloud/tsconfig.json new file mode 100644 index 0000000000000..c2d50e4cd4e13 --- /dev/null +++ b/src/plugins/chart_expressions/expression_tagcloud/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + "isolatedModules": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + ], + "references": [ + { "path": "../../../core/tsconfig.json" }, + { "path": "../../presentation_util/tsconfig.json" }, + { "path": "../../expressions/tsconfig.json" }, + { "path": "../../visualizations/tsconfig.json" }, + { "path": "../../charts/tsconfig.json" }, + { "path": "../../field_formats/tsconfig.json" }, + { "path": "../../kibana_utils/tsconfig.json" }, + ] +} diff --git a/src/plugins/vis_type_tagcloud/kibana.json b/src/plugins/vis_type_tagcloud/kibana.json index 1c427600b5de6..b51d5d49cb7b2 100644 --- a/src/plugins/vis_type_tagcloud/kibana.json +++ b/src/plugins/vis_type_tagcloud/kibana.json @@ -4,7 +4,7 @@ "ui": true, "server": true, "requiredPlugins": ["data", "expressions", "visualizations", "charts"], - "requiredBundles": ["kibanaUtils", "kibanaReact", "visDefaultEditor"], + "requiredBundles": ["kibanaReact", "visDefaultEditor"], "owner": { "name": "Kibana App", "githubTeam": "kibana-app" diff --git a/src/plugins/vis_type_tagcloud/public/plugin.ts b/src/plugins/vis_type_tagcloud/public/plugin.ts index b2414762f6e47..06e1c516d9e61 100644 --- a/src/plugins/vis_type_tagcloud/public/plugin.ts +++ b/src/plugins/vis_type_tagcloud/public/plugin.ts @@ -7,20 +7,14 @@ */ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'kibana/public'; -import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; import { VisualizationsSetup } from '../../visualizations/public'; import { ChartsPluginSetup } from '../../charts/public'; -import { createTagCloudFn } from './tag_cloud_fn'; import { getTagCloudVisTypeDefinition } from './tag_cloud_type'; -import { DataPublicPluginStart } from '../../data/public'; -import { setFormatService } from './services'; import { ConfigSchema } from '../config'; -import { getTagCloudVisRenderer } from './tag_cloud_vis_renderer'; /** @internal */ export interface TagCloudPluginSetupDependencies { - expressions: ReturnType; visualizations: VisualizationsSetup; charts: ChartsPluginSetup; } @@ -30,11 +24,6 @@ export interface TagCloudVisDependencies { palettes: ChartsPluginSetup['palettes']; } -/** @internal */ -export interface TagCloudVisPluginStartDependencies { - data: DataPublicPluginStart; -} - /** @internal */ export class TagCloudPlugin implements Plugin { initializerContext: PluginInitializerContext; @@ -43,23 +32,13 @@ export class TagCloudPlugin implements Plugin { this.initializerContext = initializerContext; } - public setup( - core: CoreSetup, - { expressions, visualizations, charts }: TagCloudPluginSetupDependencies - ) { + public setup(core: CoreSetup, { visualizations, charts }: TagCloudPluginSetupDependencies) { const visualizationDependencies: TagCloudVisDependencies = { palettes: charts.palettes, }; - expressions.registerFunction(createTagCloudFn); - expressions.registerRenderer(getTagCloudVisRenderer(visualizationDependencies)); - visualizations.createBaseVisualization( - getTagCloudVisTypeDefinition({ - palettes: charts.palettes, - }) - ); - } - public start(core: CoreStart, { data }: TagCloudVisPluginStartDependencies) { - setFormatService(data.fieldFormats); + visualizations.createBaseVisualization(getTagCloudVisTypeDefinition(visualizationDependencies)); } + + public start(core: CoreStart) {} } diff --git a/src/plugins/vis_type_tagcloud/public/tag_cloud_fn.ts b/src/plugins/vis_type_tagcloud/public/tag_cloud_fn.ts deleted file mode 100644 index bfaf557c6baff..0000000000000 --- a/src/plugins/vis_type_tagcloud/public/tag_cloud_fn.ts +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; - -import { ExpressionFunctionDefinition, Datatable, Render } from '../../expressions/public'; -import { prepareLogTable, Dimension } from '../../visualizations/public'; -import { TagCloudVisParams, TagCloudVisConfig } from './types'; - -const name = 'tagcloud'; - -interface Arguments extends TagCloudVisConfig { - palette: string; -} - -export interface TagCloudVisRenderValue { - visType: typeof name; - visData: Datatable; - visParams: TagCloudVisParams; - syncColors: boolean; -} - -export type TagcloudExpressionFunctionDefinition = ExpressionFunctionDefinition< - typeof name, - Datatable, - Arguments, - Render ->; - -export const createTagCloudFn = (): TagcloudExpressionFunctionDefinition => ({ - name, - type: 'render', - inputTypes: ['datatable'], - help: i18n.translate('visTypeTagCloud.function.help', { - defaultMessage: 'Tagcloud visualization', - }), - args: { - scale: { - types: ['string'], - default: 'linear', - options: ['linear', 'log', 'square root'], - help: i18n.translate('visTypeTagCloud.function.scale.help', { - defaultMessage: 'Scale to determine font size of a word', - }), - }, - orientation: { - types: ['string'], - default: 'single', - options: ['single', 'right angled', 'multiple'], - help: i18n.translate('visTypeTagCloud.function.orientation.help', { - defaultMessage: 'Orientation of words inside tagcloud', - }), - }, - minFontSize: { - types: ['number'], - default: 18, - help: '', - }, - maxFontSize: { - types: ['number'], - default: 72, - help: '', - }, - showLabel: { - types: ['boolean'], - default: true, - help: '', - }, - palette: { - types: ['string'], - help: i18n.translate('visTypeTagCloud.function.paletteHelpText', { - defaultMessage: 'Defines the chart palette name', - }), - default: 'default', - }, - metric: { - types: ['vis_dimension'], - help: i18n.translate('visTypeTagCloud.function.metric.help', { - defaultMessage: 'metric dimension configuration', - }), - required: true, - }, - bucket: { - types: ['vis_dimension'], - help: i18n.translate('visTypeTagCloud.function.bucket.help', { - defaultMessage: 'bucket dimension configuration', - }), - }, - }, - fn(input, args, handlers) { - const visParams = { - scale: args.scale, - orientation: args.orientation, - minFontSize: args.minFontSize, - maxFontSize: args.maxFontSize, - showLabel: args.showLabel, - metric: args.metric, - ...(args.bucket && { - bucket: args.bucket, - }), - palette: { - type: 'palette', - name: args.palette, - }, - } as TagCloudVisParams; - - if (handlers?.inspectorAdapters?.tables) { - const argsTable: Dimension[] = [ - [ - [args.metric], - i18n.translate('visTypeTagCloud.function.dimension.tagSize', { - defaultMessage: 'Tag size', - }), - ], - ]; - if (args.bucket) { - argsTable.push([ - [args.bucket], - i18n.translate('visTypeTagCloud.function.adimension.tags', { - defaultMessage: 'Tags', - }), - ]); - } - const logTable = prepareLogTable(input, argsTable); - handlers.inspectorAdapters.tables.logDatatable('default', logTable); - } - return { - type: 'render', - as: 'tagloud_vis', - value: { - visData: input, - visType: name, - visParams, - syncColors: handlers?.isSyncColorsEnabled?.() ?? false, - }, - }; - }, -}); diff --git a/src/plugins/vis_type_tagcloud/public/tag_cloud_vis_renderer.tsx b/src/plugins/vis_type_tagcloud/public/tag_cloud_vis_renderer.tsx deleted file mode 100644 index 279bfdfffee67..0000000000000 --- a/src/plugins/vis_type_tagcloud/public/tag_cloud_vis_renderer.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { lazy } from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { I18nProvider } from '@kbn/i18n/react'; - -import { VisualizationContainer } from '../../visualizations/public'; -import { ExpressionRenderDefinition } from '../../expressions/common/expression_renderers'; -import { TagCloudVisDependencies } from './plugin'; -import { TagCloudVisRenderValue } from './tag_cloud_fn'; - -const TagCloudChart = lazy(() => import('./components/tag_cloud_chart')); - -export const getTagCloudVisRenderer: ( - deps: TagCloudVisDependencies -) => ExpressionRenderDefinition = ({ palettes }) => ({ - name: 'tagloud_vis', - displayName: 'Tag Cloud visualization', - reuseDomNode: true, - render: async (domNode, config, handlers) => { - handlers.onDestroy(() => { - unmountComponentAtNode(domNode); - }); - const palettesRegistry = await palettes.getPalettes(); - - render( - - - - - , - domNode - ); - }, -}); diff --git a/src/plugins/vis_type_tagcloud/public/to_ast.ts b/src/plugins/vis_type_tagcloud/public/to_ast.ts index 8a2fb4e843973..c8810aa0397ee 100644 --- a/src/plugins/vis_type_tagcloud/public/to_ast.ts +++ b/src/plugins/vis_type_tagcloud/public/to_ast.ts @@ -12,7 +12,6 @@ import { } from '../../data/public'; import { buildExpression, buildExpressionFunction } from '../../expressions/public'; import { getVisSchemas, SchemaConfig, VisToExpressionAst } from '../../visualizations/public'; -import { TagcloudExpressionFunctionDefinition } from './tag_cloud_fn'; import { TagCloudVisParams } from './types'; const prepareDimension = (params: SchemaConfig) => { @@ -41,7 +40,7 @@ export const toExpressionAst: VisToExpressionAst = (vis, para const schemas = getVisSchemas(vis, params); const { scale, orientation, minFontSize, maxFontSize, showLabel, palette } = vis.params; - const tagcloud = buildExpressionFunction('tagcloud', { + const tagcloud = buildExpressionFunction('tagcloud', { scale, orientation, minFontSize, diff --git a/src/plugins/vis_type_tagcloud/public/types.ts b/src/plugins/vis_type_tagcloud/public/types.ts index 7105476670693..d855ae5ab65c6 100644 --- a/src/plugins/vis_type_tagcloud/public/types.ts +++ b/src/plugins/vis_type_tagcloud/public/types.ts @@ -7,7 +7,6 @@ */ import type { ChartsPluginSetup, PaletteOutput } from '../../charts/public'; import type { SerializedFieldFormat } from '../../expressions/public'; -import { ExpressionValueVisDimension } from '../../visualizations/public'; interface Dimension { accessor: number; @@ -25,11 +24,6 @@ interface TagCloudCommonParams { showLabel: boolean; } -export interface TagCloudVisConfig extends TagCloudCommonParams { - metric: ExpressionValueVisDimension; - bucket?: ExpressionValueVisDimension; -} - export interface TagCloudVisParams extends TagCloudCommonParams { palette: PaletteOutput; metric: Dimension; diff --git a/src/plugins/vis_type_tagcloud/tsconfig.json b/src/plugins/vis_type_tagcloud/tsconfig.json index 021237dd7ad5b..043eed06c6bcb 100644 --- a/src/plugins/vis_type_tagcloud/tsconfig.json +++ b/src/plugins/vis_type_tagcloud/tsconfig.json @@ -17,7 +17,6 @@ { "path": "../expressions/tsconfig.json" }, { "path": "../visualizations/tsconfig.json" }, { "path": "../charts/tsconfig.json" }, - { "path": "../kibana_utils/tsconfig.json" }, { "path": "../kibana_react/tsconfig.json" }, { "path": "../vis_default_editor/tsconfig.json" }, ] diff --git a/test/interpreter_functional/snapshots/baseline/partial_test_1.json b/test/interpreter_functional/snapshots/baseline/partial_test_1.json index e0b62688d0662..082c7b934c17c 100644 --- a/test/interpreter_functional/snapshots/baseline/partial_test_1.json +++ b/test/interpreter_functional/snapshots/baseline/partial_test_1.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json index d85444f5d3b6b..9813a3ca036a1 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json b/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json index 2c81c9447b826..bef1b10120fe5 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_invalid_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_invalid_data.json index 687b669b18e61..3e594380588dc 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_invalid_data.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_invalid_data.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[],"meta":{},"rows":[],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[],"meta":{},"rows":[],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json index b49953f9a023b..bea6dad294e01 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_options.json b/test/interpreter_functional/snapshots/baseline/tagcloud_options.json index fc7e289dfbd3a..c45b063fdb542 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_options.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_options.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","palette":{"name":"default","type":"palette"},"scale":"log","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","palette":{"name":"default","type":"palette"},"scale":"log","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/partial_test_1.json b/test/interpreter_functional/snapshots/session/partial_test_1.json index e0b62688d0662..082c7b934c17c 100644 --- a/test/interpreter_functional/snapshots/session/partial_test_1.json +++ b/test/interpreter_functional/snapshots/session/partial_test_1.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_all_data.json b/test/interpreter_functional/snapshots/session/tagcloud_all_data.json index d85444f5d3b6b..9813a3ca036a1 100644 --- a/test/interpreter_functional/snapshots/session/tagcloud_all_data.json +++ b/test/interpreter_functional/snapshots/session/tagcloud_all_data.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json b/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json index 2c81c9447b826..bef1b10120fe5 100644 --- a/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json +++ b/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_invalid_data.json b/test/interpreter_functional/snapshots/session/tagcloud_invalid_data.json index 687b669b18e61..3e594380588dc 100644 --- a/test/interpreter_functional/snapshots/session/tagcloud_invalid_data.json +++ b/test/interpreter_functional/snapshots/session/tagcloud_invalid_data.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[],"meta":{},"rows":[],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[],"meta":{},"rows":[],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json b/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json index b49953f9a023b..bea6dad294e01 100644 --- a/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json +++ b/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","palette":{"name":"default","type":"palette"},"scale":"linear","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_options.json b/test/interpreter_functional/snapshots/session/tagcloud_options.json index fc7e289dfbd3a..c45b063fdb542 100644 --- a/test/interpreter_functional/snapshots/session/tagcloud_options.json +++ b/test/interpreter_functional/snapshots/session/tagcloud_options.json @@ -1 +1 @@ -{"as":"tagloud_vis","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","palette":{"name":"default","type":"palette"},"scale":"log","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file +{"as":"tagcloud","type":"render","value":{"syncColors":false,"visData":{"columns":[{"id":"col-0-2","meta":{"field":"response.raw","index":"logstash-*","params":{"id":"terms","params":{"id":"string","missingBucketLabel":"Missing","otherBucketLabel":"Other"}},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"2","indexPatternId":"logstash-*","params":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"schema":"segment","type":"terms"},"type":"string"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"field":null,"index":"logstash-*","params":{"id":"number"},"source":"esaggs","sourceParams":{"appliedTimeRange":null,"enabled":true,"id":"1","indexPatternId":"logstash-*","params":{},"schema":"metric","type":"count"},"type":"number"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"datatable"},"visParams":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","palette":{"name":"default","type":"palette"},"scale":"log","showLabel":true},"visType":"tagcloud"}} \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index b5b7e35c4a9b4..84806cd6acfec 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4230,14 +4230,14 @@ "visTypeTable.vis.controls.formattedCSVButtonLabel": "フォーマット済み", "visTypeTable.vis.controls.rawCSVButtonLabel": "未加工", "visTypeTable.vis.noResultsFoundTitle": "結果が見つかりませんでした", - "visTypeTagCloud.feedbackMessage.tooSmallContainerDescription": "コンテナーが小さすぎてクラウド全体を表示できません。タグが切り取られたか省略されている可能性があります。", - "visTypeTagCloud.feedbackMessage.truncatedTagsDescription": "描写時間が長くなるのを防ぐため、タグの数が切り捨てられています。", - "visTypeTagCloud.function.bucket.help": "バケットディメンションの構成です。", - "visTypeTagCloud.function.help": "タグクラウドのビジュアライゼーションです。", - "visTypeTagCloud.function.metric.help": "メトリックディメンションの構成です。", - "visTypeTagCloud.function.orientation.help": "タグクラウド内の単語の方向です。", - "visTypeTagCloud.function.paletteHelpText": "グラフパレット名を定義します", - "visTypeTagCloud.function.scale.help": "単語のフォントサイズを決定するスケールです", + "expressionTagcloud.feedbackMessage.tooSmallContainerDescription": "コンテナーが小さすぎてクラウド全体を表示できません。タグが切り取られたか省略されている可能性があります。", + "expressionTagcloud.feedbackMessage.truncatedTagsDescription": "描写時間が長くなるのを防ぐため、タグの数が切り捨てられています。", + "expressionTagcloud.functions.tagcloud.args.bucketHelpText": "バケットディメンションの構成です。", + "expressionTagcloud.functions.tagcloudHelpText": "タグクラウドのビジュアライゼーションです。", + "expressionTagcloud.functions.tagcloud.args.metricHelpText": "メトリックディメンションの構成です。", + "expressionTagcloud.functions.tagcloud.args.orientationHelpText": "タグクラウド内の単語の方向です。", + "expressionTagcloud.functions.tagcloud.args.paletteHelpText": "グラフパレット名を定義します", + "expressionTagcloud.functions.tagcloud.args.scaleHelpText": "単語のフォントサイズを決定するスケールです", "visTypeTagCloud.orientations.multipleText": "複数", "visTypeTagCloud.orientations.rightAngledText": "直角", "visTypeTagCloud.orientations.singleText": "単一", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 1a21d16c80f03..8756484e8bf4f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4250,14 +4250,14 @@ "visTypeTable.vis.controls.formattedCSVButtonLabel": "格式化", "visTypeTable.vis.controls.rawCSVButtonLabel": "原始", "visTypeTable.vis.noResultsFoundTitle": "找不到结果", - "visTypeTagCloud.feedbackMessage.tooSmallContainerDescription": "容器太小,无法显示整个云。标签可能被裁剪或省略。", - "visTypeTagCloud.feedbackMessage.truncatedTagsDescription": "标签数量已截断,以避免绘制时间过长。", - "visTypeTagCloud.function.bucket.help": "存储桶维度配置", - "visTypeTagCloud.function.help": "标签云图可视化", - "visTypeTagCloud.function.metric.help": "指标维度配置", - "visTypeTagCloud.function.orientation.help": "标签云图内的字方向", - "visTypeTagCloud.function.paletteHelpText": "定义图表调色板名称", - "visTypeTagCloud.function.scale.help": "缩放以确定字体大小", + "expressionTagcloud.feedbackMessage.tooSmallContainerDescription": "容器太小,无法显示整个云。标签可能被裁剪或省略。", + "expressionTagcloud.feedbackMessage.truncatedTagsDescription": "标签数量已截断,以避免绘制时间过长。", + "expressionTagcloud.functions.tagcloud.args.bucketHelpText": "存储桶维度配置", + "expressionTagcloud.functions.tagcloudHelpText": "标签云图可视化", + "expressionTagcloud.functions.tagcloud.args.metricHelpText": "指标维度配置", + "expressionTagcloud.functions.tagcloud.args.orientationHelpText": "标签云图内的字方向", + "expressionTagcloud.functions.tagcloud.args.paletteHelpText": "定义图表调色板名称", + "expressionTagcloud.functions.tagcloud.args.scaleHelpText": "缩放以确定字体大小", "visTypeTagCloud.orientations.multipleText": "多个", "visTypeTagCloud.orientations.rightAngledText": "直角", "visTypeTagCloud.orientations.singleText": "单个", From d1a8bbd22624cb4ad08621ad20bb937a7388dfc0 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Sun, 22 Aug 2021 23:56:13 +0100 Subject: [PATCH 13/27] skip flaky suite (#106492) --- .../security_and_spaces/tests/alerting/alerts.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts index 7e819fb15ea1f..93535826d14e7 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts @@ -39,7 +39,8 @@ export default function alertTests({ getService }: FtrProviderContext) { const esTestIndexTool = new ESTestIndexTool(es, retry); const taskManagerUtils = new TaskManagerUtils(es, retry); - describe('alerts', () => { + // FLAKY: https://github.com/elastic/kibana/issues/106492 + describe.skip('alerts', () => { const authorizationIndex = '.kibana-test-authorization'; const objectRemover = new ObjectRemover(supertest); From e8e26395fd212d41a4c4ac28b3652959a4679256 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 23 Aug 2021 15:18:00 +0100 Subject: [PATCH 14/27] skip failing es promotion suite (#109660) --- .../tests/correlations/errors_failed_transactions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/apm_api_integration/tests/correlations/errors_failed_transactions.ts b/x-pack/test/apm_api_integration/tests/correlations/errors_failed_transactions.ts index 12d71530ecce1..054ccbfb4996e 100644 --- a/x-pack/test/apm_api_integration/tests/correlations/errors_failed_transactions.ts +++ b/x-pack/test/apm_api_integration/tests/correlations/errors_failed_transactions.ts @@ -40,7 +40,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { } ); - registry.when( + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/109660 + registry.when.skip( 'correlations errors failed transactions with data and default args', { config: 'trial', archives: ['apm_8.0.0'] }, () => { From ff3bd1dcacd91647b18492b667b5e99064c77975 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 23 Aug 2021 10:28:44 -0400 Subject: [PATCH 15/27] [RAC] Fix hover on alert status column (#109273) (#109632) * Fix alert status column hover Co-authored-by: Kerry Gallagher <471693+Kerry350@users.noreply.github.com> --- .../public/pages/alerts/render_cell_value.tsx | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/alerts/render_cell_value.tsx b/x-pack/plugins/observability/public/pages/alerts/render_cell_value.tsx index c85ea0b1086fa..691bfc984b9cb 100644 --- a/x-pack/plugins/observability/public/pages/alerts/render_cell_value.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/render_cell_value.tsx @@ -6,7 +6,7 @@ */ import { EuiLink, EuiHealth, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useEffect } from 'react'; +import React from 'react'; /** * We need to produce types and code transpilation at different folders during the build of the package. * We have types and code at different imports because we don't want to import the whole package in the resulting webpack bundle for the plugin. @@ -77,16 +77,6 @@ export const getRenderCellValue = ({ fieldName: columnId, })?.reduce((x) => x[0]); - useEffect(() => { - if (columnId === ALERT_STATUS) { - setCellProps({ - style: { - textAlign: 'center', - }, - }); - } - }, [columnId, setCellProps]); - const theme = useTheme(); switch (columnId) { From e27007f81cc3275df53f7a6d2aedf074eea4b44a Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 23 Aug 2021 10:45:25 -0400 Subject: [PATCH 16/27] [Fleet] Fix Fleet settings and HostInput error handling (#109418) (#109636) * fix: HostInput error placement onChange * fix: check for duplicates in hosts * fix: validate all sections * fix: error swapping * fix: yaml placeholder position and styling * test: add unit tests for delete behaviour * tidy: use generic for reduce typing Co-authored-by: Mark Hopkin --- .../settings_flyout/hosts_input.test.tsx | 70 ++++++++++++++++++- .../settings_flyout/hosts_input.tsx | 47 +++++++------ .../components/settings_flyout/index.tsx | 48 +++++++++++-- 3 files changed, 135 insertions(+), 30 deletions(-) diff --git a/x-pack/plugins/fleet/public/components/settings_flyout/hosts_input.test.tsx b/x-pack/plugins/fleet/public/components/settings_flyout/hosts_input.test.tsx index bd475acbb4feb..01ec166b74afc 100644 --- a/x-pack/plugins/fleet/public/components/settings_flyout/hosts_input.test.tsx +++ b/x-pack/plugins/fleet/public/components/settings_flyout/hosts_input.test.tsx @@ -12,13 +12,17 @@ import { createFleetTestRendererMock } from '../../mock'; import { HostsInput } from './hosts_input'; -function renderInput(value = ['http://host1.com']) { +function renderInput( + value = ['http://host1.com'], + errors: Array<{ message: string; index?: number }> = [], + mockOnChange: (...args: any[]) => void = jest.fn() +) { const renderer = createFleetTestRendererMock(); - const mockOnChange = jest.fn(); const utils = renderer.render( { fireEvent.change(inputEl, { target: { value: 'http://newhost.com' } }); expect(mockOnChange).toHaveBeenCalledWith(['http://newhost.com']); }); + +test('Should display single indexed error message', async () => { + const { utils } = renderInput(['bad host'], [{ message: 'Invalid URL', index: 0 }]); + const inputEl = await utils.findByText('Invalid URL'); + expect(inputEl).toBeDefined(); +}); + +test('Should display errors in order', async () => { + const { utils } = renderInput( + ['bad host 1', 'bad host 2', 'bad host 3'], + [ + { message: 'Error 1', index: 0 }, + { message: 'Error 2', index: 1 }, + { message: 'Error 3', index: 2 }, + ] + ); + await act(async () => { + const errors = await utils.queryAllByText(/Error [1-3]/); + expect(errors[0]).toHaveTextContent('Error 1'); + expect(errors[1]).toHaveTextContent('Error 2'); + expect(errors[2]).toHaveTextContent('Error 3'); + }); +}); + +test('Should remove error when item deleted', async () => { + const mockOnChange = jest.fn(); + const errors = [ + { message: 'Error 1', index: 0 }, + { message: 'Error 2', index: 1 }, + { message: 'Error 3', index: 2 }, + ]; + + const { utils } = renderInput(['bad host 1', 'bad host 2', 'bad host 3'], errors, mockOnChange); + + mockOnChange.mockImplementation((newValue) => { + utils.rerender( + + ); + }); + + await act(async () => { + const deleteRowButtons = await utils.container.querySelectorAll('[aria-label="Delete host"]'); + if (deleteRowButtons.length !== 3) { + throw new Error('Delete host buttons not found'); + } + + fireEvent.click(deleteRowButtons[1]); + expect(mockOnChange).toHaveBeenCalled(); + + const renderedErrors = await utils.queryAllByText(/Error [1-3]/); + expect(renderedErrors).toHaveLength(2); + expect(renderedErrors[0]).toHaveTextContent('Error 1'); + expect(renderedErrors[1]).toHaveTextContent('Error 3'); + }); +}); diff --git a/x-pack/plugins/fleet/public/components/settings_flyout/hosts_input.tsx b/x-pack/plugins/fleet/public/components/settings_flyout/hosts_input.tsx index c99cad93b7ec6..49cff905d167f 100644 --- a/x-pack/plugins/fleet/public/components/settings_flyout/hosts_input.tsx +++ b/x-pack/plugins/fleet/public/components/settings_flyout/hosts_input.tsx @@ -158,11 +158,31 @@ export const HostsInput: FunctionComponent = ({ [value, onChange] ); + const indexedErrors = useMemo(() => { + if (!errors) { + return []; + } + return errors.reduce((acc, err) => { + if (err.index === undefined) { + return acc; + } + + if (!acc[err.index]) { + acc[err.index] = []; + } + + acc[err.index].push(err.message); + + return acc; + }, []); + }, [errors]); + const onDelete = useCallback( (idx: number) => { + indexedErrors.splice(idx, 1); onChange([...value.slice(0, idx), ...value.slice(idx + 1)]); }, - [value, onChange] + [value, onChange, indexedErrors] ); const addRowHandler = useCallback(() => { @@ -174,36 +194,19 @@ export const HostsInput: FunctionComponent = ({ ({ source, destination }) => { if (source && destination) { const items = euiDragDropReorder(value, source.index, destination.index); - + const sourceErrors = indexedErrors[source.index]; + indexedErrors.splice(source.index, 1); + indexedErrors.splice(destination.index, 0, sourceErrors); onChange(items); } }, - [value, onChange] + [value, onChange, indexedErrors] ); const globalErrors = useMemo(() => { return errors && errors.filter((err) => err.index === undefined).map(({ message }) => message); }, [errors]); - const indexedErrors = useMemo(() => { - if (!errors) { - return []; - } - return errors.reduce((acc, err) => { - if (err.index === undefined) { - return acc; - } - - if (!acc[err.index]) { - acc[err.index] = []; - } - - acc[err.index].push(err.message); - - return acc; - }, [] as string[][]); - }, [errors]); - const isSortable = rows.length > 1; return ( diff --git a/x-pack/plugins/fleet/public/components/settings_flyout/index.tsx b/x-pack/plugins/fleet/public/components/settings_flyout/index.tsx index 3d3a4dda60632..e42733bbd2da0 100644 --- a/x-pack/plugins/fleet/public/components/settings_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/components/settings_flyout/index.tsx @@ -58,9 +58,11 @@ const CodeEditorPlaceholder = styled(EuiTextColor).attrs((props) => ({ }))` position: absolute; top: 0; - right: 0; + left: 0; // Matches monaco editor font-family: Menlo, Monaco, 'Courier New', monospace; + font-size: 12px; + line-height: 21px; pointer-events: none; `; @@ -102,6 +104,7 @@ function useSettingsForm(outputId: string | undefined, onSuccess: () => void) { } const res: Array<{ message: string; index: number }> = []; + const hostIndexes: { [key: string]: number[] } = {}; value.forEach((val, idx) => { if (!val.match(URL_REGEX)) { res.push({ @@ -111,7 +114,23 @@ function useSettingsForm(outputId: string | undefined, onSuccess: () => void) { index: idx, }); } + const curIndexes = hostIndexes[val] || []; + hostIndexes[val] = [...curIndexes, idx]; }); + + Object.values(hostIndexes) + .filter(({ length }) => length > 1) + .forEach((indexes) => { + indexes.forEach((index) => + res.push({ + message: i18n.translate('xpack.fleet.settings.fleetServerHostsDuplicateError', { + defaultMessage: 'Duplicate URL', + }), + index, + }) + ); + }); + if (res.length) { return res; } @@ -132,6 +151,7 @@ function useSettingsForm(outputId: string | undefined, onSuccess: () => void) { const elasticsearchUrlInput = useComboInput('esHostsComboxBox', [], (value) => { const res: Array<{ message: string; index: number }> = []; + const urlIndexes: { [key: string]: number[] } = {}; value.forEach((val, idx) => { if (!val.match(URL_REGEX)) { res.push({ @@ -141,7 +161,23 @@ function useSettingsForm(outputId: string | undefined, onSuccess: () => void) { index: idx, }); } + const curIndexes = urlIndexes[val] || []; + urlIndexes[val] = [...curIndexes, idx]; }); + + Object.values(urlIndexes) + .filter(({ length }) => length > 1) + .forEach((indexes) => { + indexes.forEach((index) => + res.push({ + message: i18n.translate('xpack.fleet.settings.elasticHostDuplicateError', { + defaultMessage: 'Duplicate URL', + }), + index, + }) + ); + }); + if (res.length) { return res; } @@ -162,11 +198,11 @@ function useSettingsForm(outputId: string | undefined, onSuccess: () => void) { }); const validate = useCallback(() => { - if ( - !fleetServerHostsInput.validate() || - !elasticsearchUrlInput.validate() || - !additionalYamlConfigInput.validate() - ) { + const fleetServerHostsValid = fleetServerHostsInput.validate(); + const elasticsearchUrlsValid = elasticsearchUrlInput.validate(); + const additionalYamlConfigValid = additionalYamlConfigInput.validate(); + + if (!fleetServerHostsValid || !elasticsearchUrlsValid || !additionalYamlConfigValid) { return false; } From 377760f0603dd9ec58f39073ffeb487fd55ed350 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 23 Aug 2021 11:19:57 -0400 Subject: [PATCH 17/27] [Discover] Fix keeping error state after invalid KQL query (#109514) (#109643) Co-authored-by: Matthias Wilhelm --- .../use_saved_search_messages.test.ts | 79 +++++++++++++++++++ .../services/use_saved_search_messages.ts | 1 + 2 files changed, 80 insertions(+) create mode 100644 src/plugins/discover/public/application/apps/main/services/use_saved_search_messages.test.ts diff --git a/src/plugins/discover/public/application/apps/main/services/use_saved_search_messages.test.ts b/src/plugins/discover/public/application/apps/main/services/use_saved_search_messages.test.ts new file mode 100644 index 0000000000000..9810436aebd90 --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/services/use_saved_search_messages.test.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { + sendCompleteMsg, + sendErrorMsg, + sendLoadingMsg, + sendPartialMsg, +} from './use_saved_search_messages'; +import { FetchStatus } from '../../../types'; +import { BehaviorSubject } from 'rxjs'; +import { DataMainMsg } from './use_saved_search'; + +describe('test useSavedSearch message generators', () => { + test('sendCompleteMsg', async (done) => { + const main$ = new BehaviorSubject({ fetchStatus: FetchStatus.LOADING }); + main$.subscribe((value) => { + if (value.fetchStatus !== FetchStatus.LOADING) { + expect(value.fetchStatus).toBe(FetchStatus.COMPLETE); + expect(value.foundDocuments).toBe(true); + expect(value.error).toBe(undefined); + done(); + } + }); + sendCompleteMsg(main$, true); + }); + test('sendPartialMessage', async (done) => { + const main$ = new BehaviorSubject({ fetchStatus: FetchStatus.LOADING }); + main$.subscribe((value) => { + if (value.fetchStatus !== FetchStatus.LOADING) { + expect(value.fetchStatus).toBe(FetchStatus.PARTIAL); + done(); + } + }); + sendPartialMsg(main$); + }); + test('sendLoadingMsg', async (done) => { + const main$ = new BehaviorSubject({ fetchStatus: FetchStatus.COMPLETE }); + main$.subscribe((value) => { + if (value.fetchStatus !== FetchStatus.COMPLETE) { + expect(value.fetchStatus).toBe(FetchStatus.LOADING); + done(); + } + }); + sendLoadingMsg(main$); + }); + test('sendErrorMsg', async (done) => { + const main$ = new BehaviorSubject({ fetchStatus: FetchStatus.PARTIAL }); + main$.subscribe((value) => { + if (value.fetchStatus === FetchStatus.ERROR) { + expect(value.fetchStatus).toBe(FetchStatus.ERROR); + expect(value.error).toBeInstanceOf(Error); + done(); + } + }); + sendErrorMsg(main$, new Error('Pls help!')); + }); + + test('sendCompleteMsg cleaning error state message', async (done) => { + const initialState = { + fetchStatus: FetchStatus.ERROR, + error: new Error('Oh noes!'), + }; + const main$ = new BehaviorSubject(initialState); + main$.subscribe((value) => { + if (value.fetchStatus === FetchStatus.COMPLETE) { + const newState = { ...initialState, ...value }; + expect(newState.fetchStatus).toBe(FetchStatus.COMPLETE); + expect(newState.error).toBeUndefined(); + done(); + } + }); + sendCompleteMsg(main$, false); + }); +}); diff --git a/src/plugins/discover/public/application/apps/main/services/use_saved_search_messages.ts b/src/plugins/discover/public/application/apps/main/services/use_saved_search_messages.ts index b42d699f344cc..ff72a69e65fa8 100644 --- a/src/plugins/discover/public/application/apps/main/services/use_saved_search_messages.ts +++ b/src/plugins/discover/public/application/apps/main/services/use_saved_search_messages.ts @@ -27,6 +27,7 @@ export function sendCompleteMsg(main$: DataMain$, foundDocuments = true) { main$.next({ fetchStatus: FetchStatus.COMPLETE, foundDocuments, + error: undefined, }); } From 6fe650d30370ff600558cf359d32fdfea2c47f23 Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Mon, 23 Aug 2021 17:22:31 +0200 Subject: [PATCH 18/27] Cumulative set of the preboot stage adjustments (#108514) (#109646) # Conflicts: # src/plugins/interactive_setup/server/elasticsearch_service.test.ts # src/plugins/interactive_setup/server/elasticsearch_service.ts # src/plugins/interactive_setup/server/routes/enroll.test.ts # src/plugins/interactive_setup/server/routes/enroll.ts --- ...-plugin-core-server.elasticsearchclientconfig.md | 1 + ...-core-server.httpservicepreboot.getserverinfo.md | 13 +++++++++++++ .../kibana-plugin-core-server.httpservicepreboot.md | 1 + src/core/server/core_app/core_app.test.ts | 2 +- src/core/server/core_app/core_app.ts | 2 +- .../elasticsearch/client/client_config.test.ts | 6 ++++++ .../server/elasticsearch/client/client_config.ts | 5 +++++ src/core/server/http/http_service.mock.ts | 2 ++ src/core/server/http/http_service.test.ts | 2 ++ src/core/server/http/http_service.ts | 1 + src/core/server/http/types.ts | 6 ++++++ src/core/server/plugins/plugin_context.ts | 1 + src/core/server/server.api.md | 2 ++ 13 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-core-server.httpservicepreboot.getserverinfo.md diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchclientconfig.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchclientconfig.md index 208e0e0175d71..0084b0b50c869 100644 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchclientconfig.md +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchclientconfig.md @@ -14,5 +14,6 @@ export declare type ElasticsearchClientConfig = Pick; keepAlive?: boolean; + caFingerprint?: ClientOptions['caFingerprint']; }; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.httpservicepreboot.getserverinfo.md b/docs/development/core/server/kibana-plugin-core-server.httpservicepreboot.getserverinfo.md new file mode 100644 index 0000000000000..0c9636b8eb634 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.httpservicepreboot.getserverinfo.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [HttpServicePreboot](./kibana-plugin-core-server.httpservicepreboot.md) > [getServerInfo](./kibana-plugin-core-server.httpservicepreboot.getserverinfo.md) + +## HttpServicePreboot.getServerInfo property + +Provides common [information](./kibana-plugin-core-server.httpserverinfo.md) about the running preboot http server. + +Signature: + +```typescript +getServerInfo: () => HttpServerInfo; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.httpservicepreboot.md b/docs/development/core/server/kibana-plugin-core-server.httpservicepreboot.md index b4adf454a480f..ab0fc365fc651 100644 --- a/docs/development/core/server/kibana-plugin-core-server.httpservicepreboot.md +++ b/docs/development/core/server/kibana-plugin-core-server.httpservicepreboot.md @@ -73,6 +73,7 @@ httpPreboot.registerRoutes('my-plugin', (router) => { | Property | Type | Description | | --- | --- | --- | | [basePath](./kibana-plugin-core-server.httpservicepreboot.basepath.md) | IBasePath | Access or manipulate the Kibana base path See [IBasePath](./kibana-plugin-core-server.ibasepath.md). | +| [getServerInfo](./kibana-plugin-core-server.httpservicepreboot.getserverinfo.md) | () => HttpServerInfo | Provides common [information](./kibana-plugin-core-server.httpserverinfo.md) about the running preboot http server. | ## Methods diff --git a/src/core/server/core_app/core_app.test.ts b/src/core/server/core_app/core_app.test.ts index f6a9b653ec034..e5c3a592a72c7 100644 --- a/src/core/server/core_app/core_app.test.ts +++ b/src/core/server/core_app/core_app.test.ts @@ -137,7 +137,7 @@ describe('CoreApp', () => { mockResponseFactory ); - expect(mockResponseFactory.renderAnonymousCoreApp).toHaveBeenCalled(); + expect(mockResponseFactory.renderCoreApp).toHaveBeenCalled(); }); }); diff --git a/src/core/server/core_app/core_app.ts b/src/core/server/core_app/core_app.ts index 35a7c57b67610..23ad78ca46d45 100644 --- a/src/core/server/core_app/core_app.ts +++ b/src/core/server/core_app/core_app.ts @@ -64,7 +64,7 @@ export class CoreApp { httpResources: corePreboot.httpResources.createRegistrar(router), router, uiPlugins, - onResourceNotFound: (res) => res.renderAnonymousCoreApp(), + onResourceNotFound: (res) => res.renderCoreApp(), }); }); } diff --git a/src/core/server/elasticsearch/client/client_config.test.ts b/src/core/server/elasticsearch/client/client_config.test.ts index 7e16339b40235..7956bcc64ea2f 100644 --- a/src/core/server/elasticsearch/client/client_config.test.ts +++ b/src/core/server/elasticsearch/client/client_config.test.ts @@ -163,6 +163,12 @@ describe('parseClientOptions', () => { ] `); }); + + it('`caFingerprint` option', () => { + const options = parseClientOptions(createConfig({ caFingerprint: 'ab:cd:ef' }), false); + + expect(options.caFingerprint).toBe('ab:cd:ef'); + }); }); describe('authorization', () => { diff --git a/src/core/server/elasticsearch/client/client_config.ts b/src/core/server/elasticsearch/client/client_config.ts index bbbb1ac247b3b..27d6f877a5572 100644 --- a/src/core/server/elasticsearch/client/client_config.ts +++ b/src/core/server/elasticsearch/client/client_config.ts @@ -35,6 +35,7 @@ export type ElasticsearchClientConfig = Pick< requestTimeout?: ElasticsearchConfig['requestTimeout'] | ClientOptions['requestTimeout']; ssl?: Partial; keepAlive?: boolean; + caFingerprint?: ClientOptions['caFingerprint']; }; /** @@ -96,6 +97,10 @@ export function parseClientOptions( ); } + if (config.caFingerprint != null) { + clientOptions.caFingerprint = config.caFingerprint; + } + return clientOptions; } diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index ef5e151083780..4cb1bc9867d2c 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -88,6 +88,7 @@ const createInternalPrebootContractMock = () => { csp: CspConfig.DEFAULT, externalUrl: ExternalUrlConfig.DEFAULT, auth: createAuthMock(), + getServerInfo: jest.fn(), }; return mock; }; @@ -98,6 +99,7 @@ const createPrebootContractMock = () => { const mock: HttpServicePrebootMock = { registerRoutes: internalMock.registerRoutes, basePath: createBasePathMock(), + getServerInfo: jest.fn(), }; return mock; diff --git a/src/core/server/http/http_service.test.ts b/src/core/server/http/http_service.test.ts index 8d29e3221a2ca..4955d19668580 100644 --- a/src/core/server/http/http_service.test.ts +++ b/src/core/server/http/http_service.test.ts @@ -379,6 +379,7 @@ test('returns `preboot` http server contract on preboot', async () => { auth: Symbol('auth'), basePath: Symbol('basePath'), csp: Symbol('csp'), + getServerInfo: jest.fn(), }; mockHttpServer.mockImplementation(() => ({ @@ -397,6 +398,7 @@ test('returns `preboot` http server contract on preboot', async () => { registerRouteHandlerContext: expect.any(Function), registerRoutes: expect.any(Function), registerStaticDir: expect.any(Function), + getServerInfo: expect.any(Function), }); }); diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index 4b9e45e271be2..538a4c065e997 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -128,6 +128,7 @@ export class HttpService prebootSetup.registerRouterAfterListening(router); }, + getServerInfo: prebootSetup.getServerInfo, }; return this.internalPreboot; diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index 7353f48b47194..89d0d72017082 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -142,6 +142,11 @@ export interface HttpServicePreboot { * See {@link IBasePath}. */ basePath: IBasePath; + + /** + * Provides common {@link HttpServerInfo | information} about the running preboot http server. + */ + getServerInfo: () => HttpServerInfo; } /** @internal */ @@ -155,6 +160,7 @@ export interface InternalHttpServicePreboot | 'registerStaticDir' | 'registerRouteHandlerContext' | 'server' + | 'getServerInfo' > { registerRoutes(path: string, callback: (router: IRouter) => void): void; } diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index b972c6078ca2b..cbefdae525180 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -115,6 +115,7 @@ export function createPluginPrebootSetupContext( http: { registerRoutes: deps.http.registerRoutes, basePath: deps.http.basePath, + getServerInfo: deps.http.getServerInfo, }, preboot: { isSetupOnHold: deps.preboot.isSetupOnHold, diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 1bd59dfc7fdb1..67b08f4c0d9b7 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -807,6 +807,7 @@ export type ElasticsearchClientConfig = Pick; keepAlive?: boolean; + caFingerprint?: ClientOptions['caFingerprint']; }; // @public @@ -1003,6 +1004,7 @@ export interface HttpServerInfo { // @public export interface HttpServicePreboot { basePath: IBasePath; + getServerInfo: () => HttpServerInfo; registerRoutes(path: string, callback: (router: IRouter) => void): void; } From 4981a8bb5807bbc03b46c4654851f90f9a9c9260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ester=20Mart=C3=AD=20Vilaseca?= Date: Mon, 23 Aug 2021 17:55:29 +0200 Subject: [PATCH 19/27] [Stack Monitoring] Add initial react app (#109218) (#109652) * Add feature toggle * Add basic react app with router and simple loading page * Add title hook * Add loading page with page template and clusters hook * fix types * fix tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/application/hooks/use_clusters.ts | 65 +++++++++++++++++++ .../public/application/hooks/use_title.ts | 24 +++++++ .../monitoring/public/application/index.tsx | 61 +++++++++++++++++ .../public/application/pages/loading_page.tsx | 41 ++++++++++++ .../application/pages/page_template.tsx | 20 ++++++ .../monitoring/public/components/index.d.ts | 8 +++ x-pack/plugins/monitoring/public/plugin.ts | 40 +++++++----- .../plugins/monitoring/server/config.test.ts | 1 + x-pack/plugins/monitoring/server/config.ts | 1 + 9 files changed, 245 insertions(+), 16 deletions(-) create mode 100644 x-pack/plugins/monitoring/public/application/hooks/use_clusters.ts create mode 100644 x-pack/plugins/monitoring/public/application/hooks/use_title.ts create mode 100644 x-pack/plugins/monitoring/public/application/index.tsx create mode 100644 x-pack/plugins/monitoring/public/application/pages/loading_page.tsx create mode 100644 x-pack/plugins/monitoring/public/application/pages/page_template.tsx create mode 100644 x-pack/plugins/monitoring/public/components/index.d.ts diff --git a/x-pack/plugins/monitoring/public/application/hooks/use_clusters.ts b/x-pack/plugins/monitoring/public/application/hooks/use_clusters.ts new file mode 100644 index 0000000000000..49f6464b2ce3e --- /dev/null +++ b/x-pack/plugins/monitoring/public/application/hooks/use_clusters.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { useState, useEffect } from 'react'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { STANDALONE_CLUSTER_CLUSTER_UUID } from '../../../common/constants'; + +export function useClusters(codePaths?: string[], fetchAllClusters?: boolean, ccs?: any) { + const clusterUuid = fetchAllClusters ? null : ''; + const { services } = useKibana<{ data: any }>(); + + const bounds = services.data?.query.timefilter.timefilter.getBounds(); + const [min] = useState(bounds.min.toISOString()); + const [max] = useState(bounds.max.toISOString()); + + const [clusters, setClusters] = useState([]); + const [loaded, setLoaded] = useState(false); + + let url = '../api/monitoring/v1/clusters'; + if (clusterUuid) { + url += `/${clusterUuid}`; + } + + useEffect(() => { + const fetchClusters = async () => { + try { + const response = await services.http?.fetch(url, { + method: 'POST', + body: JSON.stringify({ + ccs, + timeRange: { + min, + max, + }, + codePaths, + }), + }); + + setClusters(formatClusters(response)); + } catch (err) { + // TODO: handle errors + } finally { + setLoaded(null); + } + }; + + fetchClusters(); + }, [ccs, services.http, codePaths, url, min, max]); + + return { clusters, loaded }; +} + +function formatClusters(clusters: any) { + return clusters.map(formatCluster); +} + +function formatCluster(cluster: any) { + if (cluster.cluster_uuid === STANDALONE_CLUSTER_CLUSTER_UUID) { + cluster.cluster_name = 'Standalone Cluster'; + } + return cluster; +} diff --git a/x-pack/plugins/monitoring/public/application/hooks/use_title.ts b/x-pack/plugins/monitoring/public/application/hooks/use_title.ts new file mode 100644 index 0000000000000..25cc2c5b40dff --- /dev/null +++ b/x-pack/plugins/monitoring/public/application/hooks/use_title.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { get } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; + +// TODO: verify that works for all pages +export function useTitle(cluster: string, suffix: string) { + const { services } = useKibana(); + let clusterName = get(cluster, 'cluster_name'); + clusterName = clusterName ? `- ${clusterName}` : ''; + suffix = suffix ? `- ${suffix}` : ''; + + services.chrome?.docTitle.change( + i18n.translate('xpack.monitoring.stackMonitoringDocTitle', { + defaultMessage: 'Stack Monitoring {clusterName} {suffix}', + values: { clusterName, suffix }, + }) + ); +} diff --git a/x-pack/plugins/monitoring/public/application/index.tsx b/x-pack/plugins/monitoring/public/application/index.tsx new file mode 100644 index 0000000000000..a0c9afd73f0ce --- /dev/null +++ b/x-pack/plugins/monitoring/public/application/index.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CoreStart, AppMountParameters } from 'kibana/public'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Route, Switch, Redirect, HashRouter } from 'react-router-dom'; +import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; +import { LoadingPage } from './pages/loading_page'; +import { MonitoringStartPluginDependencies } from '../types'; + +export const renderApp = ( + core: CoreStart, + plugins: MonitoringStartPluginDependencies, + { element }: AppMountParameters +) => { + ReactDOM.render(, element); + + return () => { + ReactDOM.unmountComponentAtNode(element); + }; +}; + +const MonitoringApp: React.FC<{ + core: CoreStart; + plugins: MonitoringStartPluginDependencies; +}> = ({ core, plugins }) => { + return ( + + + + + + + + + + + + ); +}; + +const NoData: React.FC<{}> = () => { + return
No data page
; +}; + +const Home: React.FC<{}> = () => { + return
Home page (Cluster listing)
; +}; + +const ClusterOverview: React.FC<{}> = () => { + return
Cluster overview page
; +}; diff --git a/x-pack/plugins/monitoring/public/application/pages/loading_page.tsx b/x-pack/plugins/monitoring/public/application/pages/loading_page.tsx new file mode 100644 index 0000000000000..4bd09f73ac75a --- /dev/null +++ b/x-pack/plugins/monitoring/public/application/pages/loading_page.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { Redirect } from 'react-router-dom'; +import { i18n } from '@kbn/i18n'; +import { PageTemplate } from './page_template'; +import { PageLoading } from '../../components'; +import { useClusters } from '../hooks/use_clusters'; +import { CODE_PATH_ELASTICSEARCH } from '../../../common/constants'; + +const CODE_PATHS = [CODE_PATH_ELASTICSEARCH]; + +export const LoadingPage = () => { + const { clusters, loaded } = useClusters(CODE_PATHS, true); + const title = i18n.translate('xpack.monitoring.loading.pageTitle', { + defaultMessage: 'Loading', + }); + + return ( + + {loaded === false ? : renderRedirections(clusters)} + + ); +}; + +const renderRedirections = (clusters: any) => { + if (!clusters || !clusters.length) { + return ; + } + if (clusters.length === 1) { + // Bypass the cluster listing if there is just 1 cluster + return ; + } + + return ; +}; diff --git a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx new file mode 100644 index 0000000000000..fb766af6c8cbe --- /dev/null +++ b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { useTitle } from '../hooks/use_title'; + +interface PageTemplateProps { + title: string; + children: React.ReactNode; +} + +export const PageTemplate = ({ title, children }: PageTemplateProps) => { + useTitle('', title); + + return
{children}
; +}; diff --git a/x-pack/plugins/monitoring/public/components/index.d.ts b/x-pack/plugins/monitoring/public/components/index.d.ts new file mode 100644 index 0000000000000..d027298c81c4c --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/index.d.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const PageLoading: FunctionComponent; diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts index 710b453e7f21e..df0496d438013 100644 --- a/x-pack/plugins/monitoring/public/plugin.ts +++ b/x-pack/plugins/monitoring/public/plugin.ts @@ -94,6 +94,7 @@ export class MonitoringPlugin mount: async (params: AppMountParameters) => { const [coreStart, pluginsStart] = await core.getStartServices(); const { AngularApp } = await import('./angular'); + const externalConfig = this.getExternalConfig(); const deps: MonitoringStartPluginDependencies = { navigation: pluginsStart.navigation, kibanaLegacy: pluginsStart.kibanaLegacy, @@ -102,27 +103,33 @@ export class MonitoringPlugin data: pluginsStart.data, isCloud: Boolean(plugins.cloud?.isCloudEnabled), pluginInitializerContext: this.initializerContext, - externalConfig: this.getExternalConfig(), + externalConfig, triggersActionsUi: pluginsStart.triggersActionsUi, usageCollection: plugins.usageCollection, appMountParameters: params, }; - const monitoringApp = new AngularApp(deps); - const removeHistoryListener = params.history.listen((location) => { - if (location.pathname === '' && location.hash === '') { - monitoringApp.applyScope(); - } - }); - - const removeHashChange = this.setInitialTimefilter(deps); - return () => { - if (removeHashChange) { - removeHashChange(); - } - removeHistoryListener(); - monitoringApp.destroy(); - }; + const config = Object.fromEntries(externalConfig); + if (config.renderReactApp) { + const { renderApp } = await import('./application'); + return renderApp(coreStart, pluginsStart, params); + } else { + const monitoringApp = new AngularApp(deps); + const removeHistoryListener = params.history.listen((location) => { + if (location.pathname === '' && location.hash === '') { + monitoringApp.applyScope(); + } + }); + + const removeHashChange = this.setInitialTimefilter(deps); + return () => { + if (removeHashChange) { + removeHashChange(); + } + removeHistoryListener(); + monitoringApp.destroy(); + }; + } }, }; @@ -163,6 +170,7 @@ export class MonitoringPlugin ['showLicenseExpiration', monitoring.ui.show_license_expiration], ['showCgroupMetricsElasticsearch', monitoring.ui.container.elasticsearch.enabled], ['showCgroupMetricsLogstash', monitoring.ui.container.logstash.enabled], + ['renderReactApp', monitoring.ui.render_react_app], ]; } diff --git a/x-pack/plugins/monitoring/server/config.test.ts b/x-pack/plugins/monitoring/server/config.test.ts index f39e5a0703d22..90c6e68314b71 100644 --- a/x-pack/plugins/monitoring/server/config.test.ts +++ b/x-pack/plugins/monitoring/server/config.test.ts @@ -106,6 +106,7 @@ describe('config schema', () => { "index": "metricbeat-*", }, "min_interval_seconds": 10, + "render_react_app": false, "show_license_expiration": true, }, } diff --git a/x-pack/plugins/monitoring/server/config.ts b/x-pack/plugins/monitoring/server/config.ts index 98fd02b03539c..5c2bdc1424f93 100644 --- a/x-pack/plugins/monitoring/server/config.ts +++ b/x-pack/plugins/monitoring/server/config.ts @@ -51,6 +51,7 @@ export const configSchema = schema.object({ }), min_interval_seconds: schema.number({ defaultValue: 10 }), show_license_expiration: schema.boolean({ defaultValue: true }), + render_react_app: schema.boolean({ defaultValue: false }), }), kibana: schema.object({ collection: schema.object({ From bf9413dc42b4f2beebf2e316879ace26f254966f Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 23 Aug 2021 12:02:22 -0400 Subject: [PATCH 20/27] [APM] Support multiple route paths in useApmParams (#109370) (#109653) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Dario Gieselaar --- .../src/create_router.test.tsx | 15 +++++++ .../src/create_router.ts | 44 ++++++++++++------- .../src/types/index.ts | 16 +++++++ .../src/use_params.ts | 18 +++++++- .../service_map/Popover/backend_contents.tsx | 6 ++- .../apm/public/hooks/use_apm_params.ts | 26 +++++++++-- x-pack/plugins/apm/typings/common.d.ts | 1 + 7 files changed, 104 insertions(+), 22 deletions(-) diff --git a/packages/kbn-typed-react-router-config/src/create_router.test.tsx b/packages/kbn-typed-react-router-config/src/create_router.test.tsx index d8f42c8714e8b..3fb37f813e2e1 100644 --- a/packages/kbn-typed-react-router-config/src/create_router.test.tsx +++ b/packages/kbn-typed-react-router-config/src/create_router.test.tsx @@ -201,6 +201,21 @@ describe('createRouter', () => { }, }); }); + + it('supports multiple paths', () => { + history.push('/service-map?rangeFrom=now-15m&rangeTo=now&maxNumNodes=3'); + + const params = router.getParams('/services', '/service-map', history.location); + + expect(params).toEqual({ + path: {}, + query: { + maxNumNodes: 3, + rangeFrom: 'now-15m', + rangeTo: 'now', + }, + }); + }); }); describe('matchRoutes', () => { diff --git a/packages/kbn-typed-react-router-config/src/create_router.ts b/packages/kbn-typed-react-router-config/src/create_router.ts index 28f9e2774eb74..370d8b48e53b4 100644 --- a/packages/kbn-typed-react-router-config/src/create_router.ts +++ b/packages/kbn-typed-react-router-config/src/create_router.ts @@ -9,6 +9,7 @@ import { isLeft } from 'fp-ts/lib/Either'; import { Location } from 'history'; import { PathReporter } from 'io-ts/lib/PathReporter'; import { + MatchedRoute, matchRoutes as matchRoutesConfig, RouteConfig as ReactRouterConfig, } from 'react-router-config'; @@ -49,33 +50,44 @@ export function createRouter(routes: TRoutes): Router { - let path: string = args[0]; - let location: Location = args[1]; - let optional: boolean = args[2]; - - if (args.length === 1) { - location = args[0] as Location; - path = location.pathname; - optional = args[1]; + let optional: boolean = false; + + if (typeof args[args.length - 1] === 'boolean') { + optional = args[args.length - 1]; + args.pop(); } - const greedy = path.endsWith('/*') || args.length === 1; + const location: Location = args[args.length - 1]; + args.pop(); + + let paths: string[] = args; - if (!path) { - path = '/'; + if (paths.length === 0) { + paths = [location.pathname || '/']; } - const matches = matchRoutesConfig(reactRouterConfigs, location.pathname); + let matches: Array> = []; + let matchIndex: number = -1; - const matchIndex = greedy - ? matches.length - 1 - : findLastIndex(matches, (match) => match.route.path === path); + for (const path of paths) { + const greedy = path.endsWith('/*') || args.length === 0; + matches = matchRoutesConfig(reactRouterConfigs, location.pathname); + + matchIndex = greedy + ? matches.length - 1 + : findLastIndex(matches, (match) => match.route.path === path); + + if (matchIndex !== -1) { + break; + } + matchIndex = -1; + } if (matchIndex === -1) { if (optional) { return []; } - throw new Error(`No matching route found for ${path}`); + throw new Error(`No matching route found for ${paths}`); } return matches.slice(0, matchIndex + 1).map((matchedRoute) => { diff --git a/packages/kbn-typed-react-router-config/src/types/index.ts b/packages/kbn-typed-react-router-config/src/types/index.ts index 0e02318c50aad..4d26d2879d5e7 100644 --- a/packages/kbn-typed-react-router-config/src/types/index.ts +++ b/packages/kbn-typed-react-router-config/src/types/index.ts @@ -134,6 +134,22 @@ export interface Router { location: Location, optional: TOptional ): TOptional extends true ? TypeOf | undefined : TypeOf; + getParams, T2 extends PathsOf>( + path1: T1, + path2: T2, + location: Location + ): TypeOf | TypeOf; + getParams, T2 extends PathsOf, T3 extends PathsOf>( + path1: T1, + path2: T2, + path3: T3, + location: Location + ): TypeOf | TypeOf | TypeOf; + getParams, TOptional extends boolean>( + path: TPath, + location: Location, + optional: TOptional + ): TOptional extends true ? TypeOf | undefined : TypeOf; link>( path: TPath, ...args: TypeAsArgs> diff --git a/packages/kbn-typed-react-router-config/src/use_params.ts b/packages/kbn-typed-react-router-config/src/use_params.ts index 94a5cf401c569..0468eb9566236 100644 --- a/packages/kbn-typed-react-router-config/src/use_params.ts +++ b/packages/kbn-typed-react-router-config/src/use_params.ts @@ -6,12 +6,26 @@ * Side Public License, v 1. */ +import { Location } from 'history'; import { useLocation } from 'react-router-dom'; import { useRouter } from './use_router'; -export function useParams(path: string, optional: boolean = false) { +export function useParams(...args: any[]) { const router = useRouter(); const location = useLocation(); - return router.getParams(path as never, location, optional); + let optional: boolean = false; + + const last: boolean | string | undefined = args[args.length - 1]; + + if (typeof last === 'boolean') { + optional = last; + args.pop(); + } + + const paths = args as string[]; + + const getParamsArgs = [...paths, location, optional] as [never, Location, boolean]; + + return router.getParams(...getParamsArgs); } diff --git a/x-pack/plugins/apm/public/components/app/service_map/Popover/backend_contents.tsx b/x-pack/plugins/apm/public/components/app/service_map/Popover/backend_contents.tsx index 5a55fd1979c91..0432cdb2dff5e 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/Popover/backend_contents.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/Popover/backend_contents.tsx @@ -21,7 +21,11 @@ import { ApmRoutes } from '../../../routing/apm_route_config'; import { StatsList } from './stats_list'; export function BackendContents({ nodeData, environment }: ContentsProps) { - const { query } = useApmParams('/*'); + const { query } = useApmParams( + '/service-map', + '/services/:serviceName/service-map' + ); + const apmRouter = useApmRouter(); const { urlParams: { start, end }, diff --git a/x-pack/plugins/apm/public/hooks/use_apm_params.ts b/x-pack/plugins/apm/public/hooks/use_apm_params.ts index fd27e8446e3c4..12b79ec7c90ae 100644 --- a/x-pack/plugins/apm/public/hooks/use_apm_params.ts +++ b/x-pack/plugins/apm/public/hooks/use_apm_params.ts @@ -17,9 +17,29 @@ export function useApmParams>( path: TPath ): TypeOf; +export function useApmParams< + TPath1 extends PathsOf, + TPath2 extends PathsOf +>( + path1: TPath1, + path2: TPath2 +): TypeOf | TypeOf; + +export function useApmParams< + TPath1 extends PathsOf, + TPath2 extends PathsOf, + TPath3 extends PathsOf +>( + path1: TPath1, + path2: TPath2, + path3: TPath3 +): + | TypeOf + | TypeOf + | TypeOf; + export function useApmParams( - path: string, - optional?: true + ...args: any[] ): TypeOf> | undefined { - return useParams(path, optional); + return useParams(...args); } diff --git a/x-pack/plugins/apm/typings/common.d.ts b/x-pack/plugins/apm/typings/common.d.ts index ea4bafad84619..b94eb6cd97b06 100644 --- a/x-pack/plugins/apm/typings/common.d.ts +++ b/x-pack/plugins/apm/typings/common.d.ts @@ -10,6 +10,7 @@ import '../../../typings/rison_node'; import '../../infra/types/eui'; // EUIBasicTable import '../../reporting/public/components/report_listing'; +import '../../reporting/server/lib/puid'; import './apm_rum_react'; // Allow unknown properties in an object From a6eb15ae9348a96c92a44a35c8a3e38709ebfa7d Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 23 Aug 2021 12:44:29 -0400 Subject: [PATCH 21/27] fix TableListView empty view trapping users with no action to create new item (#109345) (#109663) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Nathan Reese --- .../table_list_view.test.tsx.snap | 46 +++++++++++++++++++ .../table_list_view/table_list_view.test.tsx | 13 ++++++ .../table_list_view/table_list_view.tsx | 1 + 3 files changed, 60 insertions(+) diff --git a/src/plugins/kibana_react/public/table_list_view/__snapshots__/table_list_view.test.tsx.snap b/src/plugins/kibana_react/public/table_list_view/__snapshots__/table_list_view.test.tsx.snap index f56dc56073542..a0c34cfdfee07 100644 --- a/src/plugins/kibana_react/public/table_list_view/__snapshots__/table_list_view.test.tsx.snap +++ b/src/plugins/kibana_react/public/table_list_view/__snapshots__/table_list_view.test.tsx.snap @@ -42,6 +42,52 @@ exports[`TableListView render default empty prompt 1`] = ` `; +exports[`TableListView render default empty prompt with create action when createItem supplied 1`] = ` + + + + + } + title={ +

+ +

+ } + /> +
+`; + exports[`TableListView render list view 1`] = ` { expect(component).toMatchSnapshot(); }); + // avoid trapping users in empty prompt that can not create new items + test('render default empty prompt with create action when createItem supplied', async () => { + const component = shallowWithIntl( {}} />); + + // Using setState to check the final render while sidestepping the debounced promise management + component.setState({ + hasInitialFetchReturned: true, + isFetchingItems: false, + }); + + expect(component).toMatchSnapshot(); + }); + test('render custom empty prompt', () => { const component = shallowWithIntl( } /> diff --git a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx index d3cbda7c2ab2e..30d09f4bf8657 100644 --- a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx +++ b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx @@ -362,6 +362,7 @@ class TableListView extends React.Component } + actions={this.renderCreateButton()} /> ); } From 48aa983b34b640307e823f25171391e8748e2bac Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 23 Aug 2021 12:49:36 -0400 Subject: [PATCH 22/27] chore(NA): moving @kbn/securitysolution-hook-utils to babel transpiler (#109432) (#109666) * chore(NA): moving @kbn/securitysolution-hook-utils to babel transpiler * chore(NA): update packages/kbn-securitysolution-hook-utils/.babelrc Co-authored-by: Frank Hassanabad * chore(NA): correct deps Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Frank Hassanabad Co-authored-by: Tiago Costa Co-authored-by: Frank Hassanabad --- .../kbn-securitysolution-hook-utils/.babelrc | 4 +++ .../BUILD.bazel | 26 +++++++++++++------ .../package.json | 4 +-- .../tsconfig.json | 3 ++- 4 files changed, 26 insertions(+), 11 deletions(-) create mode 100644 packages/kbn-securitysolution-hook-utils/.babelrc diff --git a/packages/kbn-securitysolution-hook-utils/.babelrc b/packages/kbn-securitysolution-hook-utils/.babelrc new file mode 100644 index 0000000000000..b17a19d6faf3f --- /dev/null +++ b/packages/kbn-securitysolution-hook-utils/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["@kbn/babel-preset/node_preset"], + "ignore": ["**/*.test.ts", "**/*.test.tsx"] +} diff --git a/packages/kbn-securitysolution-hook-utils/BUILD.bazel b/packages/kbn-securitysolution-hook-utils/BUILD.bazel index b5f3e0df2e0a7..fd57bc852d683 100644 --- a/packages/kbn-securitysolution-hook-utils/BUILD.bazel +++ b/packages/kbn-securitysolution-hook-utils/BUILD.bazel @@ -1,5 +1,6 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") +load("//src/dev/bazel:index.bzl", "jsts_transpiler") PKG_BASE_NAME = "kbn-securitysolution-hook-utils" @@ -27,19 +28,27 @@ NPM_MODULE_EXTRA_FILES = [ "README.md", ] -SRC_DEPS = [ +RUNTIME_DEPS = [ + "@npm//@testing-library/react-hooks", "@npm//react", "@npm//rxjs", "@npm//tslib", ] TYPES_DEPS = [ + "@npm//@testing-library/react-hooks", + "@npm//rxjs", + "@npm//tslib", "@npm//@types/jest", "@npm//@types/node", "@npm//@types/react", ] -DEPS = SRC_DEPS + TYPES_DEPS +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) ts_config( name = "tsconfig", @@ -51,24 +60,25 @@ ts_config( ) ts_project( - name = "tsc", - srcs = SRCS, + name = "tsc_types", args = ["--pretty"], + srcs = SRCS, + deps = TYPES_DEPS, declaration = True, declaration_map = True, - out_dir = "target", + emit_declaration_only = True, + out_dir = "target_types", root_dir = "src", source_map = True, tsconfig = ":tsconfig", - deps = DEPS, ) js_library( name = PKG_BASE_NAME, - package_name = PKG_REQUIRE_NAME, srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":tsc_types"], + package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], - deps = DEPS + [":tsc"], ) pkg_npm( diff --git a/packages/kbn-securitysolution-hook-utils/package.json b/packages/kbn-securitysolution-hook-utils/package.json index 6da17ab00f31c..d33fd1b79b328 100644 --- a/packages/kbn-securitysolution-hook-utils/package.json +++ b/packages/kbn-securitysolution-hook-utils/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "Security Solution utilities for React hooks", "license": "SSPL-1.0 OR Elastic License 2.0", - "main": "./target/index.js", - "types": "./target/index.d.ts", + "main": "./target_node/index.js", + "types": "./target_types/index.d.ts", "private": true } diff --git a/packages/kbn-securitysolution-hook-utils/tsconfig.json b/packages/kbn-securitysolution-hook-utils/tsconfig.json index 7e2cae223b14d..4782331e31c44 100644 --- a/packages/kbn-securitysolution-hook-utils/tsconfig.json +++ b/packages/kbn-securitysolution-hook-utils/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "declaration": true, "declarationMap": true, - "outDir": "target", + "emitDeclarationOnly": true, + "outDir": "target_types", "rootDir": "src", "sourceMap": true, "sourceRoot": "../../../../packages/kbn-securitysolution-hook-utils/src", From c621b8eb6c8b1cf91330510e880e1d7874eb1edd Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 23 Aug 2021 13:13:28 -0400 Subject: [PATCH 23/27] chore(NA): moving @kbn/securitysolution-es-utils to babel transpiler (#109426) (#109674) * chore(NA): moving @kbn/securitysolution-es-utils to babel transpiler * chore(NA): update packages/kbn-securitysolution-es-utils/.babelrc Co-authored-by: Frank Hassanabad * chore(NA): correct deps * chore(NA): finetunning package Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Frank Hassanabad Co-authored-by: Tiago Costa Co-authored-by: Frank Hassanabad --- .../kbn-securitysolution-es-utils/.babelrc | 4 +++ .../kbn-securitysolution-es-utils/BUILD.bazel | 27 +++++++++++++------ .../package.json | 4 +-- .../tsconfig.json | 3 ++- 4 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 packages/kbn-securitysolution-es-utils/.babelrc diff --git a/packages/kbn-securitysolution-es-utils/.babelrc b/packages/kbn-securitysolution-es-utils/.babelrc new file mode 100644 index 0000000000000..40a198521b903 --- /dev/null +++ b/packages/kbn-securitysolution-es-utils/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["@kbn/babel-preset/node_preset"], + "ignore": ["**/*.test.ts", "**/*.test.tsx"] +} diff --git a/packages/kbn-securitysolution-es-utils/BUILD.bazel b/packages/kbn-securitysolution-es-utils/BUILD.bazel index 97f2d9a41cd78..49c23488b3fe3 100644 --- a/packages/kbn-securitysolution-es-utils/BUILD.bazel +++ b/packages/kbn-securitysolution-es-utils/BUILD.bazel @@ -1,5 +1,6 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") +load("//src/dev/bazel:index.bzl", "jsts_transpiler") PKG_BASE_NAME = "kbn-securitysolution-es-utils" @@ -27,18 +28,27 @@ NPM_MODULE_EXTRA_FILES = [ "README.md", ] -SRC_DEPS = [ +RUNTIME_DEPS = [ "@npm//@elastic/elasticsearch", + "@npm//@hapi/boom", "@npm//@hapi/hapi", "@npm//tslib", ] TYPES_DEPS = [ + "@npm//@elastic/elasticsearch", + "@npm//@hapi/boom", + "@npm//tslib", + "@npm//@types/hapi__hapi", "@npm//@types/jest", "@npm//@types/node", ] -DEPS = SRC_DEPS + TYPES_DEPS +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) ts_config( name = "tsconfig", @@ -50,24 +60,25 @@ ts_config( ) ts_project( - name = "tsc", - srcs = SRCS, + name = "tsc_types", args = ["--pretty"], + srcs = SRCS, + deps = TYPES_DEPS, declaration = True, declaration_map = True, - out_dir = "target", + emit_declaration_only = True, + out_dir = "target_types", root_dir = "src", source_map = True, tsconfig = ":tsconfig", - deps = DEPS, ) js_library( name = PKG_BASE_NAME, - package_name = PKG_REQUIRE_NAME, srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":tsc_types"], + package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], - deps = DEPS + [":tsc"], ) pkg_npm( diff --git a/packages/kbn-securitysolution-es-utils/package.json b/packages/kbn-securitysolution-es-utils/package.json index 7d0c0993c6c32..84c16ad0b7992 100644 --- a/packages/kbn-securitysolution-es-utils/package.json +++ b/packages/kbn-securitysolution-es-utils/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "security solution elastic search utilities to use across plugins such lists, security_solution, cases, etc...", "license": "SSPL-1.0 OR Elastic License 2.0", - "main": "./target/index.js", - "types": "./target/index.d.ts", + "main": "./target_node/index.js", + "types": "./target_types/index.d.ts", "private": true } diff --git a/packages/kbn-securitysolution-es-utils/tsconfig.json b/packages/kbn-securitysolution-es-utils/tsconfig.json index 906b01c943ab7..e5b1c99d3f598 100644 --- a/packages/kbn-securitysolution-es-utils/tsconfig.json +++ b/packages/kbn-securitysolution-es-utils/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "declaration": true, "declarationMap": true, - "outDir": "target", + "emitDeclarationOnly": true, + "outDir": "target_types", "rootDir": "src", "sourceMap": true, "sourceRoot": "../../../../packages/kbn-securitysolution-es-utils/src", From 3ca3a9a8379bc37af2687cfce7598fc7937ef075 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 23 Aug 2021 18:13:06 +0100 Subject: [PATCH 24/27] skip failing es promotion suites (#109703) --- .../tests/correlations/failed_transactions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts b/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts index 2d014b3ee1e6b..94c293cd1a19f 100644 --- a/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts +++ b/x-pack/test/apm_api_integration/tests/correlations/failed_transactions.ts @@ -124,7 +124,8 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); - registry.when('on trial license with data', { config: 'trial', archives: ['8.0.0'] }, () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/109703 + registry.when.skip('on trial license with data', { config: 'trial', archives: ['8.0.0'] }, () => { it('queries the search strategy and returns results', async () => { const intialResponse = await supertest .post(`/internal/bsearch`) From c31154bf5cc4114bd311dae1e0215cac9517a610 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 23 Aug 2021 13:16:24 -0400 Subject: [PATCH 25/27] chore(NA): moving @kbn/securitysolution-t-grid to babel transpiler (#109442) (#109677) * chore(NA): moving @kbn/securitysolution-t-grid to babel transpiler * chore(NA): fix package.json * chore(NA): finetunning package Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Tiago Costa --- packages/kbn-securitysolution-t-grid/.babelrc | 4 ++ .../.babelrc.browser | 4 ++ .../kbn-securitysolution-t-grid/BUILD.bazel | 63 +++++++------------ .../babel.config.js | 19 ------ .../kbn-securitysolution-t-grid/package.json | 2 +- .../react/package.json | 5 -- .../tsconfig.browser.json | 22 ------- .../kbn-securitysolution-t-grid/tsconfig.json | 3 +- 8 files changed, 32 insertions(+), 90 deletions(-) create mode 100644 packages/kbn-securitysolution-t-grid/.babelrc create mode 100644 packages/kbn-securitysolution-t-grid/.babelrc.browser delete mode 100644 packages/kbn-securitysolution-t-grid/babel.config.js delete mode 100644 packages/kbn-securitysolution-t-grid/react/package.json delete mode 100644 packages/kbn-securitysolution-t-grid/tsconfig.browser.json diff --git a/packages/kbn-securitysolution-t-grid/.babelrc b/packages/kbn-securitysolution-t-grid/.babelrc new file mode 100644 index 0000000000000..40a198521b903 --- /dev/null +++ b/packages/kbn-securitysolution-t-grid/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["@kbn/babel-preset/node_preset"], + "ignore": ["**/*.test.ts", "**/*.test.tsx"] +} diff --git a/packages/kbn-securitysolution-t-grid/.babelrc.browser b/packages/kbn-securitysolution-t-grid/.babelrc.browser new file mode 100644 index 0000000000000..71bbfbcd6eb2f --- /dev/null +++ b/packages/kbn-securitysolution-t-grid/.babelrc.browser @@ -0,0 +1,4 @@ +{ + "presets": ["@kbn/babel-preset/webpack_preset"], + "ignore": ["**/*.test.ts", "**/*.test.tsx"] +} diff --git a/packages/kbn-securitysolution-t-grid/BUILD.bazel b/packages/kbn-securitysolution-t-grid/BUILD.bazel index 0c5ef200fc965..f9a6a5d7934ad 100644 --- a/packages/kbn-securitysolution-t-grid/BUILD.bazel +++ b/packages/kbn-securitysolution-t-grid/BUILD.bazel @@ -1,5 +1,6 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") +load("//src/dev/bazel:index.bzl", "jsts_transpiler") PKG_BASE_NAME = "kbn-securitysolution-t-grid" @@ -24,36 +25,37 @@ filegroup( ) NPM_MODULE_EXTRA_FILES = [ - "react/package.json", "package.json", "README.md", ] -SRC_DEPS = [ - "//packages/kbn-babel-preset", - "//packages/kbn-dev-utils", - "//packages/kbn-i18n", - "@npm//@babel/core", - "@npm//babel-loader", - "@npm//enzyme", +RUNTIME_DEPS = [ "@npm//jest", "@npm//lodash", - "@npm//react", "@npm//react-beautiful-dnd", "@npm//tslib", ] TYPES_DEPS = [ - "@npm//typescript", - "@npm//@types/enzyme", + "@npm//tslib", "@npm//@types/jest", "@npm//@types/lodash", "@npm//@types/node", - "@npm//@types/react", "@npm//@types/react-beautiful-dnd", ] -DEPS = SRC_DEPS + TYPES_DEPS +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + config_file = ".babelrc.browser" +) ts_config( name = "tsconfig", @@ -64,49 +66,26 @@ ts_config( ], ) -ts_config( - name = "tsconfig_browser", - src = "tsconfig.browser.json", - deps = [ - "//:tsconfig.base.json", - "//:tsconfig.browser.json", - "//:tsconfig.browser_bazel.json", - ], -) - ts_project( - name = "tsc", + name = "tsc_types", args = ["--pretty"], srcs = SRCS, - deps = DEPS, + deps = TYPES_DEPS, declaration = True, - declaration_dir = "target_types", declaration_map = True, - out_dir = "target_node", + emit_declaration_only = True, + out_dir = "target_types", root_dir = "src", source_map = True, tsconfig = ":tsconfig", ) -ts_project( - name = "tsc_browser", - args = ['--pretty'], - srcs = SRCS, - deps = DEPS, - allow_js = True, - declaration = False, - out_dir = "target_web", - source_map = True, - root_dir = "src", - tsconfig = ":tsconfig_browser", -) - js_library( name = PKG_BASE_NAME, - package_name = PKG_REQUIRE_NAME, srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web", ":tsc_types"], + package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], - deps = [":tsc", ":tsc_browser"] + DEPS, ) pkg_npm( diff --git a/packages/kbn-securitysolution-t-grid/babel.config.js b/packages/kbn-securitysolution-t-grid/babel.config.js deleted file mode 100644 index b4a118df51af5..0000000000000 --- a/packages/kbn-securitysolution-t-grid/babel.config.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - env: { - web: { - presets: ['@kbn/babel-preset/webpack_preset'], - }, - node: { - presets: ['@kbn/babel-preset/node_preset'], - }, - }, - ignore: ['**/*.test.ts', '**/*.test.tsx'], -}; diff --git a/packages/kbn-securitysolution-t-grid/package.json b/packages/kbn-securitysolution-t-grid/package.json index 68d3a8c71e7ca..92464f3246ecd 100644 --- a/packages/kbn-securitysolution-t-grid/package.json +++ b/packages/kbn-securitysolution-t-grid/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "security solution t-grid packages will allow sharing components between timelines and security_solution plugin until we transfer all functionality to timelines plugin", "license": "SSPL-1.0 OR Elastic License 2.0", - "browser": "./target_web/browser.js", + "browser": "./target_web/index.js", "main": "./target_node/index.js", "types": "./target_types/index.d.ts", "private": true diff --git a/packages/kbn-securitysolution-t-grid/react/package.json b/packages/kbn-securitysolution-t-grid/react/package.json deleted file mode 100644 index c29ddd45f084d..0000000000000 --- a/packages/kbn-securitysolution-t-grid/react/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "browser": "../target_web/react", - "main": "../target_node/react", - "types": "../target_types/react/index.d.ts" -} \ No newline at end of file diff --git a/packages/kbn-securitysolution-t-grid/tsconfig.browser.json b/packages/kbn-securitysolution-t-grid/tsconfig.browser.json deleted file mode 100644 index e951765c4b991..0000000000000 --- a/packages/kbn-securitysolution-t-grid/tsconfig.browser.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "extends": "../../tsconfig.browser_bazel.json", - "compilerOptions": { - "allowJs": true, - "outDir": "./target_web", - "declaration": false, - "isolatedModules": true, - "sourceMap": true, - "sourceRoot": "../../../../../packages/kbn-securitysolution-t-grid/src", - "types": [ - "jest", - "node" - ], - }, - "include": [ - "src/**/*.ts", - "src/**/*.tsx", - ], - "exclude": [ - "**/__fixtures__/**/*" - ] -} diff --git a/packages/kbn-securitysolution-t-grid/tsconfig.json b/packages/kbn-securitysolution-t-grid/tsconfig.json index 1d5957e516e00..3c701d149ab2e 100644 --- a/packages/kbn-securitysolution-t-grid/tsconfig.json +++ b/packages/kbn-securitysolution-t-grid/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "declaration": true, "declarationMap": true, - "outDir": "target", + "emitDeclarationOnly": true, + "outDir": "target_types", "rootDir": "src", "sourceMap": true, "sourceRoot": "../../../../packages/kbn-securitysolution-t-grid/src", From dfa1e83e94f85205bc2743dc928e3e7530f37950 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 23 Aug 2021 13:24:00 -0400 Subject: [PATCH 26/27] [Discover][Context] Set trackTotalHits to false in requests to Elasticsearch (#108661) (#109679) Co-authored-by: Matthias Wilhelm --- .../api/__snapshots__/context.test.ts.snap | 65 +++++++++++++++++++ .../angular/context/api/anchor.test.ts | 30 ++++++++- .../application/angular/context/api/anchor.ts | 63 +++++++++++------- .../context/api/context.predecessors.test.ts | 6 +- .../context/api/context.successors.test.ts | 6 +- .../angular/context/api/context.test.ts | 24 +++++++ .../angular/context/api/context.ts | 35 +++++----- 7 files changed, 183 insertions(+), 46 deletions(-) create mode 100644 src/plugins/discover/public/application/angular/context/api/__snapshots__/context.test.ts.snap create mode 100644 src/plugins/discover/public/application/angular/context/api/context.test.ts diff --git a/src/plugins/discover/public/application/angular/context/api/__snapshots__/context.test.ts.snap b/src/plugins/discover/public/application/angular/context/api/__snapshots__/context.test.ts.snap new file mode 100644 index 0000000000000..4d0fae8f1f5a6 --- /dev/null +++ b/src/plugins/discover/public/application/angular/context/api/__snapshots__/context.test.ts.snap @@ -0,0 +1,65 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`context api createSearchSource when useFieldsApi is false 1`] = ` +Object { + "_source": Object {}, + "fields": Array [], + "query": Object { + "bool": Object { + "filter": Array [], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + "runtime_mappings": Object {}, + "script_fields": Object {}, + "stored_fields": Array [ + "*", + ], + "track_total_hits": false, +} +`; + +exports[`context api createSearchSource when useFieldsApi is true 1`] = ` +Object { + "_source": false, + "fields": Array [ + Object { + "field": "_source", + }, + Object { + "field": "_index", + }, + Object { + "field": "message", + }, + Object { + "field": "extension", + }, + Object { + "field": "bytes", + }, + Object { + "field": "scripted", + }, + Object { + "field": "object.value", + }, + ], + "query": Object { + "bool": Object { + "filter": Array [], + "must": Array [], + "must_not": Array [], + "should": Array [], + }, + }, + "runtime_mappings": Object {}, + "script_fields": Object {}, + "stored_fields": Array [ + "*", + ], + "track_total_hits": false, +} +`; diff --git a/src/plugins/discover/public/application/angular/context/api/anchor.test.ts b/src/plugins/discover/public/application/angular/context/api/anchor.test.ts index 4da8ddc798003..932c7398fc951 100644 --- a/src/plugins/discover/public/application/angular/context/api/anchor.test.ts +++ b/src/plugins/discover/public/application/angular/context/api/anchor.test.ts @@ -8,8 +8,10 @@ import { EsQuerySortValue, SortDirection } from '../../../../../../data/public'; import { createIndexPatternsStub, createSearchSourceStub } from './_stubs'; -import { fetchAnchorProvider } from './anchor'; +import { fetchAnchorProvider, updateSearchSource } from './anchor'; import { EsHitRecord, EsHitRecordList } from './context'; +import { indexPatternMock } from '../../../../__mocks__/index_pattern'; +import { savedSearchMock } from '../../../../__mocks__/saved_search'; describe('context app', function () { let fetchAnchor: ( @@ -114,6 +116,32 @@ describe('context app', function () { }); }); + it('should update search source correctly when useNewFieldsApi set to false', function () { + const searchSource = updateSearchSource( + savedSearchMock.searchSource, + 'id', + [], + false, + indexPatternMock + ); + const searchRequestBody = searchSource.getSearchRequestBody(); + expect(searchRequestBody._source).toBeInstanceOf(Object); + expect(searchRequestBody.track_total_hits).toBe(false); + }); + + it('should update search source correctly when useNewFieldsApi set to true', function () { + const searchSource = updateSearchSource( + savedSearchMock.searchSource, + 'id', + [], + true, + indexPatternMock + ); + const searchRequestBody = searchSource.getSearchRequestBody(); + expect(searchRequestBody._source).toBe(false); + expect(searchRequestBody.track_total_hits).toBe(false); + }); + it('should reject with an error when no hits were found', function () { searchSourceStub._stubHits = []; diff --git a/src/plugins/discover/public/application/angular/context/api/anchor.ts b/src/plugins/discover/public/application/angular/context/api/anchor.ts index f2111d020aade..06ca4bd4afa62 100644 --- a/src/plugins/discover/public/application/angular/context/api/anchor.ts +++ b/src/plugins/discover/public/application/angular/context/api/anchor.ts @@ -13,6 +13,7 @@ import { ISearchSource, IndexPatternsContract, EsQuerySortValue, + IndexPattern, } from '../../../../../../data/public'; import { EsHitRecord } from './context'; @@ -27,31 +28,12 @@ export function fetchAnchorProvider( sort: EsQuerySortValue[] ): Promise { const indexPattern = await indexPatterns.get(indexPatternId); - searchSource - .setParent(undefined) - .setField('index', indexPattern) - .setField('version', true) - .setField('size', 1) - .setField('query', { - query: { - constant_score: { - filter: { - ids: { - values: [anchorId], - }, - }, - }, - }, - language: 'lucene', - }) - .setField('sort', sort); - if (useNewFieldsApi) { - searchSource.removeField('fieldsFromSource'); - searchSource.setField('fields', [{ field: '*', include_unmapped: 'true' }]); - } + updateSearchSource(searchSource, anchorId, sort, useNewFieldsApi, indexPattern); + const response = await searchSource.fetch(); + const doc = get(response, ['hits', 'hits', 0]); - if (get(response, ['hits', 'total'], 0) < 1) { + if (!doc) { throw new Error( i18n.translate('discover.context.failedToLoadAnchorDocumentErrorDescription', { defaultMessage: 'Failed to load anchor document.', @@ -60,8 +42,41 @@ export function fetchAnchorProvider( } return { - ...get(response, ['hits', 'hits', 0]), + ...doc, isAnchor: true, } as EsHitRecord; }; } + +export function updateSearchSource( + searchSource: ISearchSource, + anchorId: string, + sort: EsQuerySortValue[], + useNewFieldsApi: boolean, + indexPattern: IndexPattern +) { + searchSource + .setParent(undefined) + .setField('index', indexPattern) + .setField('version', true) + .setField('size', 1) + .setField('query', { + query: { + constant_score: { + filter: { + ids: { + values: [anchorId], + }, + }, + }, + }, + language: 'lucene', + }) + .setField('sort', sort) + .setField('trackTotalHits', false); + if (useNewFieldsApi) { + searchSource.removeField('fieldsFromSource'); + searchSource.setField('fields', [{ field: '*', include_unmapped: 'true' }]); + } + return searchSource; +} diff --git a/src/plugins/discover/public/application/angular/context/api/context.predecessors.test.ts b/src/plugins/discover/public/application/angular/context/api/context.predecessors.test.ts index ca74c77676edb..127616e27fd92 100644 --- a/src/plugins/discover/public/application/angular/context/api/context.predecessors.test.ts +++ b/src/plugins/discover/public/application/angular/context/api/context.predecessors.test.ts @@ -27,7 +27,7 @@ interface Timestamp { lte?: string; } -describe('context app', function () { +describe('context predecessors', function () { let fetchPredecessors: ( indexPatternId: string, timeField: string, @@ -49,7 +49,7 @@ describe('context app', function () { data: { search: { searchSource: { - create: jest.fn().mockImplementation(() => mockSearchSource), + createEmpty: jest.fn().mockImplementation(() => mockSearchSource), }, }, }, @@ -241,7 +241,7 @@ describe('context app', function () { data: { search: { searchSource: { - create: jest.fn().mockImplementation(() => mockSearchSource), + createEmpty: jest.fn().mockImplementation(() => mockSearchSource), }, }, }, diff --git a/src/plugins/discover/public/application/angular/context/api/context.successors.test.ts b/src/plugins/discover/public/application/angular/context/api/context.successors.test.ts index ba61dd15af46b..a6c4a734fdbc4 100644 --- a/src/plugins/discover/public/application/angular/context/api/context.successors.test.ts +++ b/src/plugins/discover/public/application/angular/context/api/context.successors.test.ts @@ -27,7 +27,7 @@ interface Timestamp { lte?: string; } -describe('context app', function () { +describe('context successors', function () { let fetchSuccessors: ( indexPatternId: string, timeField: string, @@ -49,7 +49,7 @@ describe('context app', function () { data: { search: { searchSource: { - create: jest.fn().mockImplementation(() => mockSearchSource), + createEmpty: jest.fn().mockImplementation(() => mockSearchSource), }, }, }, @@ -244,7 +244,7 @@ describe('context app', function () { data: { search: { searchSource: { - create: jest.fn().mockImplementation(() => mockSearchSource), + createEmpty: jest.fn().mockImplementation(() => mockSearchSource), }, }, }, diff --git a/src/plugins/discover/public/application/angular/context/api/context.test.ts b/src/plugins/discover/public/application/angular/context/api/context.test.ts new file mode 100644 index 0000000000000..5ad9c02871dca --- /dev/null +++ b/src/plugins/discover/public/application/angular/context/api/context.test.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { updateSearchSource } from './context'; +import { indexPatternMock } from '../../../../__mocks__/index_pattern'; +import { createSearchSourceMock } from '../../../../../../data/public/mocks'; + +describe('context api', function () { + test('createSearchSource when useFieldsApi is true', () => { + const newSearchSource = createSearchSourceMock({ index: indexPatternMock }); + const searchSource = updateSearchSource(newSearchSource, indexPatternMock, [], true); + expect(searchSource.getSearchRequestBody()).toMatchSnapshot(); + }); + test('createSearchSource when useFieldsApi is false', () => { + const newSearchSource = createSearchSourceMock({ index: indexPatternMock }); + const searchSource = updateSearchSource(newSearchSource, indexPatternMock, [], false); + expect(searchSource.getSearchRequestBody()).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/discover/public/application/angular/context/api/context.ts b/src/plugins/discover/public/application/angular/context/api/context.ts index e9da3a7c4784f..b6ba95fd5e84a 100644 --- a/src/plugins/discover/public/application/angular/context/api/context.ts +++ b/src/plugins/discover/public/application/angular/context/api/context.ts @@ -7,7 +7,7 @@ */ import type { estypes } from '@elastic/elasticsearch'; -import { Filter, IndexPatternsContract, IndexPattern } from 'src/plugins/data/public'; +import { Filter, IndexPatternsContract, IndexPattern, SearchSource } from 'src/plugins/data/public'; import { reverseSortDir, SortDirection } from './utils/sorting'; import { extractNanos, convertIsoToMillis } from './utils/date_conversion'; import { fetchHitsInInterval } from './utils/fetch_hits_in_interval'; @@ -46,7 +46,7 @@ function fetchContextProvider(indexPatterns: IndexPatternsContract, useNewFields * * @param {SurrDocType} type - `successors` or `predecessors` * @param {string} indexPatternId - * @param {AnchorHitRecord} anchor - anchor record + * @param {EsHitRecord} anchor - anchor record * @param {string} timeField - name of the timefield, that's sorted on * @param {string} tieBreakerField - name of the tie breaker, the 2nd sort field * @param {SortDirection} sortDir - direction of sorting @@ -68,7 +68,9 @@ function fetchContextProvider(indexPatterns: IndexPatternsContract, useNewFields return []; } const indexPattern = await indexPatterns.get(indexPatternId); - const searchSource = await createSearchSource(indexPattern, filters); + const { data } = getServices(); + const searchSource = data.search.searchSource.createEmpty() as SearchSource; + updateSearchSource(searchSource, indexPattern, filters, Boolean(useNewFieldsApi)); const sortDirToApply = type === SurrDocType.SUCCESSORS ? sortDir : reverseSortDir(sortDir); const nanos = indexPattern.isTimeNanosBased() ? extractNanos(anchor.fields[timeField][0]) : ''; @@ -116,20 +118,23 @@ function fetchContextProvider(indexPatterns: IndexPatternsContract, useNewFields return documents; } +} - async function createSearchSource(indexPattern: IndexPattern, filters: Filter[]) { - const { data } = getServices(); - - const searchSource = await data.search.searchSource.create(); - if (useNewFieldsApi) { - searchSource.removeField('fieldsFromSource'); - searchSource.setField('fields', [{ field: '*', include_unmapped: 'true' }]); - } - return searchSource - .setParent(undefined) - .setField('index', indexPattern) - .setField('filter', filters); +export function updateSearchSource( + searchSource: SearchSource, + indexPattern: IndexPattern, + filters: Filter[], + useNewFieldsApi: boolean +) { + if (useNewFieldsApi) { + searchSource.removeField('fieldsFromSource'); + searchSource.setField('fields', [{ field: '*', include_unmapped: 'true' }]); } + return searchSource + .setParent(undefined) + .setField('index', indexPattern) + .setField('filter', filters) + .setField('trackTotalHits', false); } export { fetchContextProvider }; From eefe8b54fda121dfefb2b4b0de0b6cb8ad024483 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 23 Aug 2021 13:36:36 -0400 Subject: [PATCH 27/27] [Discover] Fix discover footer width (#109403) (#109621) * [Discover] fix discover footer width * [Discover] fix types Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Dmitry Tomashevich <39378793+Dmitriynj@users.noreply.github.com> --- .../main/components/doc_table/_doc_table.scss | 1 + .../doc_table/doc_table_infinite.tsx | 55 +++++++++++-------- .../doc_table/doc_table_wrapper.tsx | 2 + 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/_doc_table.scss b/src/plugins/discover/public/application/apps/main/components/doc_table/_doc_table.scss index add2d4e753c60..d19a1fd042069 100644 --- a/src/plugins/discover/public/application/apps/main/components/doc_table/_doc_table.scss +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/_doc_table.scss @@ -5,6 +5,7 @@ .kbnDocTableWrapper { @include euiScrollBar; overflow: auto; + display: flex; flex: 1 1 100%; flex-direction: column; /* 1 */ diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_infinite.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_infinite.tsx index 8e9066151b368..778c6c1abe274 100644 --- a/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_infinite.tsx +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_infinite.tsx @@ -14,6 +14,8 @@ import { EuiButtonEmpty } from '@elastic/eui'; import { DocTableProps, DocTableRenderProps, DocTableWrapper } from './doc_table_wrapper'; import { SkipBottomButton } from '../skip_bottom_button'; +const FOOTER_PADDING = { padding: 0 }; + const DocTableInfiniteContent = (props: DocTableRenderProps) => { const [limit, setLimit] = useState(props.minimumVisibleRows); @@ -74,29 +76,38 @@ const DocTableInfiniteContent = (props: DocTableRenderProps) => { {props.renderHeader()}{props.renderRows(props.rows.slice(0, limit))} -
- {props.rows.length === props.sampleSize ? ( -
- + - - - -
- ) : ( - - ​ - - )} + values={{ sampleSize: props.sampleSize }} + /> + + + + + ) : ( + + ​ + + )} + + + + ); }; diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_wrapper.tsx b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_wrapper.tsx index c875bf155bd79..4c832d3f6a02c 100644 --- a/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_wrapper.tsx +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/doc_table_wrapper.tsx @@ -81,6 +81,7 @@ export interface DocTableProps { } export interface DocTableRenderProps { + columnLength: number; rows: DocTableRow[]; minimumVisibleRows: number; sampleSize: number; @@ -219,6 +220,7 @@ export const DocTableWrapper = ({ > {rows.length !== 0 && render({ + columnLength: columns.length, rows, minimumVisibleRows, sampleSize,