From 0f7b8efb748fe73d824574eef5e32d922c4f0c2b Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 15 Oct 2021 20:13:33 -0400 Subject: [PATCH 01/34] [build] Dockerfile update (#115237) (#115285) Signed-off-by: Tyler Smalley Co-authored-by: Tyler Smalley --- .../os_packages/docker_generator/templates/base/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile b/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile index eb4708b6ac5553..0c3cc3441166eb 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile @@ -97,7 +97,7 @@ COPY --chown=1000:0 config/kibana.yml /usr/share/kibana/config/kibana.yml # Add the launcher/wrapper script. It knows how to interpret environment # variables and translate them to Kibana CLI options. -COPY --chown=1000:0 bin/kibana-docker /usr/local/bin/ +COPY bin/kibana-docker /usr/local/bin/ # Ensure gid 0 write permissions for OpenShift. RUN chmod g+ws /usr/share/kibana && \ From 5a9e711a70a767d3757ba66ba4db6a0da183cf69 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Fri, 1 Oct 2021 12:49:52 +0100 Subject: [PATCH 02/34] skip flaky suite (#112910) --- .../public/common/hooks/use_upgrade_secuirty_packages.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_upgrade_secuirty_packages.test.tsx b/x-pack/plugins/security_solution/public/common/hooks/use_upgrade_secuirty_packages.test.tsx index f1d1b09f45f603..968bd8679b23ba 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/use_upgrade_secuirty_packages.test.tsx +++ b/x-pack/plugins/security_solution/public/common/hooks/use_upgrade_secuirty_packages.test.tsx @@ -21,7 +21,8 @@ jest.mock('../components/user_privileges', () => { }); jest.mock('../lib/kibana'); -describe('When using the `useUpgradeSecurityPackages()` hook', () => { +// FLAKY: https://github.com/elastic/kibana/issues/112910 +describe.skip('When using the `useUpgradeSecurityPackages()` hook', () => { let renderResult: RenderHookResult; let renderHook: () => RenderHookResult; let kibana: ReturnType; From b928000a1116130b5af12482d8d65fdac4ac54ad Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 18 Oct 2021 09:55:54 -0400 Subject: [PATCH 03/34] Initial commit (#115272) (#115372) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mike Côté --- docs/management/connectors/action-types/pagerduty.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/management/connectors/action-types/pagerduty.asciidoc b/docs/management/connectors/action-types/pagerduty.asciidoc index db1c4e3932d148..5e12eddaa5c774 100644 --- a/docs/management/connectors/action-types/pagerduty.asciidoc +++ b/docs/management/connectors/action-types/pagerduty.asciidoc @@ -68,7 +68,7 @@ PagerDuty actions have the following properties. Severity:: The perceived severity of on the affected system. This can be one of `Critical`, `Error`, `Warning` or `Info`(default). Event action:: One of `Trigger` (default), `Resolve`, or `Acknowledge`. See https://v2.developer.pagerduty.com/docs/events-api-v2#event-action[event action] for more details. -Dedup Key:: All actions sharing this key will be associated with the same PagerDuty alert. This value is used to correlate trigger and resolution. This value is *optional*, and if not set, defaults to `:`. The maximum length is *255* characters. See https://v2.developer.pagerduty.com/docs/events-api-v2#alert-de-duplication[alert deduplication] for details. +Dedup Key:: All actions sharing this key will be associated with the same PagerDuty alert. This value is used to correlate trigger and resolution. This value is *optional*, and if not set, defaults to `:`. The maximum length is *255* characters. See https://v2.developer.pagerduty.com/docs/events-api-v2#alert-de-duplication[alert deduplication] for details. Timestamp:: An *optional* https://v2.developer.pagerduty.com/v2/docs/types#datetime[ISO-8601 format date-time], indicating the time the event was detected or generated. Component:: An *optional* value indicating the component of the source machine that is responsible for the event, for example `mysql` or `eth0`. Group:: An *optional* value indicating the logical grouping of components of a service, for example `app-stack`. From bd92b4fbf4731eaec600e0e5921e5915bd40f66f Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Mon, 18 Oct 2021 17:15:37 +0200 Subject: [PATCH 04/34] [7.15] [Lens] Fix filters not being cleaned when navigating to another visualisation (#115162) * fix filters/query bugs * tests --- .../lens/public/app_plugin/mounter.tsx | 15 +- .../state_management/init_middleware/index.ts | 3 +- .../init_middleware/load_initial.test.tsx | 5 +- .../init_middleware/load_initial.ts | 35 ++-- .../public/state_management/lens_slice.ts | 5 +- x-pack/test/functional/apps/lens/geo_field.ts | 1 + x-pack/test/functional/apps/lens/index.ts | 9 +- .../apps/lens/persistent_context.ts | 127 ++++++++++++++- .../fixtures/kbn_archiver/lens/default.json | 153 ++++++++++++++++++ .../test/functional/page_objects/lens_page.ts | 23 +++ 10 files changed, 344 insertions(+), 32 deletions(-) create mode 100644 x-pack/test/functional/fixtures/kbn_archiver/lens/default.json diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index aaa42979d1cd5f..5da021cad268bc 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -167,13 +167,6 @@ export async function mountApp( ? historyLocationState.payload : undefined; - // Clear app-specific filters when navigating to Lens. Necessary because Lens - // can be loaded without a full page refresh. If the user navigates to Lens from Discover - // we keep the filters - if (!initialContext) { - data.query.filterManager.setAppFilters([]); - } - if (embeddableEditorIncomingState?.searchSessionId) { data.search.session.continue(embeddableEditorIncomingState.searchSessionId); } @@ -202,7 +195,13 @@ export async function mountApp( trackUiEvent('loaded'); const initialInput = getInitialInput(props.id, props.editByValue); - lensStore.dispatch(loadInitial({ redirectCallback, initialInput, emptyState })); + // Clear app-specific filters when navigating to Lens. Necessary because Lens + // can be loaded without a full page refresh. If the user navigates to Lens from Discover + // we keep the filters + if (!initialContext) { + data.query.filterManager.setAppFilters([]); + } + lensStore.dispatch(loadInitial({ redirectCallback, initialInput })); return ( diff --git a/x-pack/plugins/lens/public/state_management/init_middleware/index.ts b/x-pack/plugins/lens/public/state_management/init_middleware/index.ts index bf13ca69e82c0d..854e08dfe83e47 100644 --- a/x-pack/plugins/lens/public/state_management/init_middleware/index.ts +++ b/x-pack/plugins/lens/public/state_management/init_middleware/index.ts @@ -23,8 +23,7 @@ export const initMiddleware = (storeDeps: LensStoreDeps) => (store: MiddlewareAP store, storeDeps, action.payload.redirectCallback, - action.payload.initialInput, - action.payload.emptyState + action.payload.initialInput ); } else if (lensSlice.actions.navigateAway.match(action)) { return unsubscribeFromExternalContext(); diff --git a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.test.tsx b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.test.tsx index 79402b698af98b..e05bb2c6437bf2 100644 --- a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.test.tsx +++ b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.test.tsx @@ -15,8 +15,6 @@ import { import { act } from 'react-dom/test-utils'; import { loadInitial } from './load_initial'; import { LensEmbeddableInput } from '../../embeddable'; -import { getPreloadedState } from '../lens_slice'; -import { LensAppState } from '..'; const defaultSavedObjectId = '1234'; const preloadedState = { @@ -167,10 +165,9 @@ describe('Mounter', () => { }), }); - const emptyState = getPreloadedState(storeDeps) as LensAppState; services.attributeService.unwrapAttributes = jest.fn(); await act(async () => { - await loadInitial(lensStore, storeDeps, jest.fn(), undefined, emptyState); + await loadInitial(lensStore, storeDeps, jest.fn(), undefined); }); expect(lensStore.getState()).toEqual({ diff --git a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts index 0be2bc9cfc00ea..9a4b2235d702a1 100644 --- a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts +++ b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts @@ -6,13 +6,13 @@ */ import { MiddlewareAPI } from '@reduxjs/toolkit'; -import { isEqual } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { LensAppState, setState } from '..'; +import { setState } from '..'; import { updateLayer, updateVisualizationState, LensStoreDeps } from '..'; import { LensEmbeddableInput, LensByReferenceInput } from '../../embeddable/embeddable'; import { getInitialDatasourceId } from '../../utils'; import { initializeDatasources } from '../../editor_frame_service/editor_frame'; +import { getPreloadedState } from '../lens_slice'; import { generateId } from '../../id_generator'; import { getVisualizeFieldSuggestions, @@ -55,17 +55,23 @@ export const getPersisted = async ({ export function loadInitial( store: MiddlewareAPI, - { + storeDeps: LensStoreDeps, + redirectCallback: (savedObjectId?: string) => void, + initialInput?: LensEmbeddableInput +) { + const { lensServices, datasourceMap, - visualizationMap, embeddableEditorIncomingState, initialContext, - }: LensStoreDeps, - redirectCallback: (savedObjectId?: string) => void, - initialInput?: LensEmbeddableInput, - emptyState?: LensAppState -) { + visualizationMap, + } = storeDeps; + const { + resolvedDateRange, + searchSessionId, + isLinkedToOriginatingApp, + ...emptyState + } = getPreloadedState(storeDeps); const { getState, dispatch } = store; const { attributeService, notifications, data, dashboardFeatureFlag } = lensServices; const { persistedDoc } = getState().lens; @@ -115,11 +121,11 @@ export function loadInitial( switchToSuggestion(dispatch, selectedSuggestion, 'SWITCH_VISUALIZATION'); } } + const activeDatasourceId = getInitialDatasourceId(datasourceMap); const visualization = getState().lens.visualization; const activeVisualization = visualization.activeId && visualizationMap[visualization.activeId]; - if (visualization.state === null && activeVisualization) { const newLayerId = generateId(); @@ -158,10 +164,9 @@ export function loadInitial( initialInput.savedObjectId ); } + const filters = injectFilterReferences(doc.state.filters, doc.references); // Don't overwrite any pinned filters - data.query.filterManager.setAppFilters( - injectFilterReferences(doc.state.filters, doc.references) - ); + data.query.filterManager.setAppFilters(filters); const docDatasourceStates = Object.entries(doc.state.datasourceStates).reduce( (stateMap, [datasourceId, datasourceState]) => ({ @@ -190,6 +195,8 @@ export function loadInitial( dispatch( setState({ + isSaveable: true, + filters, query: doc.state.query, searchSessionId: dashboardFeatureFlag.allowByValueEmbeddables && @@ -198,7 +205,7 @@ export function loadInitial( currentSessionId ? currentSessionId : data.search.session.start(), - ...(!isEqual(persistedDoc, doc) ? { persistedDoc: doc } : null), + persistedDoc: doc, activeDatasourceId, visualization: { activeId: doc.visualizationType, diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index 85cb79f6ea5da6..cbbc4b7e3ed6a7 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -48,9 +48,11 @@ export const getPreloadedState = ({ const state = { ...initialState, isLoading: true, - query: data.query.queryString.getQuery(), // Do not use app-specific filters from previous app, // only if Lens was opened with the intention to visualize a field (e.g. coming from Discover) + query: !initialContext + ? data.query.queryString.getDefaultQuery() + : data.query.queryString.getQuery(), filters: !initialContext ? data.query.filterManager.getGlobalFilters() : data.query.filterManager.getFilters(), @@ -300,7 +302,6 @@ export const lensSlice = createSlice({ payload: PayloadAction<{ initialInput?: LensEmbeddableInput; redirectCallback: (savedObjectId?: string) => void; - emptyState: LensAppState; }> ) => state, }, diff --git a/x-pack/test/functional/apps/lens/geo_field.ts b/x-pack/test/functional/apps/lens/geo_field.ts index 2ba833177a1355..499188683c0a47 100644 --- a/x-pack/test/functional/apps/lens/geo_field.ts +++ b/x-pack/test/functional/apps/lens/geo_field.ts @@ -15,6 +15,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should visualize geo fields in maps', async () => { await PageObjects.visualize.navigateToNewVisualization(); await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.switchDataPanelIndexPattern('logstash-*'); await PageObjects.timePicker.setAbsoluteRange( 'Sep 22, 2015 @ 00:00:00.000', 'Sep 22, 2015 @ 04:00:00.000' diff --git a/x-pack/test/functional/apps/lens/index.ts b/x-pack/test/functional/apps/lens/index.ts index 37f03bfb1a8f29..2fd207c3549896 100644 --- a/x-pack/test/functional/apps/lens/index.ts +++ b/x-pack/test/functional/apps/lens/index.ts @@ -11,6 +11,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { const browser = getService('browser'); const log = getService('log'); const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); describe('lens app', () => { before(async () => { @@ -18,16 +19,23 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await browser.setWindowSize(1280, 800); await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); await esArchiver.load('x-pack/test/functional/es_archives/lens/basic'); + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/default' + ); }); after(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); await esArchiver.unload('x-pack/test/functional/es_archives/lens/basic'); + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/lens/default' + ); }); describe('', function () { this.tags(['ciGroup3', 'skipFirefox']); loadTestFile(require.resolve('./smokescreen')); + loadTestFile(require.resolve('./persistent_context')); }); describe('', function () { @@ -37,7 +45,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./table')); loadTestFile(require.resolve('./runtime_fields')); loadTestFile(require.resolve('./dashboard')); - loadTestFile(require.resolve('./persistent_context')); loadTestFile(require.resolve('./colors')); loadTestFile(require.resolve('./chart_data')); loadTestFile(require.resolve('./time_shift')); diff --git a/x-pack/test/functional/apps/lens/persistent_context.ts b/x-pack/test/functional/apps/lens/persistent_context.ts index e7b99ad804cd03..8a7ac6df76496d 100644 --- a/x-pack/test/functional/apps/lens/persistent_context.ts +++ b/x-pack/test/functional/apps/lens/persistent_context.ts @@ -9,11 +9,20 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['visualize', 'lens', 'header', 'timePicker']); + const PageObjects = getPageObjects([ + 'visualize', + 'lens', + 'header', + 'timePicker', + 'common', + 'navigationalSearch', + ]); const browser = getService('browser'); const filterBar = getService('filterBar'); const appsMenu = getService('appsMenu'); const security = getService('security'); + const listingTable = getService('listingTable'); + const queryBar = getService('queryBar'); describe('lens query context', () => { before(async () => { @@ -27,6 +36,122 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await security.testUser.restoreDefaults(); }); + describe('Navigation search', () => { + describe('when opening from empty visualization to existing one', () => { + before(async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + await PageObjects.navigationalSearch.focus(); + await PageObjects.navigationalSearch.searchFor('type:lens lnsTableVis'); + await PageObjects.navigationalSearch.clickOnOption(0); + await PageObjects.lens.waitForWorkspaceWithVisualization(); + }); + it('filters, time and query reflect the visualization state', async () => { + expect(await PageObjects.lens.getDatatableHeaderText(1)).to.equal( + '404 › Median of bytes' + ); + expect(await PageObjects.lens.getDatatableHeaderText(2)).to.equal( + '503 › Median of bytes' + ); + expect(await PageObjects.lens.getDatatableCellText(0, 0)).to.eql('TG'); + expect(await PageObjects.lens.getDatatableCellText(0, 1)).to.eql('9,931'); + }); + it('preserves time range', async () => { + const timePickerValues = await PageObjects.timePicker.getTimeConfigAsAbsoluteTimes(); + expect(timePickerValues.start).to.eql(PageObjects.timePicker.defaultStartTime); + expect(timePickerValues.end).to.eql(PageObjects.timePicker.defaultEndTime); + // data is correct and top nav is correct + }); + it('loads filters', async () => { + const filterCount = await filterBar.getFilterCount(); + expect(filterCount).to.equal(1); + }); + it('loads query', async () => { + const query = await queryBar.getQueryString(); + expect(query).to.equal('extension.raw : "jpg" or extension.raw : "gif" '); + }); + }); + describe('when opening from existing visualization to empty one', () => { + before(async () => { + await PageObjects.visualize.gotoVisualizationLandingPage(); + await listingTable.searchForItemWithName('lnsTableVis'); + await PageObjects.lens.clickVisualizeListItemTitle('lnsTableVis'); + await PageObjects.lens.goToTimeRange(); + await PageObjects.navigationalSearch.focus(); + await PageObjects.navigationalSearch.searchFor('type:application lens'); + await PageObjects.navigationalSearch.clickOnOption(0); + await PageObjects.lens.waitForEmptyWorkspace(); + await PageObjects.lens.switchToVisualization('lnsMetric'); + await PageObjects.lens.dragFieldToWorkspace('@timestamp'); + }); + it('preserves time range', async () => { + // fill the navigation search and select empty + // see the time + const timePickerValues = await PageObjects.timePicker.getTimeConfigAsAbsoluteTimes(); + expect(timePickerValues.start).to.eql(PageObjects.timePicker.defaultStartTime); + expect(timePickerValues.end).to.eql(PageObjects.timePicker.defaultEndTime); + }); + it('cleans filters', async () => { + const filterCount = await filterBar.getFilterCount(); + expect(filterCount).to.equal(0); + }); + it('cleans query', async () => { + const query = await queryBar.getQueryString(); + expect(query).to.equal(''); + }); + it('filters, time and query reflect the visualization state', async () => { + await PageObjects.lens.assertMetric('Unique count of @timestamp', '14,181'); + }); + }); + }); + + describe('Switching in Visualize App', () => { + it('when moving from existing to empty workspace, preserves time range, cleans filters and query', async () => { + // go to existing vis + await PageObjects.visualize.gotoVisualizationLandingPage(); + await listingTable.searchForItemWithName('lnsTableVis'); + await PageObjects.lens.clickVisualizeListItemTitle('lnsTableVis'); + await PageObjects.lens.goToTimeRange(); + // go to empty vis + await PageObjects.lens.goToListingPageViaBreadcrumbs(); + await PageObjects.visualize.clickNewVisualization(); + await PageObjects.visualize.waitForGroupsSelectPage(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.waitForEmptyWorkspace(); + await PageObjects.lens.switchToVisualization('lnsMetric'); + await PageObjects.lens.dragFieldToWorkspace('@timestamp'); + + const timePickerValues = await PageObjects.timePicker.getTimeConfigAsAbsoluteTimes(); + expect(timePickerValues.start).to.eql(PageObjects.timePicker.defaultStartTime); + expect(timePickerValues.end).to.eql(PageObjects.timePicker.defaultEndTime); + const filterCount = await filterBar.getFilterCount(); + expect(filterCount).to.equal(0); + const query = await queryBar.getQueryString(); + expect(query).to.equal(''); + await PageObjects.lens.assertMetric('Unique count of @timestamp', '14,181'); + }); + it('when moving from empty to existing workspace, preserves time range and loads filters and query', async () => { + // go to existing vis + await PageObjects.lens.goToListingPageViaBreadcrumbs(); + await listingTable.searchForItemWithName('lnsTableVis'); + await PageObjects.lens.clickVisualizeListItemTitle('lnsTableVis'); + + expect(await PageObjects.lens.getDatatableHeaderText(1)).to.equal('404 › Median of bytes'); + expect(await PageObjects.lens.getDatatableHeaderText(2)).to.equal('503 › Median of bytes'); + expect(await PageObjects.lens.getDatatableCellText(0, 0)).to.eql('TG'); + expect(await PageObjects.lens.getDatatableCellText(0, 1)).to.eql('9,931'); + + const timePickerValues = await PageObjects.timePicker.getTimeConfigAsAbsoluteTimes(); + expect(timePickerValues.start).to.eql(PageObjects.timePicker.defaultStartTime); + expect(timePickerValues.end).to.eql(PageObjects.timePicker.defaultEndTime); + const filterCount = await filterBar.getFilterCount(); + expect(filterCount).to.equal(1); + const query = await queryBar.getQueryString(); + expect(query).to.equal('extension.raw : "jpg" or extension.raw : "gif" '); + }); + }); + it('should carry over time range and pinned filters to discover', async () => { await PageObjects.visualize.navigateToNewVisualization(); await PageObjects.visualize.clickVisType('lens'); diff --git a/x-pack/test/functional/fixtures/kbn_archiver/lens/default.json b/x-pack/test/functional/fixtures/kbn_archiver/lens/default.json new file mode 100644 index 00000000000000..5ef26ebee4d982 --- /dev/null +++ b/x-pack/test/functional/fixtures/kbn_archiver/lens/default.json @@ -0,0 +1,153 @@ +{ + "attributes": { + "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"nestedField.child\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true,\"subType\":{\"nested\":{\"path\":\"nestedField\"}}}]", + "timeFieldName": "@timestamp", + "title": "logstash-*" + }, + "coreMigrationVersion": "7.15.0", + "id": "logstash-*", + "migrationVersion": { + "index-pattern": "7.11.0" + }, + "references": [], + "type": "index-pattern", + "version": "WzIsMl0=" +} + +{ + "attributes": { + "description": "", + "state": { + "datasourceStates": { + "indexpattern": { + "layers": { + "4ba1a1be-6e67-434b-b3a0-f30db8ea5395": { + "columnOrder": [ + "70d52318-354d-47d5-b33b-43d50eb34425", + "bafe3009-1776-4227-a0fe-b0d6ccbb4961", + "3dc0bd55-2087-4e60-aea2-f9910714f7db" + ], + "columns": { + "3dc0bd55-2087-4e60-aea2-f9910714f7db": { + "dataType": "number", + "isBucketed": false, + "label": "Median of bytes", + "operationType": "median", + "scale": "ratio", + "sourceField": "bytes" + }, + "70d52318-354d-47d5-b33b-43d50eb34425": { + "dataType": "string", + "isBucketed": true, + "label": "Top values of response.raw", + "operationType": "terms", + "params": { + "missingBucket": false, + "orderBy": { + "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", + "type": "column" + }, + "orderDirection": "desc", + "otherBucket": true, + "size": 3 + }, + "scale": "ordinal", + "sourceField": "response.raw" + }, + "bafe3009-1776-4227-a0fe-b0d6ccbb4961": { + "dataType": "string", + "isBucketed": true, + "label": "Top values of geo.src", + "operationType": "terms", + "params": { + "orderBy": { + "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", + "type": "column" + }, + "orderDirection": "desc", + "size": 7 + }, + "scale": "ordinal", + "sourceField": "geo.src" + } + }, + "incompleteColumns": {} + } + } + } + }, + "filters": [ + { + "$state": { + "store": "appState" + }, + "meta": { + "alias": null, + "disabled": false, + "indexRefName": "filter-index-pattern-0", + "key": "response", + "negate": true, + "params": { + "query": "200" + }, + "type": "phrase" + }, + "query": { + "match_phrase": { + "response": "200" + } + } + } + ], + "query": { + "language": "kuery", + "query": "extension.raw : \"jpg\" or extension.raw : \"gif\" " + }, + "visualization": { + "columns": [ + { + "columnId": "bafe3009-1776-4227-a0fe-b0d6ccbb4961", + "isTransposed": false + }, + { + "columnId": "3dc0bd55-2087-4e60-aea2-f9910714f7db", + "isTransposed": false + }, + { + "columnId": "70d52318-354d-47d5-b33b-43d50eb34425", + "isTransposed": true + } + ], + "layerId": "4ba1a1be-6e67-434b-b3a0-f30db8ea5395", + "layerType": "data" + } + }, + "title": "lnsTableVis", + "visualizationType": "lnsDatatable" +}, + "coreMigrationVersion": "7.15.0", + "id": "a800e2b0-268c-11ec-b2b6-f1bd289a74d4", + "migrationVersion": { + "lens": "7.15.0" + }, + "references": [ + { + "id": "logstash-*", + "name": "indexpattern-datasource-current-indexpattern", + "type": "index-pattern" + }, + { + "id": "logstash-*", + "name": "indexpattern-datasource-layer-4ba1a1be-6e67-434b-b3a0-f30db8ea5395", + "type": "index-pattern" + }, + { + "id": "logstash-*", + "name": "filter-index-pattern-0", + "type": "index-pattern" + } + ], + "type": "lens", + "updated_at": "2020-11-23T19:57:52.834Z", + "version": "WzUyLDJd" +} \ No newline at end of file diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index 2e1151602f3110..6f0c9cb9462e14 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -247,6 +247,18 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont }); }, + async waitForEmptyWorkspace() { + await retry.try(async () => { + await testSubjects.existOrFail(`empty-workspace`); + }); + }, + + async waitForWorkspaceWithVisualization() { + await retry.try(async () => { + await testSubjects.existOrFail(`lnsVisualizationContainer`); + }); + }, + async waitForFieldMissing(field: string) { await retry.try(async () => { await testSubjects.missingOrFail(`lnsFieldListPanelField-${field}`); @@ -1079,6 +1091,17 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont await testSubjects.click('lnsFormula-fullscreen'); }, + async goToListingPageViaBreadcrumbs() { + await retry.try(async () => { + await testSubjects.click('breadcrumb first'); + if (await testSubjects.exists('appLeaveConfirmModal')) { + await testSubjects.exists('confirmModalConfirmButton'); + await testSubjects.click('confirmModalConfirmButton'); + } + await testSubjects.existOrFail('visualizationLandingPage', { timeout: 3000 }); + }); + }, + async typeFormula(formula: string) { await find.byCssSelector('.monaco-editor'); await find.clickByCssSelectorWhenNotDisabled('.monaco-editor'); From 9e554d9ea6318eef0394fb5f9e177864944fa2ed Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Tue, 19 Oct 2021 11:01:15 -0400 Subject: [PATCH 05/34] skip flaky suite (#106051) --- .../test/security_solution_endpoint_api_int/apis/metadata.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts index cadb9a420708ad..116a6ebf8b57df 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts @@ -23,7 +23,8 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); - describe('test metadata api', () => { + // Failing: See https://github.com/elastic/kibana/issues/106051 + describe.skip('test metadata api', () => { describe(`POST ${HOST_METADATA_LIST_ROUTE} when index is empty`, () => { it('metadata api should return empty result when index is empty', async () => { await deleteMetadataStream(getService); From b5b03be93ac928d2ade5030e2d3817d396c5f412 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Tue, 19 Oct 2021 19:27:45 +0200 Subject: [PATCH 06/34] [ML] APM Correlations: Get trace samples tab overall distribution via APM endpoint. (#114615) (#115483) This creates an APM API endpoint that fetches data for the latency distribution chart in the trace samples tab on the transactions page. Previously, this data was fetched via the custom Kibana search strategies used for APM Correlations which causes issues in load balancing setups. --- .../search_strategies/correlations/types.ts | 4 +- .../distribution/index.test.tsx | 51 ++---- .../distribution/index.tsx | 65 +++---- .../use_transaction_distribution_fetcher.ts | 160 ------------------ .../get_overall_latency_distribution.ts | 125 ++++++++++++++ .../latency/get_percentile_threshold_value.ts | 56 ++++++ .../plugins/apm/server/lib/latency/types.ts | 21 +++ .../queries/get_query_with_params.ts | 21 ++- .../queries/query_histogram_range_steps.ts | 6 +- .../get_global_apm_server_route_repository.ts | 2 + .../apm/server/routes/latency_distribution.ts | 60 +++++++ .../test/apm_api_integration/tests/index.ts | 4 + .../latency_overall_distribution.ts | 64 +++++++ 13 files changed, 404 insertions(+), 235 deletions(-) delete mode 100644 x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts create mode 100644 x-pack/plugins/apm/server/lib/latency/get_overall_latency_distribution.ts create mode 100644 x-pack/plugins/apm/server/lib/latency/get_percentile_threshold_value.ts create mode 100644 x-pack/plugins/apm/server/lib/latency/types.ts create mode 100644 x-pack/plugins/apm/server/routes/latency_distribution.ts create mode 100644 x-pack/test/apm_api_integration/tests/transactions/latency_overall_distribution.ts diff --git a/x-pack/plugins/apm/common/search_strategies/correlations/types.ts b/x-pack/plugins/apm/common/search_strategies/correlations/types.ts index 886c5fd6161d8d..ef8ab761df2680 100644 --- a/x-pack/plugins/apm/common/search_strategies/correlations/types.ts +++ b/x-pack/plugins/apm/common/search_strategies/correlations/types.ts @@ -24,8 +24,8 @@ export interface SearchServiceParams { serviceName?: string; transactionName?: string; transactionType?: string; - start?: string; - end?: string; + start?: string | number; + end?: string | number; percentileThreshold?: number; analyzeCorrelations?: boolean; } diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.tsx index 5a9977b373c336..a518d2b49316dc 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.test.tsx @@ -8,43 +8,24 @@ import { render, screen, waitFor } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import React, { ReactNode } from 'react'; -import { of } from 'rxjs'; import { CoreStart } from 'kibana/public'; import { merge } from 'lodash'; -import { dataPluginMock } from 'src/plugins/data/public/mocks'; -import type { IKibanaSearchResponse } from 'src/plugins/data/public'; import { EuiThemeProvider } from 'src/plugins/kibana_react/common'; import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; -import type { SearchServiceRawResponse } from '../../../../../common/search_strategies/correlations/types'; import { MockUrlParamsContextProvider } from '../../../../context/url_params_context/mock_url_params_context_provider'; import { ApmPluginContextValue } from '../../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, } from '../../../../context/apm_plugin/mock_apm_plugin_context'; +import * as useFetcherModule from '../../../../hooks/use_fetcher'; import { fromQuery } from '../../../shared/Links/url_helpers'; import { getFormattedSelection, TransactionDistribution } from './index'; -function Wrapper({ - children, - dataSearchResponse, -}: { - children?: ReactNode; - dataSearchResponse: IKibanaSearchResponse; -}) { - const mockDataSearch = jest.fn(() => of(dataSearchResponse)); - - const dataPluginMockStart = dataPluginMock.createStartContract(); +function Wrapper({ children }: { children?: ReactNode }) { const KibanaReactContext = createKibanaReactContext({ - data: { - ...dataPluginMockStart, - search: { - ...dataPluginMockStart.search, - search: mockDataSearch, - }, - }, usageCollection: { reportUiCounter: () => {} }, } as Partial); @@ -102,13 +83,15 @@ describe('transaction_details/distribution', () => { describe('TransactionDistribution', () => { it('shows loading indicator when the service is running and returned no results yet', async () => { const onHasData = jest.fn(); + + jest.spyOn(useFetcherModule, 'useFetcher').mockImplementation(() => ({ + data: {}, + refetch: () => {}, + status: useFetcherModule.FETCH_STATUS.LOADING, + })); + render( - + { it("doesn't show loading indicator when the service isn't running", async () => { const onHasData = jest.fn(); + + jest.spyOn(useFetcherModule, 'useFetcher').mockImplementation(() => ({ + data: { percentileThresholdValue: 1234, overallHistogram: [] }, + refetch: () => {}, + status: useFetcherModule.FETCH_STATUS.SUCCESS, + })); + render( - + { - startFetch({ - environment, - kuery, + const { data = { log: [] }, status, error } = useFetcher( + (callApmApi) => { + if (serviceName && environment && start && end) { + return callApmApi({ + endpoint: 'GET /api/apm/latency/overall_distribution', + params: { + query: { + serviceName, + transactionName, + transactionType, + kuery, + environment, + start, + end, + percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD, + }, + }, + }); + } + }, + [ serviceName, transactionName, transactionType, + kuery, + environment, start, end, - percentileThreshold: DEFAULT_PERCENTILE_THRESHOLD, - }); - }, [ - startFetch, - environment, - serviceName, - transactionName, - transactionType, - kuery, - start, - end, - ]); + ] + ); - useEffect(() => { - startFetchHandler(); - return cancelFetch; - }, [cancelFetch, startFetchHandler]); + const overallHistogram = + data.overallHistogram === undefined && status !== FETCH_STATUS.LOADING + ? [] + : data.overallHistogram; useEffect(() => { if (isErrorMessage(error)) { @@ -230,11 +231,11 @@ export function TransactionDistribution({ diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts deleted file mode 100644 index 2ff1b83ef17829..00000000000000 --- a/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts +++ /dev/null @@ -1,160 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useCallback, useRef, useState } from 'react'; -import type { Subscription } from 'rxjs'; -import { - IKibanaSearchRequest, - IKibanaSearchResponse, - isCompleteResponse, - isErrorResponse, -} from '../../../../../src/plugins/data/public'; -import type { - SearchServiceParams, - SearchServiceRawResponse, -} from '../../common/search_strategies/correlations/types'; -import { useKibana } from '../../../../../src/plugins/kibana_react/public'; -import { ApmPluginStartDeps } from '../plugin'; - -interface TransactionDistributionFetcherState { - error?: Error; - isComplete: boolean; - isRunning: boolean; - loaded: number; - ccsWarning: SearchServiceRawResponse['ccsWarning']; - log: SearchServiceRawResponse['log']; - transactionDistribution?: SearchServiceRawResponse['overallHistogram']; - percentileThresholdValue?: SearchServiceRawResponse['percentileThresholdValue']; - timeTook?: number; - total: number; -} - -export function useTransactionDistributionFetcher() { - const { - services: { data }, - } = useKibana(); - - const [ - fetchState, - setFetchState, - ] = useState({ - isComplete: false, - isRunning: false, - loaded: 0, - ccsWarning: false, - log: [], - total: 100, - }); - - const abortCtrl = useRef(new AbortController()); - const searchSubscription$ = useRef(); - - function setResponse( - response: IKibanaSearchResponse - ) { - setFetchState((prevState) => ({ - ...prevState, - isRunning: response.isRunning || false, - ccsWarning: response.rawResponse?.ccsWarning ?? false, - histograms: response.rawResponse?.values ?? [], - log: response.rawResponse?.log ?? [], - loaded: response.loaded!, - total: response.total!, - timeTook: response.rawResponse.took, - // only set percentileThresholdValue and overallHistogram once it's repopulated on a refresh, - // otherwise the consuming chart would flicker with an empty state on reload. - ...(response.rawResponse?.percentileThresholdValue !== undefined && - response.rawResponse?.overallHistogram !== undefined - ? { - transactionDistribution: response.rawResponse?.overallHistogram, - percentileThresholdValue: - response.rawResponse?.percentileThresholdValue, - } - : {}), - // if loading is done but didn't return any data for the overall histogram, - // set it to an empty array so the consuming chart component knows loading is done. - ...(!response.isRunning && - response.rawResponse?.overallHistogram === undefined - ? { transactionDistribution: [] } - : {}), - })); - } - - const startFetch = useCallback( - (params: Omit) => { - setFetchState((prevState) => ({ - ...prevState, - error: undefined, - isComplete: false, - })); - searchSubscription$.current?.unsubscribe(); - abortCtrl.current.abort(); - abortCtrl.current = new AbortController(); - - const searchServiceParams: SearchServiceParams = { - ...params, - analyzeCorrelations: false, - }; - const req = { params: searchServiceParams }; - - // Submit the search request using the `data.search` service. - searchSubscription$.current = data.search - .search< - IKibanaSearchRequest, - IKibanaSearchResponse - >(req, { - strategy: 'apmCorrelationsSearchStrategy', - abortSignal: abortCtrl.current.signal, - }) - .subscribe({ - next: (res: IKibanaSearchResponse) => { - setResponse(res); - if (isCompleteResponse(res)) { - searchSubscription$.current?.unsubscribe(); - setFetchState((prevState) => ({ - ...prevState, - isRunnning: false, - isComplete: true, - })); - } else if (isErrorResponse(res)) { - searchSubscription$.current?.unsubscribe(); - setFetchState((prevState) => ({ - ...prevState, - error: (res as unknown) as Error, - isRunning: false, - })); - } - }, - error: (error: Error) => { - setFetchState((prevState) => ({ - ...prevState, - error, - isRunning: false, - })); - }, - }); - }, - [data.search, setFetchState] - ); - - const cancelFetch = useCallback(() => { - searchSubscription$.current?.unsubscribe(); - searchSubscription$.current = undefined; - abortCtrl.current.abort(); - setFetchState((prevState) => ({ - ...prevState, - isRunning: false, - })); - }, [setFetchState]); - - return { - ...fetchState, - progress: fetchState.loaded / fetchState.total, - startFetch, - cancelFetch, - }; -} diff --git a/x-pack/plugins/apm/server/lib/latency/get_overall_latency_distribution.ts b/x-pack/plugins/apm/server/lib/latency/get_overall_latency_distribution.ts new file mode 100644 index 00000000000000..fb77013c364486 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/latency/get_overall_latency_distribution.ts @@ -0,0 +1,125 @@ +/* + * 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 type { estypes } from '@elastic/elasticsearch'; + +import { ProcessorEvent } from '../../../common/processor_event'; + +import { withApmSpan } from '../../utils/with_apm_span'; + +import { + getHistogramIntervalRequest, + getHistogramRangeSteps, +} from '../search_strategies/correlations/queries/query_histogram_range_steps'; +import { getTransactionDurationRangesRequest } from '../search_strategies/correlations/queries/query_ranges'; + +import { getPercentileThresholdValue } from './get_percentile_threshold_value'; +import type { + OverallLatencyDistributionOptions, + OverallLatencyDistributionResponse, +} from './types'; + +export async function getOverallLatencyDistribution( + options: OverallLatencyDistributionOptions +) { + return withApmSpan('get_overall_latency_distribution', async () => { + const overallLatencyDistribution: OverallLatencyDistributionResponse = { + log: [], + }; + + const { setup, ...rawParams } = options; + const { apmEventClient, start, end } = setup; + const params = { + // pass on an empty index because we're using only the body attribute + // of the request body getters we're reusing from search strategies. + index: '', + start, + end, + ...rawParams, + }; + + // #1: get 95th percentile to be displayed as a marker in the log log chart + overallLatencyDistribution.percentileThresholdValue = await getPercentileThresholdValue( + options + ); + + // finish early if we weren't able to identify the percentileThresholdValue. + if (!overallLatencyDistribution.percentileThresholdValue) { + return overallLatencyDistribution; + } + + // #2: get histogram range steps + const steps = 100; + + const { body: histogramIntervalRequestBody } = getHistogramIntervalRequest( + params + ); + + const histogramIntervalResponse = (await apmEventClient.search( + 'get_histogram_interval', + { + // TODO: add support for metrics + apm: { events: [ProcessorEvent.transaction] }, + body: histogramIntervalRequestBody, + } + )) as { + aggregations?: { + transaction_duration_min: estypes.AggregationsValueAggregate; + transaction_duration_max: estypes.AggregationsValueAggregate; + }; + hits: { total: estypes.SearchTotalHits }; + }; + + if ( + !histogramIntervalResponse.aggregations || + histogramIntervalResponse.hits.total.value === 0 + ) { + return overallLatencyDistribution; + } + + const min = + histogramIntervalResponse.aggregations.transaction_duration_min.value; + const max = + histogramIntervalResponse.aggregations.transaction_duration_max.value * 2; + + const histogramRangeSteps = getHistogramRangeSteps(min, max, steps); + + // #3: get histogram chart data + const { + body: transactionDurationRangesRequestBody, + } = getTransactionDurationRangesRequest(params, histogramRangeSteps); + + const transactionDurationRangesResponse = (await apmEventClient.search( + 'get_transaction_duration_ranges', + { + // TODO: add support for metrics + apm: { events: [ProcessorEvent.transaction] }, + body: transactionDurationRangesRequestBody, + } + )) as { + aggregations?: { + logspace_ranges: estypes.AggregationsMultiBucketAggregate<{ + from: number; + doc_count: number; + }>; + }; + }; + + if (!transactionDurationRangesResponse.aggregations) { + return overallLatencyDistribution; + } + + overallLatencyDistribution.overallHistogram = transactionDurationRangesResponse.aggregations.logspace_ranges.buckets + .map((d) => ({ + key: d.from, + doc_count: d.doc_count, + })) + .filter((d) => d.key !== undefined); + + return overallLatencyDistribution; + }); +} diff --git a/x-pack/plugins/apm/server/lib/latency/get_percentile_threshold_value.ts b/x-pack/plugins/apm/server/lib/latency/get_percentile_threshold_value.ts new file mode 100644 index 00000000000000..28866402ce4918 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/latency/get_percentile_threshold_value.ts @@ -0,0 +1,56 @@ +/* + * 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 type { estypes } from '@elastic/elasticsearch'; + +import { ProcessorEvent } from '../../../common/processor_event'; + +import { getTransactionDurationPercentilesRequest } from '../search_strategies/correlations/queries/query_percentiles'; + +import type { OverallLatencyDistributionOptions } from './types'; + +export async function getPercentileThresholdValue( + options: OverallLatencyDistributionOptions +) { + const { setup, percentileThreshold, ...rawParams } = options; + const { apmEventClient, start, end } = setup; + const params = { + // pass on an empty index because we're using only the body attribute + // of the request body getters we're reusing from search strategies. + index: '', + start, + end, + ...rawParams, + }; + + const { + body: transactionDurationPercentilesRequestBody, + } = getTransactionDurationPercentilesRequest(params, [percentileThreshold]); + + const transactionDurationPercentilesResponse = (await apmEventClient.search( + 'get_transaction_duration_percentiles', + { + // TODO: add support for metrics + apm: { events: [ProcessorEvent.transaction] }, + body: transactionDurationPercentilesRequestBody, + } + )) as { + aggregations?: { + transaction_duration_percentiles: estypes.AggregationsTDigestPercentilesAggregate; + }; + }; + + if (!transactionDurationPercentilesResponse.aggregations) { + return; + } + + const percentilesResponseThresholds = + transactionDurationPercentilesResponse.aggregations + .transaction_duration_percentiles?.values ?? {}; + + return percentilesResponseThresholds[`${percentileThreshold}.0`]; +} diff --git a/x-pack/plugins/apm/server/lib/latency/types.ts b/x-pack/plugins/apm/server/lib/latency/types.ts new file mode 100644 index 00000000000000..deb4e5d6fe7b56 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/latency/types.ts @@ -0,0 +1,21 @@ +/* + * 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 { CorrelationsOptions } from '../correlations/get_filters'; + +export interface OverallLatencyDistributionOptions extends CorrelationsOptions { + percentileThreshold: number; +} + +export interface OverallLatencyDistributionResponse { + log: string[]; + percentileThresholdValue?: number; + overallHistogram?: Array<{ + key: number; + doc_count: number; + }>; +} diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_query_with_params.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_query_with_params.ts index 8bd9f3d4e582c9..3c283a8b01da2d 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_query_with_params.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/get_query_with_params.ts @@ -42,13 +42,20 @@ export const getQueryWithParams = ({ transactionName, } = params; - // converts string based start/end to epochmillis - const setup = pipe( - rangeRt.decode({ start, end }), - getOrElse((errors) => { - throw new Error(failure(errors).join('\n')); - }) - ) as Setup & SetupTimeRange; + let setup = { start: 0, end: 0 } as Setup & SetupTimeRange; + + if (typeof start === 'number' && typeof end === 'number') { + setup.start = start; + setup.end = end; + } else if (typeof start === 'string' && typeof end === 'string') { + // converts string based start/end to epochmillis + setup = pipe( + rangeRt.decode({ start, end }), + getOrElse((errors) => { + throw new Error(failure(errors).join('\n')); + }) + ) as Setup & SetupTimeRange; + } const filters = getCorrelationsFilters({ setup, diff --git a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram_range_steps.ts b/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram_range_steps.ts index 116b5d16456015..bbc78520ace8ae 100644 --- a/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram_range_steps.ts +++ b/x-pack/plugins/apm/server/lib/search_strategies/correlations/queries/query_histogram_range_steps.ts @@ -17,7 +17,11 @@ import type { SearchServiceFetchParams } from '../../../../../common/search_stra import { getQueryWithParams } from './get_query_with_params'; import { getRequestBase } from './get_request_base'; -const getHistogramRangeSteps = (min: number, max: number, steps: number) => { +export const getHistogramRangeSteps = ( + min: number, + max: number, + steps: number +) => { // A d3 based scale function as a helper to get equally distributed bins on a log scale. const logFn = scaleLog().domain([min, max]).range([1, steps]); return [...Array(steps).keys()] diff --git a/x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts b/x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts index b66daf80bd7634..a5f0db9cf81a84 100644 --- a/x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts +++ b/x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts @@ -18,6 +18,7 @@ import { environmentsRouteRepository } from './environments'; import { errorsRouteRepository } from './errors'; import { apmFleetRouteRepository } from './fleet'; import { indexPatternRouteRepository } from './index_pattern'; +import { latencyDistributionRouteRepository } from './latency_distribution'; import { metricsRouteRepository } from './metrics'; import { observabilityOverviewRouteRepository } from './observability_overview'; import { rumRouteRepository } from './rum_client'; @@ -39,6 +40,7 @@ const getTypedGlobalApmServerRouteRepository = () => { .merge(indexPatternRouteRepository) .merge(environmentsRouteRepository) .merge(errorsRouteRepository) + .merge(latencyDistributionRouteRepository) .merge(metricsRouteRepository) .merge(observabilityOverviewRouteRepository) .merge(rumRouteRepository) diff --git a/x-pack/plugins/apm/server/routes/latency_distribution.ts b/x-pack/plugins/apm/server/routes/latency_distribution.ts new file mode 100644 index 00000000000000..eef2ffb75d996c --- /dev/null +++ b/x-pack/plugins/apm/server/routes/latency_distribution.ts @@ -0,0 +1,60 @@ +/* + * 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 * as t from 'io-ts'; +import { toNumberRt } from '@kbn/io-ts-utils'; +import { getOverallLatencyDistribution } from '../lib/latency/get_overall_latency_distribution'; +import { setupRequest } from '../lib/helpers/setup_request'; +import { createApmServerRoute } from './create_apm_server_route'; +import { createApmServerRouteRepository } from './create_apm_server_route_repository'; +import { environmentRt, kueryRt, rangeRt } from './default_api_types'; + +const latencyOverallDistributionRoute = createApmServerRoute({ + endpoint: 'GET /api/apm/latency/overall_distribution', + params: t.type({ + query: t.intersection([ + t.partial({ + serviceName: t.string, + transactionName: t.string, + transactionType: t.string, + }), + environmentRt, + kueryRt, + rangeRt, + t.type({ + percentileThreshold: toNumberRt, + }), + ]), + }), + options: { tags: ['access:apm'] }, + handler: async (resources) => { + const setup = await setupRequest(resources); + + const { + environment, + kuery, + serviceName, + transactionType, + transactionName, + percentileThreshold, + } = resources.params.query; + + return getOverallLatencyDistribution({ + environment, + kuery, + serviceName, + transactionType, + transactionName, + percentileThreshold, + setup, + }); + }, +}); + +export const latencyDistributionRouteRepository = createApmServerRouteRepository().add( + latencyOverallDistributionRoute +); diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts index c8a57bc613a929..bc7e40a46c0baa 100644 --- a/x-pack/test/apm_api_integration/tests/index.ts +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -171,6 +171,10 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte loadTestFile(require.resolve('./transactions/error_rate')); }); + describe('transactions/latency_overall_distribution', function () { + loadTestFile(require.resolve('./transactions/latency_overall_distribution')); + }); + describe('transactions/latency', function () { loadTestFile(require.resolve('./transactions/latency')); }); diff --git a/x-pack/test/apm_api_integration/tests/transactions/latency_overall_distribution.ts b/x-pack/test/apm_api_integration/tests/transactions/latency_overall_distribution.ts new file mode 100644 index 00000000000000..2ad2dc41afb481 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/transactions/latency_overall_distribution.ts @@ -0,0 +1,64 @@ +/* + * 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 url from 'url'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + // This matches the parameters used for the other tab's search strategy approach in `../correlations/*`. + const getQuery = () => ({ + environment: 'ENVIRONMENT_ALL', + start: '2020', + end: '2021', + kuery: '', + percentileThreshold: '95', + }); + + registry.when( + 'latency overall distribution without data', + { config: 'trial', archives: [] }, + () => { + it('handles the empty state', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/latency/overall_distribution`, + query: getQuery(), + }) + ); + + expect(response.status).to.be(200); + expect(response.body?.percentileThresholdValue).to.be(undefined); + expect(response.body?.overallHistogram?.length).to.be(undefined); + }); + } + ); + + registry.when( + 'latency overall distribution with data and default args', + // This uses the same archive used for the other tab's search strategy approach in `../correlations/*`. + { config: 'trial', archives: ['8.0.0'] }, + () => { + it('returns percentileThresholdValue and overall histogram', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/latency/overall_distribution`, + query: getQuery(), + }) + ); + + expect(response.status).to.eql(200); + // This matches the values returned for the other tab's search strategy approach in `../correlations/*`. + expect(response.body?.percentileThresholdValue).to.be(1309695.875); + expect(response.body?.overallHistogram?.length).to.be(101); + }); + } + ); +} From 055ab5bd2d0ac65335fdf5f2f9e5fe2bbe4a67b9 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 19 Oct 2021 14:26:23 -0400 Subject: [PATCH 07/34] :bug: Fix single percentile case when ES is returning no buckets (#115214) (#115568) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Marco Liberati --- .../common/search/aggs/metrics/single_percentile.test.ts | 5 +++++ .../data/common/search/aggs/metrics/single_percentile.ts | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/plugins/data/common/search/aggs/metrics/single_percentile.test.ts b/src/plugins/data/common/search/aggs/metrics/single_percentile.test.ts index c2ba6ee1a403a3..967e1b1f624aa4 100644 --- a/src/plugins/data/common/search/aggs/metrics/single_percentile.test.ts +++ b/src/plugins/data/common/search/aggs/metrics/single_percentile.test.ts @@ -73,6 +73,11 @@ describe('AggTypeMetricSinglePercentileProvider class', () => { ).toEqual(123); }); + it('should not throw error for empty buckets', () => { + const agg = aggConfigs.getResponseAggs()[0]; + expect(agg.getValue({})).toEqual(NaN); + }); + it('produces the expected expression ast', () => { const agg = aggConfigs.getResponseAggs()[0]; expect(agg.toExpressionAst()).toMatchInlineSnapshot(` diff --git a/src/plugins/data/common/search/aggs/metrics/single_percentile.ts b/src/plugins/data/common/search/aggs/metrics/single_percentile.ts index 4bdafcae327cdd..954576e2bbe1fc 100644 --- a/src/plugins/data/common/search/aggs/metrics/single_percentile.ts +++ b/src/plugins/data/common/search/aggs/metrics/single_percentile.ts @@ -57,7 +57,9 @@ export const getSinglePercentileMetricAgg = () => { if (Number.isInteger(agg.params.percentile)) { valueKey += '.0'; } - return bucket[agg.id].values[valueKey]; + const { values } = bucket[agg.id] ?? {}; + + return values ? values[valueKey] : NaN; }, }); }; From 24e0ca9ae4b449d75d24cee59c7920baa80ae14a Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 19 Oct 2021 18:33:22 -0400 Subject: [PATCH 08/34] [Security Solution] Fix height issue (#114718) (#115646) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Michael Olorunnisola --- .../public/components/t_grid/body/height_hack.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts b/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts index 5371d7004a8646..26d32b13eede7f 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts +++ b/x-pack/plugins/timelines/public/components/t_grid/body/height_hack.ts @@ -35,6 +35,11 @@ const DATA_GRID_HEIGHT_BY_PAGE_SIZE: { [key: number]: number } = { * * Please delete me and allow DataGrid to calculate its height when the bug is fixed. */ + +const dataGridRowHeight = 36; +const headerSectionHeight = 32; +const additionalFiltersHeight = 44; + export const useDataGridHeightHack = (pageSize: number, rowCount: number) => { const [height, setHeight] = useState(DATA_GRID_HEIGHT_BY_PAGE_SIZE[pageSize]); @@ -44,7 +49,11 @@ export const useDataGridHeightHack = (pageSize: number, rowCount: number) => { if (rowCount === pageSize) { setHeight(DATA_GRID_HEIGHT_BY_PAGE_SIZE[pageSize]); + } else if (rowCount <= pageSize) { + // This is unnecessary if we add rowCount > pageSize below + setHeight(dataGridRowHeight * rowCount + (headerSectionHeight + additionalFiltersHeight)); } else if ( + // rowCount > pageSize && // This will fix the issue but is always full height so has a lot of empty state gridVirtualized && gridVirtualized.children[0].clientHeight !== gridVirtualized.clientHeight // check if it has vertical scroll ) { From 2910f6bc9fa5bda464ad47285117def657dbaf0a Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 20 Oct 2021 07:37:40 -0400 Subject: [PATCH 09/34] Fix maps font path (#115453) (#115720) * fixed font path * fix functional tests * fix directory issue Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Thom Heymann <190132+thomheymann@users.noreply.github.com> --- x-pack/plugins/maps/server/routes.js | 5 ++-- .../api_integration/apis/maps/fonts_api.js | 30 ++++++++++++++++--- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/maps/server/routes.js b/x-pack/plugins/maps/server/routes.js index 635770a562c2de..d70d72e021c652 100644 --- a/x-pack/plugins/maps/server/routes.js +++ b/x-pack/plugins/maps/server/routes.js @@ -529,10 +529,11 @@ export async function initRoutes(core, getLicenseId, emsSettings, kbnVersion, lo }, (context, request, response) => { const range = path.normalize(request.params.range); - return range.startsWith('..') + const rootPath = path.resolve(__dirname, 'fonts', 'open_sans'); + const fontPath = path.resolve(rootPath, `${range}.pbf`); + return !fontPath.startsWith(rootPath) ? response.notFound() : new Promise((resolve) => { - const fontPath = path.join(__dirname, 'fonts', 'open_sans', `${range}.pbf`); fs.readFile(fontPath, (error, data) => { if (error) { resolve(response.notFound()); diff --git a/x-pack/test/api_integration/apis/maps/fonts_api.js b/x-pack/test/api_integration/apis/maps/fonts_api.js index afde003b05f2de..35017e6e37db8e 100644 --- a/x-pack/test/api_integration/apis/maps/fonts_api.js +++ b/x-pack/test/api_integration/apis/maps/fonts_api.js @@ -6,11 +6,33 @@ */ import expect from '@kbn/expect'; +import path from 'path'; +import { copyFile, rm } from 'fs/promises'; export default function ({ getService }) { const supertest = getService('supertest'); + const log = getService('log'); describe('fonts', () => { + // [HACK]: On CI tests are run from the different directories than the built and running Kibana + // instance. To workaround that we use Kibana `process.cwd()` to construct font path manually. + // x-pack tests can be run from root directory or from within x-pack so need to cater for both possibilities. + const fontPath = path.join( + process.cwd().replace(/x-pack.*$/, ''), + 'x-pack/plugins/maps/server/fonts/open_sans/0-255.pbf' + ); + const destinationPath = path.join(path.dirname(fontPath), '..', path.basename(fontPath)); + + before(async () => { + log.debug(`Copying test file from '${fontPath}' to '${destinationPath}'`); + await copyFile(fontPath, destinationPath); + }); + + after(async () => { + log.debug(`Removing test file '${destinationPath}'`); + await rm(destinationPath); + }); + it('should return fonts', async () => { const resp = await supertest .get(`/api/maps/fonts/Open%20Sans%20Regular,Arial%20Unicode%20MS%20Regular/0-255`) @@ -25,12 +47,12 @@ export default function ({ getService }) { .expect(404); }); - it('should return 404 when file is not in font folder (../)', async () => { - await supertest.get(`/api/maps/fonts/open_sans/..%2fopen_sans%2f0-255`).expect(404); + it('should return 404 when file is not in font folder (..)', async () => { + await supertest.get(`/api/maps/fonts/open_sans/..%2f0-255`).expect(404); }); - it('should return 404 when file is not in font folder (./../)', async () => { - await supertest.get(`/api/maps/fonts/open_sans/.%2f..%2fopen_sans%2f0-255`).expect(404); + it('should return 404 when file is not in font folder (./..)', async () => { + await supertest.get(`/api/maps/fonts/open_sans/.%2f..%2f0-255`).expect(404); }); }); } From a18c2e76192a981bc78e3b270cece3be82354774 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 20 Oct 2021 12:43:47 -0400 Subject: [PATCH 10/34] chore(NA): adds backport configs for renaming 7.x into 7.16 (#115783) (#115813) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Tiago Costa --- .backportrc.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.backportrc.json b/.backportrc.json index 8ad858d37f840b..0162ef6085ab52 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -2,7 +2,7 @@ "upstream": "elastic/kibana", "targetBranchChoices": [ { "name": "master", "checked": true }, - { "name": "7.x", "checked": true }, + { "name": "7.16", "checked": true }, "7.15", "7.14", "7.13", @@ -33,7 +33,6 @@ "targetPRLabels": ["backport"], "branchLabelMapping": { "^v8.0.0$": "master", - "^v7.16.0$": "7.x", "^v(\\d+).(\\d+).\\d+$": "$1.$2" } } \ No newline at end of file From d40e85c25bda3be07b959a036505a8a58db3809f Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Wed, 20 Oct 2021 18:59:28 +0200 Subject: [PATCH 11/34] [ftr] update webdriver dependency to 4.0 (#115649) (#115790) * [ftr] update webdriver dependency to 4.0 * [ftr] cast options on assign Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> # Conflicts: # yarn.lock --- package.json | 4 +- test/functional/services/remote/webdriver.ts | 124 +++++++++---------- yarn.lock | 47 ++++--- 3 files changed, 87 insertions(+), 88 deletions(-) diff --git a/package.json b/package.json index d8e1cd25807980..be54a06a9afc83 100644 --- a/package.json +++ b/package.json @@ -624,7 +624,7 @@ "@types/redux-actions": "^2.6.1", "@types/request": "^2.48.2", "@types/seedrandom": ">=2.0.0 <4.0.0", - "@types/selenium-webdriver": "^4.0.9", + "@types/selenium-webdriver": "^4.0.15", "@types/semver": "^7", "@types/set-value": "^2.0.0", "@types/sinon": "^7.0.13", @@ -814,7 +814,7 @@ "rxjs-marbles": "^5.0.6", "sass-loader": "^8.0.2", "sass-resources-loader": "^2.0.1", - "selenium-webdriver": "^4.0.0-alpha.7", + "selenium-webdriver": "^4.0.0", "serve-static": "1.14.1", "shelljs": "^0.8.4", "simple-git": "1.116.0", diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts index b501acb296b35c..063c3527d4da3f 100644 --- a/test/functional/services/remote/webdriver.ts +++ b/test/functional/services/remote/webdriver.ts @@ -71,6 +71,60 @@ export interface BrowserConfig { acceptInsecureCerts: boolean; } +function initChromiumOptions(browserType: Browsers, acceptInsecureCerts: boolean) { + const options = browserType === Browsers.Chrome ? new chrome.Options() : new edge.Options(); + + options.addArguments( + // Disables the sandbox for all process types that are normally sandboxed. + 'no-sandbox', + // Launches URL in new browser window. + 'new-window', + // By default, file:// URIs cannot read other file:// URIs. This is an override for developers who need the old behavior for testing. + 'allow-file-access-from-files', + // Use fake device for Media Stream to replace actual camera and microphone. + 'use-fake-device-for-media-stream', + // Bypass the media stream infobar by selecting the default device for media streams (e.g. WebRTC). Works with --use-fake-device-for-media-stream. + 'use-fake-ui-for-media-stream' + ); + + if (process.platform === 'linux') { + // The /dev/shm partition is too small in certain VM environments, causing + // Chrome to fail or crash. Use this flag to work-around this issue + // (a temporary directory will always be used to create anonymous shared memory files). + options.addArguments('disable-dev-shm-usage'); + } + + if (headlessBrowser === '1') { + // Use --disable-gpu to avoid an error from a missing Mesa library, as per + // See: https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md + options.headless(); + options.addArguments('disable-gpu'); + } + + if (certValidation === '0') { + options.addArguments('ignore-certificate-errors'); + } + + if (remoteDebug === '1') { + // Visit chrome://inspect in chrome to remotely view/debug + options.headless(); + options.addArguments('disable-gpu', 'remote-debugging-port=9222'); + } + + if (browserBinaryPath) { + options.setChromeBinaryPath(browserBinaryPath); + } + + const prefs = new logging.Preferences(); + prefs.setLevel(logging.Type.BROWSER, logging.Level.ALL); + options.setUserPreferences(chromiumUserPrefs); + options.setLoggingPrefs(prefs); + options.set('unexpectedAlertBehaviour', 'accept'); + options.setAcceptInsecureCerts(acceptInsecureCerts); + + return options; +} + let attemptCounter = 0; let edgePaths: { driverPath: string | undefined; browserPath: string | undefined }; async function attemptToCreateCommand( @@ -86,55 +140,10 @@ async function attemptToCreateCommand( const buildDriverInstance = async () => { switch (browserType) { case 'chrome': { - const chromeOptions = new chrome.Options(); - chromeOptions.addArguments( - // Disables the sandbox for all process types that are normally sandboxed. - 'no-sandbox', - // Launches URL in new browser window. - 'new-window', - // By default, file:// URIs cannot read other file:// URIs. This is an override for developers who need the old behavior for testing. - 'allow-file-access-from-files', - // Use fake device for Media Stream to replace actual camera and microphone. - 'use-fake-device-for-media-stream', - // Bypass the media stream infobar by selecting the default device for media streams (e.g. WebRTC). Works with --use-fake-device-for-media-stream. - 'use-fake-ui-for-media-stream' - ); - - if (process.platform === 'linux') { - // The /dev/shm partition is too small in certain VM environments, causing - // Chrome to fail or crash. Use this flag to work-around this issue - // (a temporary directory will always be used to create anonymous shared memory files). - chromeOptions.addArguments('disable-dev-shm-usage'); - } - - if (headlessBrowser === '1') { - // Use --disable-gpu to avoid an error from a missing Mesa library, as per - // See: https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md - chromeOptions.headless(); - chromeOptions.addArguments('disable-gpu'); - } - - if (certValidation === '0') { - chromeOptions.addArguments('ignore-certificate-errors'); - } - - if (remoteDebug === '1') { - // Visit chrome://inspect in chrome to remotely view/debug - chromeOptions.headless(); - chromeOptions.addArguments('disable-gpu', 'remote-debugging-port=9222'); - } - - if (browserBinaryPath) { - chromeOptions.setChromeBinaryPath(browserBinaryPath); - } - - const prefs = new logging.Preferences(); - prefs.setLevel(logging.Type.BROWSER, logging.Level.ALL); - chromeOptions.setUserPreferences(chromiumUserPrefs); - chromeOptions.setLoggingPrefs(prefs); - chromeOptions.set('unexpectedAlertBehaviour', 'accept'); - chromeOptions.setAcceptInsecureCerts(config.acceptInsecureCerts); - + const chromeOptions = initChromiumOptions( + browserType, + config.acceptInsecureCerts + ) as chrome.Options; let session; if (remoteSessionUrl) { session = await new Builder() @@ -164,19 +173,10 @@ async function attemptToCreateCommand( case 'msedge': { if (edgePaths && edgePaths.browserPath && edgePaths.driverPath) { - const edgeOptions = new edge.Options(); - if (headlessBrowser === '1') { - // @ts-ignore internal modules are not typed - edgeOptions.headless(); - } - // @ts-ignore internal modules are not typed - edgeOptions.setEdgeChromium(true); - // @ts-ignore internal modules are not typed - edgeOptions.setBinaryPath(edgePaths.browserPath); - const options = edgeOptions.get('ms:edgeOptions'); - // overriding options to include preferences - Object.assign(options, { prefs: chromiumUserPrefs }); - edgeOptions.set('ms:edgeOptions', options); + const edgeOptions = initChromiumOptions( + browserType, + config.acceptInsecureCerts + ) as edge.Options; const session = await new Builder() .forBrowser('MicrosoftEdge') .setEdgeOptions(edgeOptions) diff --git a/yarn.lock b/yarn.lock index 553a8dfbb9b240..e1c6bf41f3068b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6053,10 +6053,10 @@ resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-2.4.28.tgz#9ce8fa048c1e8c85cb71d7fe4d704e000226036f" integrity sha512-SMA+fUwULwK7sd/ZJicUztiPs8F1yCPwF3O23Z9uQ32ME5Ha0NmDK9+QTsYE4O2tHXChzXomSWWeIhCnoN1LqA== -"@types/selenium-webdriver@^4.0.9": - version "4.0.9" - resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-4.0.9.tgz#12621e55b2ef8f6c98bd17fe23fa720c6cba16bd" - integrity sha512-HopIwBE7GUXsscmt/J0DhnFXLSmO04AfxT6b8HAprknwka7pqEWquWDMXxCjd+NUHK9MkCe1SDKKsMiNmCItbQ== +"@types/selenium-webdriver@^4.0.15": + version "4.0.15" + resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-4.0.15.tgz#03012b84155cf6bbae2f64aa9dccf2a84c78c7c8" + integrity sha512-5760PIZkzhPejy3hsKAdCKe5LJygGdxLKOLxmZL9GEUcFlO5OgzM6G2EbdbvOnaw4xvUSa9Uip6Ipwkih12BPA== "@types/semver@^7": version "7.3.4" @@ -18129,10 +18129,10 @@ jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.4.1: array-includes "^3.1.1" object.assign "^4.1.0" -jszip@^3.2.2: - version "3.3.0" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.3.0.tgz#29d72c21a54990fa885b11fc843db320640d5271" - integrity sha512-EJ9k766htB1ZWnsV5ZMDkKLgA+201r/ouFF8R2OigVjVdcm2rurcBrrdXaeqBJbqnUVMko512PYmlncBKE1Huw== +jszip@^3.6.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.7.1.tgz#bd63401221c15625a1228c556ca8a68da6fda3d9" + integrity sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg== dependencies: lie "~3.3.0" pako "~1.0.2" @@ -21014,7 +21014,7 @@ os-shim@^0.1.2: resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917" integrity sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc= -os-tmpdir@^1.0.0, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: +os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= @@ -24980,14 +24980,15 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= -selenium-webdriver@^4.0.0-alpha.7: - version "4.0.0-alpha.7" - resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.0.0-alpha.7.tgz#e3879d8457fd7ad8e4424094b7dc0540d99e6797" - integrity sha512-D4qnTsyTr91jT8f7MfN+OwY0IlU5+5FmlO5xlgRUV6hDEV8JyYx2NerdTEqDDkNq7RZDYc4VoPALk8l578RBHw== +selenium-webdriver@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.0.0.tgz#7dc8969facee3be634459e173f557b7e34308e73" + integrity sha512-tOlu6FnTjPq2FKpd153pl8o2cB7H40Rvl/ogiD2sapMv4IDjQqpIxbd+swDJe9UDLdszeh5CDis6lgy4e9UG1w== dependencies: - jszip "^3.2.2" - rimraf "^2.7.1" - tmp "0.0.30" + jszip "^3.6.0" + rimraf "^3.0.2" + tmp "^0.2.1" + ws ">=7.4.6" selfsigned@^1.10.7: version "1.10.8" @@ -27090,13 +27091,6 @@ title-case@^2.1.1: no-case "^2.2.0" upper-case "^1.0.3" -tmp@0.0.30: - version "0.0.30" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.30.tgz#72419d4a8be7d6ce75148fd8b324e593a711c2ed" - integrity sha1-ckGdSovn1s51FI/YsyTlk6cRwu0= - dependencies: - os-tmpdir "~1.0.1" - tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -27104,7 +27098,7 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" -tmp@~0.2.1: +tmp@^0.2.1, tmp@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== @@ -29509,6 +29503,11 @@ write@1.0.3: dependencies: mkdirp "^0.5.1" +ws@>=7.4.6: + version "7.5.5" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881" + integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w== + ws@^6.1.2, ws@^6.2.1: version "6.2.2" resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e" From 63ee500f8c5d4e67dc4864052f9869b4b0a4f273 Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Wed, 20 Oct 2021 19:15:47 +0200 Subject: [PATCH 12/34] Fix alerts Count table title overflow wraps prematurely (#115364) (#115560) # Conflicts: # x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../alerts_kpis/alerts_count_panel/index.tsx | 2 +- .../alerts_kpis/alerts_histogram_panel/index.tsx | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx index 6bd902658c8e46..e6f179016fe9bb 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_count_panel/index.tsx @@ -95,7 +95,7 @@ export const AlertsCountPanel = memo( {i18n.COUNT_TABLE_TITLE}} titleSize="s" hideSubtitle > diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx index 2182ed7da0c4fa..e9eaebf72039f9 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx @@ -247,10 +247,14 @@ export const AlertsHistogramPanel = memo( } }, [showLinkToAlerts, goToDetectionEngine, formatUrl]); - const titleText = useMemo(() => (onlyField == null ? title : i18n.TOP(onlyField)), [ - onlyField, - title, - ]); + const titleText = useMemo( + () => ( + + {onlyField == null ? title : i18n.TOP(onlyField)} + + ), + [onlyField, title] + ); return ( From 6fbe025c3e35df66b6a16c5e6aafc5fb6ee10cd9 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 20 Oct 2021 14:33:09 -0400 Subject: [PATCH 13/34] [Metrics UI] Add track_total_hits to Metric Threshold query to support alerts with over 10K documents (#115465) (#115811) * [Metrics UI] Add track_total_hits to Metric Threshold query * Adding tests * Making the esArchive smaller Co-authored-by: Chris Cowan --- .../metric_threshold/lib/metric_query.ts | 1 + .../apis/metrics_ui/constants.ts | 4 + .../apis/metrics_ui/metric_threshold_alert.ts | 93 +- .../infra/ten_thousand_plus/data.json.gz | Bin 0 -> 410516 bytes .../infra/ten_thousand_plus/mappings.json | 21724 ++++++++++++++++ 5 files changed, 21819 insertions(+), 3 deletions(-) create mode 100644 x-pack/test/functional/es_archives/infra/ten_thousand_plus/data.json.gz create mode 100644 x-pack/test/functional/es_archives/infra/ten_thousand_plus/mappings.json diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts index f5ff4448ecb600..59dc398973f8c1 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/metric_query.ts @@ -120,6 +120,7 @@ export const getElasticsearchMetricQuery = ( const parsedFilterQuery = getParsedFilterQuery(filterQuery); return { + track_total_hits: true, query: { bool: { filter: [ diff --git a/x-pack/test/api_integration/apis/metrics_ui/constants.ts b/x-pack/test/api_integration/apis/metrics_ui/constants.ts index 2ca89f2f9ab87f..90db71ae081302 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/constants.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/constants.ts @@ -39,4 +39,8 @@ export const DATES = { max: 1609545900000, // '2021-01-02T00:05:00Z' }, }, + ten_thousand_plus: { + min: 1634604480001, // 2021-10-19T00:48:00.001Z + max: 1634604839997, // 2021-10-19T00:53:59.997Z + }, }; diff --git a/x-pack/test/api_integration/apis/metrics_ui/metric_threshold_alert.ts b/x-pack/test/api_integration/apis/metrics_ui/metric_threshold_alert.ts index 66c40e2e6e92d8..880d73a236c3b4 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/metric_threshold_alert.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/metric_threshold_alert.ts @@ -81,10 +81,95 @@ export default function ({ getService }: FtrProviderContext) { }; describe('Metric Threshold Alerts Executor', () => { - before(() => esArchiver.load('x-pack/test/functional/es_archives/infra/alerts_test_data')); - after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/alerts_test_data')); - + describe('with 10K plus docs', () => { + before(() => esArchiver.load('x-pack/test/functional/es_archives/infra/ten_thousand_plus')); + after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/ten_thousand_plus')); + describe('without group by', () => { + it('should alert on document count', async () => { + const params = { + ...baseParams, + criteria: [ + { + timeSize: 5, + timeUnit: 'm', + threshold: [10000], + comparator: Comparator.LT_OR_EQ, + aggType: Aggregators.COUNT, + } as CountMetricExpressionParams, + ], + }; + const config = { + ...configuration, + metricAlias: 'filebeat-*', + }; + const timeFrame = { end: DATES.ten_thousand_plus.max }; + const results = await evaluateAlert(esClient, params, config, [], timeFrame); + expect(results).to.eql([ + { + '*': { + timeSize: 5, + timeUnit: 'm', + threshold: [10000], + comparator: '<=', + aggType: 'count', + metric: 'Document count', + currentValue: 20895, + timestamp: '2021-10-19T00:48:59.997Z', + shouldFire: [false], + shouldWarn: [false], + isNoData: [false], + isError: false, + }, + }, + ]); + }); + }); + describe('with group by', () => { + it('should alert on document count', async () => { + const params = { + ...baseParams, + groupBy: ['event.category'], + criteria: [ + { + timeSize: 5, + timeUnit: 'm', + threshold: [10000], + comparator: Comparator.LT_OR_EQ, + aggType: Aggregators.COUNT, + } as CountMetricExpressionParams, + ], + }; + const config = { + ...configuration, + metricAlias: 'filebeat-*', + }; + const timeFrame = { end: DATES.ten_thousand_plus.max }; + const results = await evaluateAlert(esClient, params, config, [], timeFrame); + expect(results).to.eql([ + { + web: { + timeSize: 5, + timeUnit: 'm', + threshold: [10000], + comparator: '<=', + aggType: 'count', + metric: 'Document count', + currentValue: 20895, + timestamp: '2021-10-19T00:48:59.997Z', + shouldFire: [false], + shouldWarn: [false], + isNoData: [false], + isError: false, + }, + }, + ]); + }); + }); + }); describe('with gauge data', () => { + before(() => esArchiver.load('x-pack/test/functional/es_archives/infra/alerts_test_data')); + after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/alerts_test_data')); + describe('without groupBy', () => { it('should alert on document count', async () => { const params = { @@ -285,6 +370,8 @@ export default function ({ getService }: FtrProviderContext) { }); describe('with rate data', () => { + before(() => esArchiver.load('x-pack/test/functional/es_archives/infra/alerts_test_data')); + after(() => esArchiver.unload('x-pack/test/functional/es_archives/infra/alerts_test_data')); describe('without groupBy', () => { it('should alert on rate', async () => { const params = { diff --git a/x-pack/test/functional/es_archives/infra/ten_thousand_plus/data.json.gz b/x-pack/test/functional/es_archives/infra/ten_thousand_plus/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..d407dbea7cdcbd856a0aca9c8b6c2a3f820914e4 GIT binary patch literal 410516 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