From 5256b6d23ef4d05ff3dce1d7d91711c1748c8d38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Mon, 12 Jul 2021 11:46:38 +0200 Subject: [PATCH 1/9] Accomodate height of the (#104882) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../sections/agent_policy/edit_package_policy_page/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index b07d76dc6bd8e..ee529b6865e56 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -430,7 +430,9 @@ export const EditPackagePolicyForm = memo<{ /> )} {configurePackage} - + {/* Extra space to accomodate the EuiBottomBar height */} + + From b8dfcafe38e636ec88b2ca03253cf15cec844d27 Mon Sep 17 00:00:00 2001 From: Pete Harverson Date: Mon, 12 Jul 2021 11:31:54 +0100 Subject: [PATCH 2/9] [ML] Fixes unnecessary too many buckets warning on anomaly chart embeddable (#105043) * [ML] Fixes unnecessary too many buckets warning on anomaly chart embeddable * [ML] Update jest tests for number of axis ticks. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../explorer_chart_distribution.js | 24 ++++++++++--------- .../explorer_chart_distribution.test.js | 2 +- .../explorer_chart_single_metric.js | 24 ++++++++++--------- .../explorer_chart_single_metric.test.js | 2 +- .../anomaly_explorer_charts_service.ts | 11 +++++++-- 5 files changed, 37 insertions(+), 26 deletions(-) diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js index 7efd36bbe57c6..27a934fa841fe 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js @@ -270,12 +270,6 @@ export class ExplorerChartDistribution extends React.Component { const tickValuesStart = Math.max(config.selectedEarliest, config.plotEarliest); // +1 ms to account for the ms that was subtracted for query aggregations. const interval = config.selectedLatest - config.selectedEarliest + 1; - const tickValues = getTickValues( - tickValuesStart, - interval, - config.plotEarliest, - config.plotLatest - ); const xAxis = d3.svg .axis() @@ -286,10 +280,18 @@ export class ExplorerChartDistribution extends React.Component { .tickPadding(10) .tickFormat((d) => moment(d).format(xAxisTickFormat)); - // With tooManyBuckets the chart would end up with no x-axis labels - // because the ticks are based on the span of the emphasis section, - // and the highlighted area spans the whole chart. - if (tooManyBuckets === false) { + // With tooManyBuckets, or when the chart is used as an embeddable, + // the chart would end up with no x-axis labels because the ticks are based on the span of the + // emphasis section, and the selected area spans the whole chart. + const useAutoTicks = + tooManyBuckets === true || interval >= config.plotLatest - config.plotEarliest; + if (useAutoTicks === false) { + const tickValues = getTickValues( + tickValuesStart, + interval, + config.plotEarliest, + config.plotLatest + ); xAxis.tickValues(tickValues); } else { xAxis.ticks(numTicksForDateFormat(vizWidth, xAxisTickFormat)); @@ -327,7 +329,7 @@ export class ExplorerChartDistribution extends React.Component { }); } - if (tooManyBuckets === false) { + if (useAutoTicks === false) { removeLabelOverlap(gAxis, tickValuesStart, interval, vizWidth); } } diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js index 11a15b192fc52..8d2f66a870c75 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js @@ -139,7 +139,7 @@ describe('ExplorerChart', () => { expect(+selectedInterval.getAttribute('height')).toBe(166); const xAxisTicks = wrapper.getDOMNode().querySelector('.x').querySelectorAll('.tick'); - expect([...xAxisTicks]).toHaveLength(0); + expect([...xAxisTicks]).toHaveLength(1); const yAxisTicks = wrapper.getDOMNode().querySelector('.y').querySelectorAll('.tick'); expect([...yAxisTicks]).toHaveLength(5); const emphasizedAxisLabel = wrapper diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js index dd07a7d6cbdee..19390017244a8 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js @@ -196,12 +196,6 @@ export class ExplorerChartSingleMetric extends React.Component { const tickValuesStart = Math.max(config.selectedEarliest, config.plotEarliest); // +1 ms to account for the ms that was subtracted for query aggregations. const interval = config.selectedLatest - config.selectedEarliest + 1; - const tickValues = getTickValues( - tickValuesStart, - interval, - config.plotEarliest, - config.plotLatest - ); const xAxis = d3.svg .axis() @@ -212,10 +206,18 @@ export class ExplorerChartSingleMetric extends React.Component { .tickPadding(10) .tickFormat((d) => moment(d).format(xAxisTickFormat)); - // With tooManyBuckets the chart would end up with no x-axis labels - // because the ticks are based on the span of the emphasis section, - // and the highlighted area spans the whole chart. - if (tooManyBuckets === false) { + // With tooManyBuckets, or when the chart is used as an embeddable, + // the chart would end up with no x-axis labels because the ticks are based on the span of the + // emphasis section, and the selected area spans the whole chart. + const useAutoTicks = + tooManyBuckets === true || interval >= config.plotLatest - config.plotEarliest; + if (useAutoTicks === false) { + const tickValues = getTickValues( + tickValuesStart, + interval, + config.plotEarliest, + config.plotLatest + ); xAxis.tickValues(tickValues); } else { xAxis.ticks(numTicksForDateFormat(vizWidth, xAxisTickFormat)); @@ -243,7 +245,7 @@ export class ExplorerChartSingleMetric extends React.Component { axes.append('g').attr('class', 'y axis').call(yAxis); - if (tooManyBuckets === false) { + if (useAutoTicks === false) { removeLabelOverlap(gAxis, tickValuesStart, interval, vizWidth); } } diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js index 981f7515d3d70..00172965bc216 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js @@ -144,7 +144,7 @@ describe('ExplorerChart', () => { expect(+selectedInterval.getAttribute('height')).toBe(166); const xAxisTicks = wrapper.getDOMNode().querySelector('.x').querySelectorAll('.tick'); - expect([...xAxisTicks]).toHaveLength(0); + expect([...xAxisTicks]).toHaveLength(1); const yAxisTicks = wrapper.getDOMNode().querySelector('.y').querySelectorAll('.tick'); expect([...yAxisTicks]).toHaveLength(10); diff --git a/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts b/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts index afad043fcc4d1..97ddefac860f2 100644 --- a/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts +++ b/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts @@ -225,13 +225,20 @@ export class AnomalyExplorerChartsService { chartRange.min = chartRange.min + maxBucketSpanMs; } + // When used as an embeddable, selectedEarliestMs is the start date on the time picker, + // which may be earlier than the time of the first point plotted in the chart (as we plot + // the first full bucket with a start date no earlier than the start). + const selectedEarliestBucketCeil = boundsMin + ? Math.ceil(Math.max(selectedEarliestMs, boundsMin) / maxBucketSpanMs) * maxBucketSpanMs + : Math.ceil(selectedEarliestMs / maxBucketSpanMs) * maxBucketSpanMs; + const selectedLatestBucketStart = boundsMax ? Math.floor(Math.min(selectedLatestMs, boundsMax) / maxBucketSpanMs) * maxBucketSpanMs : Math.floor(selectedLatestMs / maxBucketSpanMs) * maxBucketSpanMs; if ( - (chartRange.min > selectedEarliestMs || chartRange.max < selectedLatestBucketStart) && - chartRange.max - chartRange.min < selectedLatestBucketStart - selectedEarliestMs + (chartRange.min > selectedEarliestBucketCeil || chartRange.max < selectedLatestBucketStart) && + chartRange.max - chartRange.min < selectedLatestBucketStart - selectedEarliestBucketCeil ) { tooManyBuckets = true; } From 216bb5e1b8c8406cd23503619972e5a3ea543790 Mon Sep 17 00:00:00 2001 From: Dzmitry Lemechko Date: Mon, 12 Jul 2021 13:23:08 +0200 Subject: [PATCH 3/9] [load] run puppeteer script before gatling scenarios (#104836) * [load] puppeteer script before load testing * install dependencies after metricbeat configuration Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- test/scripts/jenkins_build_load_testing.sh | 3 +++ x-pack/test/load/runner.ts | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/test/scripts/jenkins_build_load_testing.sh b/test/scripts/jenkins_build_load_testing.sh index d7c7bda83c9ef..667540515fc83 100755 --- a/test/scripts/jenkins_build_load_testing.sh +++ b/test/scripts/jenkins_build_load_testing.sh @@ -53,6 +53,9 @@ echo "cloud.auth: ${USER_FROM_VAULT}:${PASS_FROM_VAULT}" >> cfg/metricbeat/metri cp cfg/metricbeat/metricbeat.yml $KIBANA_DIR/metricbeat-install/metricbeat.yml # Disable system monitoring: enabled for now to have more data #mv $KIBANA_DIR/metricbeat-install/modules.d/system.yml $KIBANA_DIR/metricbeat-install/modules.d/system.yml.disabled +echo " -> Building puppeteer project" +cd puppeteer +yarn install && yarn build popd # doesn't persist, also set in kibanaPipeline.groovy diff --git a/x-pack/test/load/runner.ts b/x-pack/test/load/runner.ts index 2d379391b2089..0bea5992f5539 100644 --- a/x-pack/test/load/runner.ts +++ b/x-pack/test/load/runner.ts @@ -18,6 +18,7 @@ const simulationPackage = 'org.kibanaLoadTest.simulation'; const simulationFIleExtension = '.scala'; const gatlingProjectRootPath: string = process.env.GATLING_PROJECT_PATH || resolve(REPO_ROOT, '../kibana-load-testing'); +const puppeteerProjectRootPath: string = resolve(gatlingProjectRootPath, 'puppeteer'); const simulationEntry: string = process.env.GATLING_SIMULATIONS || 'branch.DemoJourney'; if (!Fs.existsSync(gatlingProjectRootPath)) { @@ -52,6 +53,15 @@ export async function GatlingTestRunner({ getService }: FtrProviderContext) { const log = getService('log'); await withProcRunner(log, async (procs) => { + await procs.run('node build/index.js', { + cmd: 'node', + args: ['build/index.js'], + cwd: puppeteerProjectRootPath, + env: { + ...process.env, + }, + wait: true, + }); for (let i = 0; i < simulationClasses.length; i++) { await procs.run('gatling: test', { cmd: 'mvn', From 832d349930be1f8d4a7a47733f413b0ab75303e8 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 12 Jul 2021 14:13:42 +0200 Subject: [PATCH 4/9] [KibanaLegacy] Remove unused stuff and make things async if it is easy (#104638) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * remove unused stuff and make things async if it is easy * fix problems * load bootstrap in monitoring * load angular bootstrap for saved searches and in unit tests * fix vis_type_table tests * Update x-pack/plugins/monitoring/public/plugin.ts Co-authored-by: Ester Martí Vilaseca * Update x-pack/plugins/monitoring/public/plugin.ts Co-authored-by: Ester Martí Vilaseca Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Stratoula Kalafateli Co-authored-by: Ester Martí Vilaseca --- .../doc_table/components/row_headers.test.js | 4 + .../angular/doc_table/doc_table.test.js | 4 + .../application/angular/get_inner_angular.ts | 4 +- .../application/angular/helpers/index.ts | 1 + .../angular/helpers}/promises.d.ts | 0 .../application/angular/helpers}/promises.js | 0 src/plugins/discover/public/plugin.tsx | 2 + .../public/angular/angular_config.tsx | 7 +- .../kibana_legacy/public/angular/index.ts | 2 - src/plugins/kibana_legacy/public/index.ts | 1 - src/plugins/kibana_legacy/public/mocks.ts | 1 + .../kibana_legacy/public/notify/index.ts | 1 - .../notify/toasts/TOAST_NOTIFICATIONS.md | 100 ------------------ .../public/notify/toasts/index.ts | 9 -- .../notify/toasts/toast_notifications.test.ts | 76 ------------- .../notify/toasts/toast_notifications.ts | 37 ------- src/plugins/kibana_legacy/public/plugin.ts | 8 ++ .../kibana_legacy/public/utils/index.ts | 1 - .../kibana_legacy/public/utils/system_api.ts | 40 ------- src/plugins/timelion/public/plugin.ts | 5 +- .../public/legacy/agg_table/agg_table.test.js | 6 +- .../legacy/agg_table/agg_table_group.test.js | 6 +- .../public/legacy/get_inner_angular.ts | 3 - .../paginated_table/paginated_table.test.ts | 5 + .../legacy/table_vis_controller.test.ts | 4 + .../public/legacy/vis_controller.ts | 1 + x-pack/plugins/graph/public/plugin.ts | 7 +- x-pack/plugins/monitoring/public/plugin.ts | 5 +- 28 files changed, 54 insertions(+), 286 deletions(-) rename src/plugins/{kibana_legacy/public/angular => discover/public/application/angular/helpers}/promises.d.ts (100%) rename src/plugins/{kibana_legacy/public/angular => discover/public/application/angular/helpers}/promises.js (100%) delete mode 100644 src/plugins/kibana_legacy/public/notify/toasts/TOAST_NOTIFICATIONS.md delete mode 100644 src/plugins/kibana_legacy/public/notify/toasts/index.ts delete mode 100644 src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.test.ts delete mode 100644 src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.ts delete mode 100644 src/plugins/kibana_legacy/public/utils/system_api.ts diff --git a/src/plugins/discover/public/application/angular/doc_table/components/row_headers.test.js b/src/plugins/discover/public/application/angular/doc_table/components/row_headers.test.js index a087ac8697183..1a3b34c45d05e 100644 --- a/src/plugins/discover/public/application/angular/doc_table/components/row_headers.test.js +++ b/src/plugins/discover/public/application/angular/doc_table/components/row_headers.test.js @@ -19,6 +19,7 @@ import { setScopedHistory, setServices, setDocViewsRegistry } from '../../../../ import { coreMock } from '../../../../../../../core/public/mocks'; import { dataPluginMock } from '../../../../../../data/public/mocks'; import { navigationPluginMock } from '../../../../../../navigation/public/mocks'; +import { initAngularBootstrap } from '../../../../../../kibana_legacy/public/angular_bootstrap'; import { getInnerAngularModule } from '../../get_inner_angular'; import { createBrowserHistory } from 'history'; @@ -41,6 +42,9 @@ describe('Doc Table', () => { // Stub out a minimal mapping of 4 fields let mapping; + beforeAll(async () => { + await initAngularBootstrap(); + }); beforeAll(() => setScopedHistory(createBrowserHistory())); beforeEach(() => { angular.element.prototype.slice = jest.fn(function (index) { diff --git a/src/plugins/discover/public/application/angular/doc_table/doc_table.test.js b/src/plugins/discover/public/application/angular/doc_table/doc_table.test.js index 1db35ddf18089..097f32965b141 100644 --- a/src/plugins/discover/public/application/angular/doc_table/doc_table.test.js +++ b/src/plugins/discover/public/application/angular/doc_table/doc_table.test.js @@ -17,6 +17,7 @@ import hits from '../../../__fixtures__/real_hits'; import { coreMock } from '../../../../../../core/public/mocks'; import { dataPluginMock } from '../../../../../data/public/mocks'; import { navigationPluginMock } from '../../../../../navigation/public/mocks'; +import { initAngularBootstrap } from '../../../../../kibana_legacy/public/angular_bootstrap'; import { setScopedHistory, setServices } from '../../../kibana_services'; import { getInnerAngularModule } from '../get_inner_angular'; @@ -54,6 +55,9 @@ describe('docTable', () => { const core = coreMock.createStart(); let $elem; + beforeAll(async () => { + await initAngularBootstrap(); + }); beforeAll(() => setScopedHistory(createBrowserHistory())); beforeEach(() => { angular.element.prototype.slice = jest.fn(() => { diff --git a/src/plugins/discover/public/application/angular/get_inner_angular.ts b/src/plugins/discover/public/application/angular/get_inner_angular.ts index 26d64d5adc8a3..992d82795302b 100644 --- a/src/plugins/discover/public/application/angular/get_inner_angular.ts +++ b/src/plugins/discover/public/application/angular/get_inner_angular.ts @@ -33,13 +33,12 @@ import { createDocViewerDirective } from './doc_viewer'; import { createDiscoverGridDirective } from './create_discover_grid_directive'; import { createRenderCompleteDirective } from './directives/render_complete'; import { - initAngularBootstrap, configureAppAngularModule, PrivateProvider, - PromiseServiceCreator, registerListenEventListener, watchMultiDecorator, } from '../../../../kibana_legacy/public'; +import { PromiseServiceCreator } from './helpers'; import { DiscoverStartPlugins } from '../../plugin'; import { getScopedHistory } from '../../kibana_services'; import { createDiscoverDirective } from './create_discover_directive'; @@ -54,7 +53,6 @@ export function getInnerAngularModule( deps: DiscoverStartPlugins, context: PluginInitializerContext ) { - initAngularBootstrap(); const module = initializeInnerAngularModule(name, core, deps.navigation, deps.data); configureAppAngularModule(module, { core, env: context.env }, true, getScopedHistory); return module; diff --git a/src/plugins/discover/public/application/angular/helpers/index.ts b/src/plugins/discover/public/application/angular/helpers/index.ts index 3d2c0b1c63b33..6a7f75b7e81a2 100644 --- a/src/plugins/discover/public/application/angular/helpers/index.ts +++ b/src/plugins/discover/public/application/angular/helpers/index.ts @@ -8,3 +8,4 @@ export { formatRow, formatTopLevelObject } from './row_formatter'; export { handleSourceColumnState } from './state_helpers'; +export { PromiseServiceCreator } from './promises'; diff --git a/src/plugins/kibana_legacy/public/angular/promises.d.ts b/src/plugins/discover/public/application/angular/helpers/promises.d.ts similarity index 100% rename from src/plugins/kibana_legacy/public/angular/promises.d.ts rename to src/plugins/discover/public/application/angular/helpers/promises.d.ts diff --git a/src/plugins/kibana_legacy/public/angular/promises.js b/src/plugins/discover/public/application/angular/helpers/promises.js similarity index 100% rename from src/plugins/kibana_legacy/public/angular/promises.js rename to src/plugins/discover/public/application/angular/helpers/promises.js diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index 3e31fe1d46d45..1e8a5cdac95ef 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -403,6 +403,7 @@ export class DiscoverPlugin } // this is used by application mount and tests const { getInnerAngularModule } = await import('./application/angular/get_inner_angular'); + await plugins.kibanaLegacy.loadAngularBootstrap(); const module = getInnerAngularModule( innerAngularName, core, @@ -473,6 +474,7 @@ export class DiscoverPlugin throw Error('Discover plugin getEmbeddableInjector: initializeServices is undefined'); } const { core, plugins } = await this.initializeServices(); + await getServices().kibanaLegacy.loadAngularBootstrap(); getServices().kibanaLegacy.loadFontAwesome(); const { getInnerAngularModuleEmbeddable } = await import( './application/angular/get_inner_angular' diff --git a/src/plugins/kibana_legacy/public/angular/angular_config.tsx b/src/plugins/kibana_legacy/public/angular/angular_config.tsx index daecfbc57ea99..48ee6d2db269e 100644 --- a/src/plugins/kibana_legacy/public/angular/angular_config.tsx +++ b/src/plugins/kibana_legacy/public/angular/angular_config.tsx @@ -13,6 +13,7 @@ import { ILocationProvider, IModule, IRootScopeService, + IRequestConfig, } from 'angular'; import $ from 'jquery'; import { set } from '@elastic/safer-lodash-set'; @@ -22,7 +23,6 @@ import { ChromeBreadcrumb, EnvironmentMode, PackageInfo } from 'kibana/public'; import { History } from 'history'; import { CoreStart } from 'kibana/public'; -import { isSystemApiRequest } from '../utils'; import { formatAngularHttpError, isAngularHttpError } from '../notify/lib'; export interface RouteConfiguration { @@ -38,6 +38,11 @@ export interface RouteConfiguration { requireUICapability?: string; } +function isSystemApiRequest(request: IRequestConfig) { + const { headers } = request; + return headers && !!headers['kbn-system-request']; +} + /** * Detects whether a given angular route is a dummy route that doesn't * require any action. There are two ways this can happen: diff --git a/src/plugins/kibana_legacy/public/angular/index.ts b/src/plugins/kibana_legacy/public/angular/index.ts index d9d8c0c19eb7b..369495698591d 100644 --- a/src/plugins/kibana_legacy/public/angular/index.ts +++ b/src/plugins/kibana_legacy/public/angular/index.ts @@ -6,8 +6,6 @@ * Side Public License, v 1. */ -// @ts-ignore -export { PromiseServiceCreator } from './promises'; // @ts-ignore export { watchMultiDecorator } from './watch_multi'; export * from './angular_config'; diff --git a/src/plugins/kibana_legacy/public/index.ts b/src/plugins/kibana_legacy/public/index.ts index 03adb768cde20..ea5172f78a68f 100644 --- a/src/plugins/kibana_legacy/public/index.ts +++ b/src/plugins/kibana_legacy/public/index.ts @@ -14,7 +14,6 @@ export const plugin = (initializerContext: PluginInitializerContext) => export * from './plugin'; -export { initAngularBootstrap } from './angular_bootstrap'; export { PaginateDirectiveProvider, PaginateControlsDirectiveProvider } from './paginate/paginate'; export * from './angular'; export * from './notify'; diff --git a/src/plugins/kibana_legacy/public/mocks.ts b/src/plugins/kibana_legacy/public/mocks.ts index 40834635cc570..6116c0682cb3b 100644 --- a/src/plugins/kibana_legacy/public/mocks.ts +++ b/src/plugins/kibana_legacy/public/mocks.ts @@ -22,6 +22,7 @@ const createStartContract = (): Start => ({ getHideWriteControls: jest.fn(), }, loadFontAwesome: jest.fn(), + loadAngularBootstrap: jest.fn(), }); export const kibanaLegacyPluginMock = { diff --git a/src/plugins/kibana_legacy/public/notify/index.ts b/src/plugins/kibana_legacy/public/notify/index.ts index a243059cb1918..d4dcaa77cc47a 100644 --- a/src/plugins/kibana_legacy/public/notify/index.ts +++ b/src/plugins/kibana_legacy/public/notify/index.ts @@ -6,5 +6,4 @@ * Side Public License, v 1. */ -export * from './toasts'; export * from './lib'; diff --git a/src/plugins/kibana_legacy/public/notify/toasts/TOAST_NOTIFICATIONS.md b/src/plugins/kibana_legacy/public/notify/toasts/TOAST_NOTIFICATIONS.md deleted file mode 100644 index de6a51f3927d1..0000000000000 --- a/src/plugins/kibana_legacy/public/notify/toasts/TOAST_NOTIFICATIONS.md +++ /dev/null @@ -1,100 +0,0 @@ -# Toast notifications - -Use this service to surface toasts in the bottom-right corner of the screen. After a brief delay, they'll disappear. They're useful for notifying the user of state changes. See [the EUI docs](https://elastic.github.io/eui/) for more information on toasts and their role within the UI. - -## Importing the module - -```js -import { toastNotifications } from 'ui/notify'; -``` - -## Interface - -### Adding toasts - -For convenience, there are several methods which predefine the appearance of different types of toasts. Use these methods so that the same types of toasts look similar to the user. - -#### Default - -Neutral toast. Tell the user a change in state has occurred, which is not necessarily good or bad. - -```js -toastNotifications.add('Copied to clipboard'); -``` - -#### Success - -Let the user know that an action was successful, such as saving or deleting an object. - -```js -toastNotifications.addSuccess('Your document was saved'); -``` - -#### Warning - -If something OK or good happened, but perhaps wasn't perfect, show a warning toast. - -```js -toastNotifications.addWarning('Your document was saved, but not its edit history'); -``` - -#### Danger - -When the user initiated an action but the action failed, show them a danger toast. - -```js -toastNotifications.addDanger('An error caused your document to be lost'); -``` - -### Removing a toast - -Toasts will automatically be dismissed after a brief delay, but if for some reason you want to dismiss a toast, you can use the returned toast from one of the `add` methods and then pass it to `remove`. - -```js -const toast = toastNotifications.add('Your document was saved'); -toastNotifications.remove(toast); -``` - -### Configuration options - -If you want to configure the toast further you can provide an object instead of a string. The properties of this object correspond to the `propTypes` accepted by the `EuiToast` component. Refer to [the EUI docs](https://elastic.github.io/eui/) for info on these `propTypes`. - -```js -toastNotifications.add({ - title: 'Your document was saved', - text: 'Only you have access to this document', - color: 'success', - iconType: 'check', - 'data-test-subj': 'saveDocumentSuccess', -}); -``` - -Because the underlying components are React, you can use JSX to pass in React elements to the `text` prop. This gives you total flexibility over the content displayed within the toast. - -```js -toastNotifications.add({ - title: 'Your document was saved', - text: ( -
-

- Only you have access to this document. Edit permissions. -

- - -
- ), -}); -``` - -## Use in functional tests - -Functional tests are commonly used to verify that a user action yielded a successful outcome. If you surface a toast to notify the user of this successful outcome, you can place a `data-test-subj` attribute on the toast and use it to check if the toast exists inside of your functional test. This acts as a proxy for verifying the successful outcome. - -```js -toastNotifications.addSuccess({ - title: 'Your document was saved', - 'data-test-subj': 'saveDocumentSuccess', -}); -``` diff --git a/src/plugins/kibana_legacy/public/notify/toasts/index.ts b/src/plugins/kibana_legacy/public/notify/toasts/index.ts deleted file mode 100644 index cdd7df04548fb..0000000000000 --- a/src/plugins/kibana_legacy/public/notify/toasts/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { ToastNotifications } from './toast_notifications'; diff --git a/src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.test.ts b/src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.test.ts deleted file mode 100644 index c2c5d9a4fc014..0000000000000 --- a/src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { notificationServiceMock } from '../../../../../core/public/mocks'; -import { ToastNotifications } from './toast_notifications'; -import { Toast } from 'kibana/public'; -import { BehaviorSubject } from 'rxjs'; - -describe('ToastNotifications', () => { - describe('interface', () => { - function setup() { - const toastsMock = notificationServiceMock.createStartContract().toasts; - return { toastNotifications: new ToastNotifications(toastsMock), toastsMock }; - } - - describe('add method', () => { - test('adds a toast', () => { - const { toastNotifications, toastsMock } = setup(); - toastNotifications.add({}); - expect(toastsMock.add).toHaveBeenCalled(); - }); - }); - - describe('remove method', () => { - test('removes a toast', () => { - const { toastNotifications, toastsMock } = setup(); - const fakeToast = {} as Toast; - toastNotifications.remove(fakeToast); - expect(toastsMock.remove).toHaveBeenCalledWith(fakeToast); - }); - }); - - describe('onChange method', () => { - test('callback is called when observable changes', () => { - const toastsMock = notificationServiceMock.createStartContract().toasts; - const toasts$ = new BehaviorSubject([]); - toastsMock.get$.mockReturnValue(toasts$); - const toastNotifications = new ToastNotifications(toastsMock); - const onChangeSpy = jest.fn(); - toastNotifications.onChange(onChangeSpy); - toasts$.next([{ id: 'toast1' }]); - toasts$.next([]); - expect(onChangeSpy).toHaveBeenCalledTimes(2); - }); - }); - - describe('addSuccess method', () => { - test('adds a success toast', () => { - const { toastNotifications, toastsMock } = setup(); - toastNotifications.addSuccess({}); - expect(toastsMock.addSuccess).toHaveBeenCalled(); - }); - }); - - describe('addWarning method', () => { - test('adds a warning toast', () => { - const { toastNotifications, toastsMock } = setup(); - toastNotifications.addWarning({}); - expect(toastsMock.addWarning).toHaveBeenCalled(); - }); - }); - - describe('addDanger method', () => { - test('adds a danger toast', () => { - const { toastNotifications, toastsMock } = setup(); - toastNotifications.addWarning({}); - expect(toastsMock.addWarning).toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.ts b/src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.ts deleted file mode 100644 index e7ccbbca07b73..0000000000000 --- a/src/plugins/kibana_legacy/public/notify/toasts/toast_notifications.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { NotificationsSetup, Toast, ToastInput, ErrorToastOptions } from 'kibana/public'; - -export class ToastNotifications { - public list: Toast[] = []; - - private onChangeCallback?: () => void; - - constructor(private readonly toasts: NotificationsSetup['toasts']) { - toasts.get$().subscribe((list) => { - this.list = list; - - if (this.onChangeCallback) { - this.onChangeCallback(); - } - }); - } - - public onChange = (callback: () => void) => { - this.onChangeCallback = callback; - }; - - public add = (toastOrTitle: ToastInput) => this.toasts.add(toastOrTitle); - public remove = (toast: Toast) => this.toasts.remove(toast); - public addSuccess = (toastOrTitle: ToastInput) => this.toasts.addSuccess(toastOrTitle); - public addWarning = (toastOrTitle: ToastInput) => this.toasts.addWarning(toastOrTitle); - public addDanger = (toastOrTitle: ToastInput) => this.toasts.addDanger(toastOrTitle); - public addError = (error: Error, options: ErrorToastOptions) => - this.toasts.addError(error, options); -} diff --git a/src/plugins/kibana_legacy/public/plugin.ts b/src/plugins/kibana_legacy/public/plugin.ts index 337fdb80da7e4..f60130d367b58 100644 --- a/src/plugins/kibana_legacy/public/plugin.ts +++ b/src/plugins/kibana_legacy/public/plugin.ts @@ -33,6 +33,14 @@ export class KibanaLegacyPlugin { loadFontAwesome: async () => { await import('./font_awesome'); }, + /** + * Loads angular bootstrap modules. Should be removed once the last consumer has migrated to EUI + * @deprecated + */ + loadAngularBootstrap: async () => { + const { initAngularBootstrap } = await import('./angular_bootstrap'); + initAngularBootstrap(); + }, /** * @deprecated * Just exported for wiring up with dashboard mode, should not be used. diff --git a/src/plugins/kibana_legacy/public/utils/index.ts b/src/plugins/kibana_legacy/public/utils/index.ts index db3c0af6c8cb9..94233558b4627 100644 --- a/src/plugins/kibana_legacy/public/utils/index.ts +++ b/src/plugins/kibana_legacy/public/utils/index.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -export * from './system_api'; // @ts-ignore export { KbnAccessibleClickProvider } from './kbn_accessible_click'; // @ts-ignore diff --git a/src/plugins/kibana_legacy/public/utils/system_api.ts b/src/plugins/kibana_legacy/public/utils/system_api.ts deleted file mode 100644 index d0fe221935ba5..0000000000000 --- a/src/plugins/kibana_legacy/public/utils/system_api.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { IRequestConfig } from 'angular'; - -const SYSTEM_REQUEST_HEADER_NAME = 'kbn-system-request'; -const LEGACY_SYSTEM_API_HEADER_NAME = 'kbn-system-api'; - -/** - * Adds a custom header designating request as system API - * @param originalHeaders Object representing set of headers - * @return Object representing set of headers, with system API header added in - */ -export function addSystemApiHeader(originalHeaders: Record) { - const systemApiHeaders = { - [SYSTEM_REQUEST_HEADER_NAME]: true, - }; - return { - ...originalHeaders, - ...systemApiHeaders, - }; -} - -/** - * Returns true if request is a system API request; false otherwise - * - * @param request Object Request object created by $http service - * @return true if request is a system API request; false otherwise - */ -export function isSystemApiRequest(request: IRequestConfig) { - const { headers } = request; - return ( - headers && (!!headers[SYSTEM_REQUEST_HEADER_NAME] || !!headers[LEGACY_SYSTEM_API_HEADER_NAME]) - ); -} diff --git a/src/plugins/timelion/public/plugin.ts b/src/plugins/timelion/public/plugin.ts index 6f8dbfdcc6704..63ea9a38e2795 100644 --- a/src/plugins/timelion/public/plugin.ts +++ b/src/plugins/timelion/public/plugin.ts @@ -19,7 +19,7 @@ import { AppNavLinkStatus, } from '../../../core/public'; import { Panel } from './panels/panel'; -import { initAngularBootstrap } from '../../kibana_legacy/public'; +import { KibanaLegacyStart } from '../../kibana_legacy/public'; import { createKbnUrlTracker } from '../../kibana_utils/public'; import { DataPublicPluginStart, esFilters, DataPublicPluginSetup } from '../../data/public'; import { NavigationPublicPluginStart } from '../../navigation/public'; @@ -41,6 +41,7 @@ export interface TimelionPluginStartDependencies { visualizations: VisualizationsStart; visTypeTimelion: VisTypeTimelionPluginStart; savedObjects: SavedObjectsStart; + kibanaLegacy: KibanaLegacyStart; } /** @internal */ @@ -91,7 +92,6 @@ export class TimelionPlugin stopUrlTracker(); }; - initAngularBootstrap(); core.application.register({ id: 'timelion', title: 'Timelion', @@ -103,6 +103,7 @@ export class TimelionPlugin visTypeTimelion.isUiEnabled === false ? AppNavLinkStatus.hidden : AppNavLinkStatus.default, mount: async (params: AppMountParameters) => { const [coreStart, pluginsStart] = await core.getStartServices(); + await pluginsStart.kibanaLegacy.loadAngularBootstrap(); this.currentHistory = params.history; appMounted(); diff --git a/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.test.js b/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.test.js index 65e26ddf6e03f..cbc3db6585a7d 100644 --- a/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.test.js +++ b/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.test.js @@ -15,7 +15,7 @@ import { round } from 'lodash'; import { getFieldFormatsRegistry } from '../../../../data/public/test_utils'; import { coreMock } from '../../../../../core/public/mocks'; -import { initAngularBootstrap } from '../../../../kibana_legacy/public'; +import { initAngularBootstrap } from '../../../../kibana_legacy/public/angular_bootstrap'; import { setUiSettings } from '../../../../data/public/services'; import { UI_SETTINGS } from '../../../../data/public/'; import { CSV_SEPARATOR_SETTING, CSV_QUOTE_VALUES_SETTING } from '../../../../share/public'; @@ -60,10 +60,12 @@ describe('Table Vis - AggTable Directive', function () { initTableVisLegacyModule(tableVisModule); }; + beforeAll(async () => { + await initAngularBootstrap(); + }); beforeEach(() => { setUiSettings(core.uiSettings); setFormatService(getFieldFormatsRegistry(core)); - initAngularBootstrap(); initLocalAngular(); angular.mock.module('kibana/table_vis'); angular.mock.inject(($injector, config) => { diff --git a/src/plugins/vis_type_table/public/legacy/agg_table/agg_table_group.test.js b/src/plugins/vis_type_table/public/legacy/agg_table/agg_table_group.test.js index 1c6630e30e5f7..ba04b2f449f6d 100644 --- a/src/plugins/vis_type_table/public/legacy/agg_table/agg_table_group.test.js +++ b/src/plugins/vis_type_table/public/legacy/agg_table/agg_table_group.test.js @@ -13,11 +13,11 @@ import expect from '@kbn/expect'; import { getFieldFormatsRegistry } from '../../../../data/public/test_utils'; import { coreMock } from '../../../../../core/public/mocks'; -import { initAngularBootstrap } from '../../../../kibana_legacy/public'; import { setUiSettings } from '../../../../data/public/services'; import { setFormatService } from '../../services'; import { getInnerAngular } from '../get_inner_angular'; import { initTableVisLegacyModule } from '../table_vis_legacy_module'; +import { initAngularBootstrap } from '../../../../kibana_legacy/public/angular_bootstrap'; import { tabifiedData } from './tabified_data'; const uiSettings = new Map(); @@ -40,10 +40,12 @@ describe('Table Vis - AggTableGroup Directive', function () { initTableVisLegacyModule(tableVisModule); }; + beforeAll(async () => { + await initAngularBootstrap(); + }); beforeEach(() => { setUiSettings(core.uiSettings); setFormatService(getFieldFormatsRegistry(core)); - initAngularBootstrap(); initLocalAngular(); angular.mock.module('kibana/table_vis'); angular.mock.inject(($injector) => { diff --git a/src/plugins/vis_type_table/public/legacy/get_inner_angular.ts b/src/plugins/vis_type_table/public/legacy/get_inner_angular.ts index 09fde318ee4df..412dd904a5e87 100644 --- a/src/plugins/vis_type_table/public/legacy/get_inner_angular.ts +++ b/src/plugins/vis_type_table/public/legacy/get_inner_angular.ts @@ -16,7 +16,6 @@ import 'angular-recursion'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; import { CoreStart, IUiSettingsClient, PluginInitializerContext } from 'kibana/public'; import { - initAngularBootstrap, PaginateDirectiveProvider, PaginateControlsDirectiveProvider, PrivateProvider, @@ -24,8 +23,6 @@ import { KbnAccessibleClickProvider, } from '../../../kibana_legacy/public'; -initAngularBootstrap(); - const thirdPartyAngularDependencies = ['ngSanitize', 'ui.bootstrap', 'RecursionHelper']; export function getAngularModule(name: string, core: CoreStart, context: PluginInitializerContext) { diff --git a/src/plugins/vis_type_table/public/legacy/paginated_table/paginated_table.test.ts b/src/plugins/vis_type_table/public/legacy/paginated_table/paginated_table.test.ts index 77148803e7978..3feff52f86792 100644 --- a/src/plugins/vis_type_table/public/legacy/paginated_table/paginated_table.test.ts +++ b/src/plugins/vis_type_table/public/legacy/paginated_table/paginated_table.test.ts @@ -12,6 +12,7 @@ import $ from 'jquery'; import 'angular-sanitize'; import 'angular-mocks'; +import { initAngularBootstrap } from '../../../../kibana_legacy/public/angular_bootstrap'; import { getAngularModule } from '../get_inner_angular'; import { initTableVisLegacyModule } from '../table_vis_legacy_module'; import { coreMock } from '../../../../../core/public/mocks'; @@ -56,6 +57,10 @@ describe('Table Vis - Paginated table', () => { const defaultPerPage = 10; let paginatedTable: any; + beforeAll(async () => { + await initAngularBootstrap(); + }); + const initLocalAngular = () => { const tableVisModule = getAngularModule( 'kibana/table_vis', diff --git a/src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts b/src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts index 36a9cc9cce77f..f4a742ea16cb4 100644 --- a/src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts +++ b/src/plugins/vis_type_table/public/legacy/table_vis_controller.test.ts @@ -13,6 +13,7 @@ import $ from 'jquery'; import { getAngularModule } from './get_inner_angular'; import { initTableVisLegacyModule } from './table_vis_legacy_module'; +import { initAngularBootstrap } from '../../../kibana_legacy/public/angular_bootstrap'; import { tableVisLegacyTypeDefinition } from './table_vis_legacy_type'; import { Vis } from '../../../visualizations/public'; import { stubFields } from '../../../data/public/stubs'; @@ -76,6 +77,9 @@ describe('Table Vis - Controller', () => { initTableVisLegacyModule(tableVisModule); }; + beforeAll(async () => { + await initAngularBootstrap(); + }); beforeEach(initLocalAngular); beforeEach(angular.mock.module('kibana/table_vis')); diff --git a/src/plugins/vis_type_table/public/legacy/vis_controller.ts b/src/plugins/vis_type_table/public/legacy/vis_controller.ts index ee446c58c0013..ec198aa96f1f9 100644 --- a/src/plugins/vis_type_table/public/legacy/vis_controller.ts +++ b/src/plugins/vis_type_table/public/legacy/vis_controller.ts @@ -56,6 +56,7 @@ export function getTableVisualizationControllerClass( async initLocalAngular() { if (!this.tableVisModule) { const [coreStart, { kibanaLegacy }] = await core.getStartServices(); + await kibanaLegacy.loadAngularBootstrap(); this.tableVisModule = getAngularModule(innerAngularName, coreStart, context); initTableVisLegacyModule(this.tableVisModule); kibanaLegacy.loadFontAwesome(); diff --git a/x-pack/plugins/graph/public/plugin.ts b/x-pack/plugins/graph/public/plugin.ts index 4525b42b3feb4..ec19e639b91c9 100644 --- a/x-pack/plugins/graph/public/plugin.ts +++ b/x-pack/plugins/graph/public/plugin.ts @@ -19,10 +19,7 @@ import { } from '../../../../src/core/public'; import { Storage } from '../../../../src/plugins/kibana_utils/public'; -import { - initAngularBootstrap, - KibanaLegacyStart, -} from '../../../../src/plugins/kibana_legacy/public'; +import { KibanaLegacyStart } from '../../../../src/plugins/kibana_legacy/public'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../src/plugins/navigation/public'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; @@ -77,7 +74,6 @@ export class GraphPlugin const config = this.initializerContext.config.get(); - initAngularBootstrap(); core.application.register({ id: 'graph', title: 'Graph', @@ -88,6 +84,7 @@ export class GraphPlugin updater$: this.appUpdater$, mount: async (params: AppMountParameters) => { const [coreStart, pluginsStart] = await core.getStartServices(); + await pluginsStart.kibanaLegacy.loadAngularBootstrap(); coreStart.chrome.docTitle.change( i18n.translate('xpack.graph.pageTitle', { defaultMessage: 'Graph' }) ); diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts index a5b7d4906b586..9f84165a27ba9 100644 --- a/x-pack/plugins/monitoring/public/plugin.ts +++ b/x-pack/plugins/monitoring/public/plugin.ts @@ -93,7 +93,10 @@ export class MonitoringPlugin category: DEFAULT_APP_CATEGORIES.management, mount: async (params: AppMountParameters) => { const [coreStart, pluginsStart] = await core.getStartServices(); - const { AngularApp } = await import('./angular'); + const [, { AngularApp }] = await Promise.all([ + pluginsStart.kibanaLegacy.loadAngularBootstrap(), + import('./angular'), + ]); const deps: MonitoringStartPluginDependencies = { navigation: pluginsStart.navigation, kibanaLegacy: pluginsStart.kibanaLegacy, From 87066e06b3cfe238b021f786896afdfd74b86303 Mon Sep 17 00:00:00 2001 From: Diana Derevyankina <54894989+DziyanaDzeraviankina@users.noreply.github.com> Date: Mon, 12 Jul 2021 17:25:52 +0300 Subject: [PATCH 5/9] [TSVB] Top_hit supports runtime fields (#103401) * [TSVB] Refactor top-hit aggregation to work with fields instead of _source * Allow select date strings for top_hit aggregation in table, metric, and markdown * Fix agg_with handling for top_hit and add some tests * Refactor get_agg_value and fix type check for _tsvb_chart * Refactor top_hit.js Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../application/components/aggs/top_hit.js | 37 +++++-- .../components/lib/tick_formatter.js | 2 +- .../lib/vis_data/helpers/bucket_transform.js | 2 +- .../lib/vis_data/helpers/get_agg_value.js | 4 +- .../vis_data/helpers/get_agg_value.test.js | 6 +- test/functional/apps/visualize/_tsvb_chart.ts | 103 +++++++++++++++--- .../fixtures/kbn_archiver/visualize.json | 3 +- .../page_objects/visual_builder_page.ts | 47 ++++++++ 8 files changed, 168 insertions(+), 36 deletions(-) diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/top_hit.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/top_hit.js index 546c09cdf34fd..b9ef2d8913574 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/top_hit.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/top_hit.js @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React from 'react'; +import React, { useMemo, useEffect } from 'react'; import { AggRow } from './agg_row'; import { AggSelect } from './agg_select'; import { FieldSelect } from './field_select'; @@ -62,6 +62,7 @@ const getAggWithOptions = (field = {}, fieldTypesRestriction) => { }, ]; case KBN_FIELD_TYPES.STRING: + case KBN_FIELD_TYPES.DATE: return [ { label: i18n.translate('visTypeTimeseries.topHit.aggWithOptions.concatenate', { @@ -91,16 +92,18 @@ const getOrderOptions = () => [ }, ]; +const AGG_WITH_KEY = 'agg_with'; const ORDER_DATE_RESTRICT_FIELDS = [KBN_FIELD_TYPES.DATE]; +const getModelDefaults = () => ({ + size: 1, + order: 'desc', + [AGG_WITH_KEY]: 'noop', +}); + const TopHitAggUi = (props) => { const { fields, series, panel } = props; - const defaults = { - size: 1, - agg_with: 'noop', - order: 'desc', - }; - const model = { ...defaults, ...props.model }; + const model = useMemo(() => ({ ...getModelDefaults(), ...props.model }), [props.model]); const indexPattern = series.override_index_pattern ? series.series_index_pattern : panel.index_pattern; @@ -110,7 +113,7 @@ const TopHitAggUi = (props) => { PANEL_TYPES.METRIC, PANEL_TYPES.MARKDOWN, ].includes(panel.type) - ? [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.STRING] + ? [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.STRING, KBN_FIELD_TYPES.DATE] : [KBN_FIELD_TYPES.NUMBER]; const handleChange = createChangeHandler(props.onChange, model); @@ -124,13 +127,23 @@ const TopHitAggUi = (props) => { const htmlId = htmlIdGenerator(); const selectedAggWithOption = aggWithOptions.find((option) => { - return model.agg_with === option.value; + return model[AGG_WITH_KEY] === option.value; }); const selectedOrderOption = orderOptions.find((option) => { return model.order === option.value; }); + useEffect(() => { + const defaultFn = aggWithOptions?.[0]?.value; + const aggWith = model[AGG_WITH_KEY]; + if (aggWith && defaultFn && aggWith !== defaultFn && !selectedAggWithOption) { + handleChange({ + [AGG_WITH_KEY]: defaultFn, + }); + } + }, [model, selectedAggWithOption, aggWithOptions, handleChange]); + return ( {
{ )} options={aggWithOptions} selectedOptions={selectedAggWithOption ? [selectedAggWithOption] : []} - onChange={handleSelectChange('agg_with')} + onChange={handleSelectChange(AGG_WITH_KEY)} singleSelection={{ asPlainText: true }} + data-test-subj="topHitAggregateWithComboBox" /> @@ -231,6 +245,7 @@ const TopHitAggUi = (props) => { onChange={handleSelectChange('order_by')} indexPattern={indexPattern} fields={fields} + data-test-subj="topHitOrderByFieldSelect" /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js index c1d82a182e509..9bccc13d19269 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js @@ -16,7 +16,7 @@ export const createTickFormatter = (format = '0,0.[00]', template, getConfig = n const fieldFormats = getFieldFormats(); if (!template) template = '{{value}}'; - const render = handlebars.compile(template, { knownHelpersOnly: true }); + const render = handlebars.compile(template, { noEscape: true, knownHelpersOnly: true }); let formatter; if (isDuration(format)) { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.js index 16e7b9d6072cb..13b890189325c 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.js @@ -111,7 +111,7 @@ export const bucketTransform = { docs: { top_hits: { size: bucket.size, - _source: { includes: [bucket.field] }, + fields: [bucket.field], }, }, }, diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.js index 32d17ef6d6cb7..90df3f2675959 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.js @@ -45,10 +45,10 @@ export const getAggValue = (row, metric) => { } const hits = get(row, [metric.id, 'docs', 'hits', 'hits'], []); - const values = hits.map((doc) => get(doc, `_source.${metric.field}`)); + const values = hits.map((doc) => doc.fields[metric.field]); const aggWith = (metric.agg_with && aggFns[metric.agg_with]) || aggFns.noop; - return aggWith(values); + return aggWith(values.flat()); case METRIC_TYPES.COUNT: return get(row, 'doc_count', null); default: diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.test.js index a23c57f567563..ecbdd1563c304 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.test.js @@ -67,11 +67,7 @@ describe('getAggValue', () => { doc_count: 1, docs: { hits: { - hits: [ - { _source: { example: { value: 25 } } }, - { _source: { example: { value: 25 } } }, - { _source: { example: { value: 25 } } }, - ], + hits: [{ fields: { 'example.value': [25, 25, 25] } }], }, }, }, diff --git a/test/functional/apps/visualize/_tsvb_chart.ts b/test/functional/apps/visualize/_tsvb_chart.ts index ca310493960f5..49b2ad8f9646a 100644 --- a/test/functional/apps/visualize/_tsvb_chart.ts +++ b/test/functional/apps/visualize/_tsvb_chart.ts @@ -24,6 +24,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'timePicker', 'visChart', 'common', + 'settings', ]); describe('visual builder', function describeIndexTests() { @@ -44,14 +45,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); describe('metric', () => { + const { visualBuilder } = PageObjects; + beforeEach(async () => { - await PageObjects.visualBuilder.resetPage(); - await PageObjects.visualBuilder.clickMetric(); - await PageObjects.visualBuilder.checkMetricTabIsPresent(); - await PageObjects.visualBuilder.clickPanelOptions('metric'); - await PageObjects.visualBuilder.setMetricsDataTimerangeMode('Last value'); - await PageObjects.visualBuilder.setDropLastBucket(true); - await PageObjects.visualBuilder.clickDataTab('metric'); + await visualBuilder.resetPage(); + await visualBuilder.clickMetric(); + await visualBuilder.checkMetricTabIsPresent(); + await visualBuilder.clickPanelOptions('metric'); + await visualBuilder.setMetricsDataTimerangeMode('Last value'); + await visualBuilder.setDropLastBucket(true); + await visualBuilder.clickDataTab('metric'); }); it('should not have inspector enabled', async () => { @@ -59,28 +62,98 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should show correct data', async () => { - const value = await PageObjects.visualBuilder.getMetricValue(); + const value = await visualBuilder.getMetricValue(); expect(value).to.eql('156'); }); it('should show correct data with Math Aggregation', async () => { - await PageObjects.visualBuilder.createNewAgg(); - await PageObjects.visualBuilder.selectAggType('math', 1); - await PageObjects.visualBuilder.fillInVariable(); - await PageObjects.visualBuilder.fillInExpression('params.test + 1'); - const value = await PageObjects.visualBuilder.getMetricValue(); + await visualBuilder.createNewAgg(); + await visualBuilder.selectAggType('math', 1); + await visualBuilder.fillInVariable(); + await visualBuilder.fillInExpression('params.test + 1'); + const value = await visualBuilder.getMetricValue(); expect(value).to.eql('157'); }); it('should populate fields for basic functions', async () => { - const { visualBuilder } = PageObjects; - await visualBuilder.selectAggType('Average'); await visualBuilder.setFieldForAggregation('machine.ram'); const isFieldForAggregationValid = await visualBuilder.checkFieldForAggregationValidity(); expect(isFieldForAggregationValid).to.be(true); }); + + it('should show correct data for Value Count with Entire time range mode', async () => { + await visualBuilder.selectAggType('Value Count'); + await visualBuilder.setFieldForAggregation('machine.ram'); + + await visualBuilder.clickPanelOptions('metric'); + await visualBuilder.setMetricsDataTimerangeMode('Entire time range'); + + const value = await visualBuilder.getMetricValue(); + expect(value).to.eql('13,492'); + }); + + it('should show same data for kibana and string index pattern modes', async () => { + await visualBuilder.selectAggType('Max'); + await visualBuilder.setFieldForAggregation('machine.ram'); + const kibanaIndexPatternModeValue = await visualBuilder.getMetricValue(); + + await visualBuilder.clickPanelOptions('metric'); + await visualBuilder.switchIndexPatternSelectionMode(false); + const stringIndexPatternModeValue = await visualBuilder.getMetricValue(); + + expect(kibanaIndexPatternModeValue).to.eql(stringIndexPatternModeValue); + expect(kibanaIndexPatternModeValue).to.eql('32,212,254,720'); + }); + + describe('Color rules', () => { + beforeEach(async () => { + await visualBuilder.selectAggType('Min'); + await visualBuilder.setFieldForAggregation('machine.ram'); + + await visualBuilder.clickPanelOptions('metric'); + await visualBuilder.setColorRuleOperator('>= greater than or equal'); + await visualBuilder.setColorRuleValue(0); + }); + + it('should apply color rules to visualization background', async () => { + await visualBuilder.setColorPickerValue('#FFCFDF'); + + const backGroundStyle = await visualBuilder.getBackgroundStyle(); + expect(backGroundStyle).to.eql('background-color: rgb(255, 207, 223);'); + }); + + it('should apply color rules to metric value', async () => { + await visualBuilder.setColorPickerValue('#AD7DE6', 1); + + const backGroundStyle = await visualBuilder.getMetricValueStyle(); + expect(backGroundStyle).to.eql('color: rgb(173, 125, 230);'); + }); + }); + + describe('Top Hit aggregation', () => { + beforeEach(async () => { + await visualBuilder.selectAggType('Top Hit'); + await visualBuilder.setTopHitOrderByField('@timestamp'); + }); + + it('should show correct data for string type field', async () => { + await visualBuilder.setFieldForAggregation('machine.os.raw'); + await visualBuilder.setTopHitAggregateWithOption('Concatenate'); + + const value = await visualBuilder.getMetricValue(); + expect(value).to.eql('win 7'); + }); + + it('should show correct data for runtime field', async () => { + await visualBuilder.setFieldForAggregation('hello_world_runtime_field'); + await visualBuilder.setTopHitAggregateWithOption('Concatenate'); + + const value = await visualBuilder.getMetricValue(); + expect(value).to.eql('hello world'); + }); + }); }); describe('gauge', () => { diff --git a/test/functional/fixtures/kbn_archiver/visualize.json b/test/functional/fixtures/kbn_archiver/visualize.json index 660da856964b4..225dc0592e87d 100644 --- a/test/functional/fixtures/kbn_archiver/visualize.json +++ b/test/functional/fixtures/kbn_archiver/visualize.json @@ -3,6 +3,7 @@ "fieldAttrs": "{\"utc_time\":{\"customLabel\":\"UTC time\"}}", "fieldFormatMap": "{\"bytes\":{\"id\":\"bytes\"}}", "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}]", + "runtimeFieldMap":"{\"hello_world_runtime_field\":{\"type\":\"keyword\",\"script\":{\"source\":\"emit('hello world')\"}}}", "timeFieldName": "@timestamp", "title": "logstash-*" }, @@ -301,4 +302,4 @@ "references": [], "type": "index-pattern", "version": "WzE1LDFd" -} \ No newline at end of file +} diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 8e28ffab6c9c3..fd89a88658b3a 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -575,6 +575,42 @@ export class VisualBuilderPageObject extends FtrService { await this.testSubjects.existOrFail('euiColorPickerPopover', { timeout: 5000 }); } + public async setColorPickerValue(colorHex: string, nth: number = 0): Promise { + const picker = await this.find.allByCssSelector('.tvbColorPicker button'); + await picker[nth].clickMouseButton(); + await this.checkColorPickerPopUpIsPresent(); + await this.find.setValue('.euiColorPicker input', colorHex); + await this.visChart.waitForVisualizationRenderingStabilized(); + } + + public async setColorRuleOperator(condition: string): Promise { + await this.retry.try(async () => { + await this.comboBox.clearInputField('colorRuleOperator'); + await this.comboBox.set('colorRuleOperator', condition); + }); + } + + public async setColorRuleValue(value: number): Promise { + await this.retry.try(async () => { + const colorRuleValueInput = await this.find.byCssSelector( + '[data-test-subj="colorRuleValue"]' + ); + await colorRuleValueInput.type(value.toString()); + }); + } + + public async getBackgroundStyle(): Promise { + await this.visChart.waitForVisualizationRenderingStabilized(); + const visualization = await this.find.byClassName('tvbVis'); + return await visualization.getAttribute('style'); + } + + public async getMetricValueStyle(): Promise { + await this.visChart.waitForVisualizationRenderingStabilized(); + const metricValue = await this.find.byCssSelector('[data-test-subj="tsvbMetricValue"]'); + return await metricValue.getAttribute('style'); + } + public async changePanelPreview(nth: number = 0): Promise { const prevRenderingCount = await this.visChart.getVisualizationRenderingCount(); const changePreviewBtnArray = await this.testSubjects.findAll('AddActivatePanelBtn'); @@ -680,4 +716,15 @@ export class VisualBuilderPageObject extends FtrService { const dataTimeRangeMode = await this.testSubjects.find('dataTimeRangeMode'); return await this.comboBox.isOptionSelected(dataTimeRangeMode, value); } + + public async setTopHitAggregateWithOption(option: string): Promise { + await this.comboBox.set('topHitAggregateWithComboBox', option); + } + + public async setTopHitOrderByField(timeField: string) { + await this.retry.try(async () => { + await this.comboBox.clearInputField('topHitOrderByFieldSelect'); + await this.comboBox.set('topHitOrderByFieldSelect', timeField); + }); + } } From a80791aa4ccc2902820591299ce8a89c364d2cd6 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Mon, 12 Jul 2021 10:26:41 -0400 Subject: [PATCH 6/9] Pass locale to calendar (#105134) --- .../components/analytics/components/analytics_filters.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.tsx index 0c8455e986ae1..dd99d368a0105 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_filters.tsx @@ -80,6 +80,7 @@ export const AnalyticsFilters: React.FC = () => { 'xpack.enterpriseSearch.appSearch.engine.analytics.filters.startDateAriaLabel', { defaultMessage: 'Filter by start date' } )} + locale={i18n.getLocale()} /> } endDateControl={ @@ -93,6 +94,7 @@ export const AnalyticsFilters: React.FC = () => { 'xpack.enterpriseSearch.appSearch.engine.analytics.filters.endDateAriaLabel', { defaultMessage: 'Filter by end date' } )} + locale={i18n.getLocale()} /> } fullWidth From d6a36926008b000d006d012edafed5cad6a7f3f6 Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Mon, 12 Jul 2021 09:58:39 -0500 Subject: [PATCH 7/9] [DOCS] Adds how to create dashboard drilldowns for Top N and Table TSVB panels (#104548) --- docs/user/dashboard/tsvb.asciidoc | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/user/dashboard/tsvb.asciidoc b/docs/user/dashboard/tsvb.asciidoc index 89da3f7285924..11fe71b7639bb 100644 --- a/docs/user/dashboard/tsvb.asciidoc +++ b/docs/user/dashboard/tsvb.asciidoc @@ -148,6 +148,27 @@ The *Markdown* visualization supports Markdown with Handlebar (mustache) syntax For answers to frequently asked *TSVB* question, review the following. +[float] +===== How do I create dashboard drilldowns for Top N and Table visualizations? + +You can create dashboard drilldowns that include the specified time range for *Top N* and *Table* visualizations. + +. Open the dashboard that you want to link to, then copy the URL. + +. Open the dashboard with the *Top N* and *Table* visualization panel, then click *Edit* in the toolbar. + +. Open the *Top N* or *Table* panel menu, then select *Edit visualization*. + +. Click *Panel options*. + +. In the *Item URL* field, enter the URL. ++ +For example `dashboards#/view/f193ca90-c9f4-11eb-b038-dd3270053a27`. + +. Click *Save and return*. + +. In the toolbar, cick *Save as*, then make sure *Store time with dashboard* is deselected. + [float] ===== Why is my TSVB visualization missing data? From 1aa9459ba2d8c4e736fdae5c0f4f4a59f09c7973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ece=20=C3=96zalp?= Date: Mon, 12 Jul 2021 11:07:34 -0400 Subject: [PATCH 8/9] [CTI] converts disabled panel danger to warning (#104989) --- .../overview_cti_links/cti_disabled_module.tsx | 4 ++-- .../overview_cti_links/cti_inner_panel.tsx | 12 ++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx index 21a4beca72f3b..1600356882c36 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx @@ -21,11 +21,11 @@ export const CtiDisabledModuleComponent = () => { const danger = useMemo( () => ( + {i18n.DANGER_BUTTON} } diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_inner_panel.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_inner_panel.tsx index 08bf0a432f9bb..ddff78608dfb0 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_inner_panel.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_inner_panel.tsx @@ -17,13 +17,9 @@ const ButtonContainer = styled(EuiFlexGroup)` padding: ${({ theme }) => theme.eui.paddingSizes.s}; `; -const Title = styled(EuiText)<{ textcolor: 'primary' | 'warning' | 'danger' }>` +const Title = styled(EuiText)<{ textcolor: 'primary' | 'warning' }>` color: ${({ theme, textcolor }) => - textcolor === 'primary' - ? theme.eui.euiColorPrimary - : textcolor === 'warning' - ? theme.eui.euiColorWarningText - : theme.eui.euiColorDangerText}; + textcolor === 'primary' ? theme.eui.euiColorPrimary : theme.eui.euiColorWarningText}; margin-bottom: ${({ theme }) => theme.eui.paddingSizes.m}; `; @@ -40,12 +36,12 @@ export const CtiInnerPanel = ({ body, button, }: { - color: 'primary' | 'warning' | 'danger'; + color: 'primary' | 'warning'; title: string; body: string; button?: JSX.Element; }) => { - const iconType = color === 'primary' ? 'iInCircle' : color === 'warning' ? 'help' : 'alert'; + const iconType = color === 'primary' ? 'iInCircle' : 'help'; return ( From 0c9777c6020501b6c953b4bca97f41dd858549f1 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 12 Jul 2021 11:23:57 -0400 Subject: [PATCH 9/9] [Uptime] Refactor page headers to avoid invalid markup (#104215) * Refactor monitor and waterfall page headers to avoid rendering invalid markup. * Update tests. * Translate page titles. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/monitor/monitor_title.test.tsx | 37 ++++++++++------- .../components/monitor/monitor_title.tsx | 41 +++++++++---------- .../step_detail/step_detail_container.tsx | 9 ++-- .../step_detail/step_page_title.tsx | 13 ++---- x-pack/plugins/uptime/public/routes.tsx | 33 +++++++++++---- .../uptime/server/lib/requests/get_certs.ts | 1 - 6 files changed, 77 insertions(+), 57 deletions(-) diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx index 4fd6335c3d3ca..726ad235f7f49 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx @@ -11,7 +11,7 @@ import { screen } from '@testing-library/react'; import { render } from '../../lib/helper/rtl_helpers'; import * as reactRouterDom from 'react-router-dom'; import { Ping } from '../../../common/runtime_types'; -import { MonitorPageTitle } from './monitor_title'; +import { MonitorPageTitle, MonitorPageTitleContent } from './monitor_title'; jest.mock('react-router-dom', () => { const originalModule = jest.requireActual('react-router-dom'); @@ -77,11 +77,17 @@ describe('MonitorTitle component', () => { }); it('renders the monitor heading and EnableMonitorAlert toggle', () => { - render(, { - state: { monitorStatus: { status: monitorStatusWithName, loading: false } }, - }); - expect(screen.getByRole('heading', { level: 1, name: monitorName })).toBeInTheDocument(); - expect(screen.getByTestId('uptimeDisplayDefineConnector')).toBeInTheDocument(); + render( + <> + + + , + { + state: { monitorStatus: { status: monitorStatusWithName, loading: false } }, + } + ); + expect(screen.getByText(monitorName)); + expect(screen.getByRole('switch')).toBeInTheDocument(); }); it('renders the user provided monitorId when the name is not present', () => { @@ -89,21 +95,24 @@ describe('MonitorTitle component', () => { render(, { state: { monitorStatus: { status: defaultMonitorStatus, loading: false } }, }); - expect(screen.getByRole('heading', { level: 1, name: defaultMonitorId })).toBeInTheDocument(); + expect(screen.getByText(defaultMonitorId)); }); it('renders the url when the monitorId is auto generated and the monitor name is not present', () => { mockReactRouterDomHooks({ useParamsResponse: { monitorId: autoGeneratedMonitorIdEncoded } }); - render(, { - state: { monitorStatus: { status: defaultMonitorStatus, loading: false } }, - }); - expect( - screen.getByRole('heading', { level: 1, name: defaultMonitorStatus.url?.full }) - ).toBeInTheDocument(); + render( +
+ +
, + { + state: { monitorStatus: { status: defaultMonitorStatus, loading: false } }, + } + ); + expect(screen.getByText(defaultMonitorStatus!.url!.full!)); }); it('renders beta disclaimer for synthetics monitors', () => { - render(, { + render(, { state: { monitorStatus: { status: defaultBrowserMonitorStatus, loading: false } }, }); const betaLink = screen.getByRole('link', { diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx index 2112af0653669..aa68e2aa7fc4b 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx @@ -5,15 +5,7 @@ * 2.0. */ -import { - EuiBadge, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiTitle, - EuiLink, - EuiText, -} from '@elastic/eui'; +import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiLink, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; import { useSelector } from 'react-redux'; @@ -40,18 +32,11 @@ const getPageTitle = (monitorId: string, selectedMonitor: Ping | null) => { return monitorId; }; -export const MonitorPageTitle: React.FC = () => { +export const MonitorPageTitleContent: React.FC = () => { const monitorId = useMonitorId(); - const selectedMonitor = useSelector(monitorStatusSelector); - - const nameOrId = selectedMonitor?.monitor?.name || getPageTitle(monitorId, selectedMonitor); - const type = selectedMonitor?.monitor?.type; const isBrowser = type === 'browser'; - - useBreadcrumbs([{ text: nameOrId }]); - const renderMonitorType = (monitorType: string) => { switch (monitorType) { case 'http': @@ -86,12 +71,13 @@ export const MonitorPageTitle: React.FC = () => { return ''; } }; - return ( <> - -

{nameOrId}

-
+ + + + + @@ -118,7 +104,18 @@ export const MonitorPageTitle: React.FC = () => { )} - ); }; + +export const MonitorPageTitle: React.FC = () => { + const monitorId = useMonitorId(); + + const selectedMonitor = useSelector(monitorStatusSelector); + + const nameOrId = selectedMonitor?.monitor?.name || getPageTitle(monitorId, selectedMonitor); + + useBreadcrumbs([{ text: nameOrId }]); + + return {nameOrId}; +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx index 610107f406306..c24ecd9183865 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx @@ -16,7 +16,7 @@ import { useUiSetting$ } from '../../../../../../../../src/plugins/kibana_react/ import { useMonitorBreadcrumb } from './use_monitor_breadcrumb'; import { ClientPluginsStart } from '../../../../apps/plugin'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; -import { StepPageTitle } from './step_page_title'; +import { StepPageTitleContent } from './step_page_title'; import { StepPageNavigation } from './step_page_nav'; import { WaterfallChartContainer } from './waterfall/waterfall_chart_container'; @@ -78,10 +78,11 @@ export const StepDetailContainer: React.FC = ({ checkGroup, stepIndex }) return ( void; handleNextStep: () => void; } -export const StepPageTitle = ({ - stepName, + +export const StepPageTitleContent = ({ stepIndex, totalSteps, handleNextStep, @@ -29,11 +29,6 @@ export const StepPageTitle = ({ }: Props) => { return ( - - -

{stepName}

-
-
diff --git a/x-pack/plugins/uptime/public/routes.tsx b/x-pack/plugins/uptime/public/routes.tsx index e3c558cee2c32..2b0cc4dc5e5c2 100644 --- a/x-pack/plugins/uptime/public/routes.tsx +++ b/x-pack/plugins/uptime/public/routes.tsx @@ -23,7 +23,7 @@ import { UptimePage, useUptimeTelemetry } from './hooks'; import { OverviewPageComponent } from './pages/overview'; import { SyntheticsCheckSteps } from './pages/synthetics/synthetics_checks'; import { ClientPluginsStart } from './apps/plugin'; -import { MonitorPageTitle } from './components/monitor/monitor_title'; +import { MonitorPageTitle, MonitorPageTitleContent } from './components/monitor/monitor_title'; import { UptimeDatePicker } from './components/common/uptime_date_picker'; import { useKibana } from '../../../../src/plugins/kibana_react/public'; import { CertRefreshBtn } from './components/certificates/cert_refresh_btn'; @@ -36,10 +36,16 @@ interface RouteProps { dataTestSubj: string; title: string; telemetryId: UptimePage; - pageHeader?: { pageTitle: string | JSX.Element; rightSideItems?: JSX.Element[] }; + pageHeader?: { + children?: JSX.Element; + pageTitle: string | JSX.Element; + rightSideItems?: JSX.Element[]; + }; } -const baseTitle = 'Uptime - Kibana'; +const baseTitle = i18n.translate('xpack.uptime.routes.baseTitle', { + defaultMessage: 'Uptime - Kibana', +}); export const MONITORING_OVERVIEW_LABEL = i18n.translate('xpack.uptime.overview.heading', { defaultMessage: 'Monitors', @@ -47,18 +53,25 @@ export const MONITORING_OVERVIEW_LABEL = i18n.translate('xpack.uptime.overview.h const Routes: RouteProps[] = [ { - title: `Monitor | ${baseTitle}`, + title: i18n.translate('xpack.uptime.monitorRoute.title', { + defaultMessage: 'Monitor | {baseTitle}', + values: { baseTitle }, + }), path: MONITOR_ROUTE, component: MonitorPage, dataTestSubj: 'uptimeMonitorPage', telemetryId: UptimePage.Monitor, pageHeader: { + children: , pageTitle: , rightSideItems: [], }, }, { - title: `Settings | ${baseTitle}`, + title: i18n.translate('xpack.uptime.settingsRoute.title', { + defaultMessage: `Settings | {baseTitle}`, + values: { baseTitle }, + }), path: SETTINGS_ROUTE, component: SettingsPage, dataTestSubj: 'uptimeSettingsPage', @@ -70,7 +83,10 @@ const Routes: RouteProps[] = [ }, }, { - title: `Certificates | ${baseTitle}`, + title: i18n.translate('xpack.uptime.certificatesRoute.title', { + defaultMessage: `Certificates | {baseTitle}`, + values: { baseTitle }, + }), path: CERTIFICATES_ROUTE, component: CertificatesPage, dataTestSubj: 'uptimeCertificatesPage', @@ -81,7 +97,10 @@ const Routes: RouteProps[] = [ }, }, { - title: baseTitle, + title: i18n.translate('xpack.uptime.stepDetailRoute.title', { + defaultMessage: 'Synthetics detail | {baseTitle}', + values: { baseTitle }, + }), path: STEP_DETAIL_ROUTE, component: StepDetailPage, dataTestSubj: 'uptimeStepDetailPage', diff --git a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts index 1b20ed9085fef..7639484f51737 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts @@ -138,7 +138,6 @@ export const getCerts: UMElasticsearchQueryFn = asyn searchBody.query.bool.filter.push(validityFilters); } - // console.log(JSON.stringify(params, null, 2)); const { body: result } = await uptimeEsClient.search({ body: searchBody, });