diff --git a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js index 556a3f2f691f..5b971290092a 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js +++ b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js @@ -113,7 +113,7 @@ export const IndexPattern = ({ const defaults = { [indexPatternName]: '', [intervalName]: AUTO_INTERVAL, - [dropBucketName]: 1, + [dropBucketName]: 0, [maxBarsName]: config.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET), [TIME_RANGE_MODE_KEY]: timeRangeOptions[0].value, }; diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx index ae9d7326140a..86d3d50eb1f6 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx @@ -406,6 +406,7 @@ export class TimeseriesPanelConfig extends Component< this.switchTab(PANEL_CONFIG_TABS.DATA)} + data-test-subj="timeSeriesEditorDataBtn" > series.series_drop_last_bucket) + )} /> diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js index f9a52a9450dc..a7ef1ff34395 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js @@ -32,6 +32,9 @@ import { getStackAccessors } from './utils/stack_format'; import { getBaseTheme, getChartClasses } from './utils/theme'; import { emptyLabel } from '../../../../../common/empty_label'; import { getSplitByTermsColor } from '../../../lib/get_split_by_terms_color'; +import { renderEndzoneTooltip } from '../../../../../../charts/public'; +import { getAxisLabelString } from '../../../components/lib/get_axis_label_string'; +import { calculateDomainForSeries } from './utils/series_domain_calculation'; const generateAnnotationData = (values, formatter) => values.map(({ key, docs }) => ({ @@ -54,7 +57,6 @@ export const TimeSeries = ({ legend, legendPosition, tooltipMode, - xAxisLabel, series, yAxis, onBrush, @@ -62,6 +64,8 @@ export const TimeSeries = ({ annotations, syncColors, palettesService, + interval, + isLastBucketDropped, }) => { const chartRef = useRef(); // const [palettesRegistry, setPalettesRegistry] = useState(null); @@ -80,7 +84,17 @@ export const TimeSeries = ({ }; }, []); - const tooltipFormatter = decorateFormatter(xAxisFormatter); + let tooltipFormatter = decorateFormatter(xAxisFormatter); + if (!isLastBucketDropped) { + const domainBounds = calculateDomainForSeries(series); + tooltipFormatter = renderEndzoneTooltip( + interval, + domainBounds?.domainStart, + domainBounds?.domainEnd, + xAxisFormatter + ); + } + const uiSettings = getUISettings(); const timeZone = getTimezone(uiSettings); const hasBarChart = series.some(({ bars }) => bars?.show); @@ -281,7 +295,7 @@ export const TimeSeries = ({ { + const seriesData = series[0]?.data || []; + + return seriesData?.length + ? { + domainStart: seriesData[0][0], + domainEnd: seriesData[Math.max(seriesData.length - 1, 0)][0], + } + : undefined; +}; diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/series_domain_calculations.test.ts b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/series_domain_calculations.test.ts new file mode 100644 index 000000000000..5b502636003f --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/utils/series_domain_calculations.test.ts @@ -0,0 +1,35 @@ +/* + * 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 { calculateDomainForSeries } from './series_domain_calculation'; +import { PanelData } from 'src/plugins/vis_type_timeseries/common/types'; + +describe('calculateDomainForSeries', () => { + it('should return 0 for domainStart and 3 for domainEnd', () => { + const series = [ + { + data: [ + [0, 0], + [1, 1], + [2, 2], + [3, 3], + ], + }, + ] as PanelData[]; + const domainBounds = calculateDomainForSeries(series); + + expect(domainBounds?.domainStart).toBe(0); + expect(domainBounds?.domainEnd).toBe(3); + }); + + it('should return undefined when series is empty', () => { + const domainBounds = calculateDomainForSeries([]); + + expect(domainBounds).toBeUndefined(); + }); +}); diff --git a/src/plugins/vis_type_timeseries/public/metrics_type.ts b/src/plugins/vis_type_timeseries/public/metrics_type.ts index 797e40df2271..6200f08bee32 100644 --- a/src/plugins/vis_type_timeseries/public/metrics_type.ts +++ b/src/plugins/vis_type_timeseries/public/metrics_type.ts @@ -62,6 +62,7 @@ export const metricsVisDefinition = { show_legend: 1, show_grid: 1, tooltip_mode: 'show_all', + drop_last_bucket: 0, }, }, editorConfig: { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/drop_last_bucket.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/drop_last_bucket.js index 49c1f631953e..ad63fcc687a5 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/drop_last_bucket.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/drop_last_bucket.js @@ -14,8 +14,9 @@ export function dropLastBucket(resp, panel, series) { const shouldDropLastBucket = isLastValueTimerangeMode(panel, series); if (shouldDropLastBucket) { - const seriesDropLastBucket = get(series, 'override_drop_last_bucket', 1); - const dropLastBucket = get(panel, 'drop_last_bucket', seriesDropLastBucket); + const dropLastBucket = series.override_index_pattern + ? get(series, 'series_drop_last_bucket', 0) + : get(panel, 'drop_last_bucket', 0); if (dropLastBucket) { results.forEach((item) => { diff --git a/test/functional/apps/visualize/_tsvb_chart.ts b/test/functional/apps/visualize/_tsvb_chart.ts index 6b0080c3856f..6568eab0fc1f 100644 --- a/test/functional/apps/visualize/_tsvb_chart.ts +++ b/test/functional/apps/visualize/_tsvb_chart.ts @@ -45,6 +45,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 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'); }); @@ -106,6 +107,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visualBuilder.checkTopNTabIsPresent(); await PageObjects.visualBuilder.clickPanelOptions('topN'); await PageObjects.visualBuilder.setMetricsDataTimerangeMode('Last value'); + await PageObjects.visualBuilder.setDropLastBucket(true); await PageObjects.visualBuilder.clickDataTab('topN'); }); @@ -129,6 +131,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 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 PageObjects.timePicker.setAbsoluteRange( 'Sep 22, 2019 @ 00:00:00.000', @@ -215,6 +218,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const finalLegendItems = ['jpg: 106', 'css: 22', 'png: 14', 'gif: 8', 'php: 6']; log.debug('Group metrics by terms: extension.raw'); + await PageObjects.visualBuilder.clickPanelOptions('timeSeries'); + await PageObjects.visualBuilder.setDropLastBucket(true); + await PageObjects.visualBuilder.clickDataTab('timeSeries'); await PageObjects.visualBuilder.setMetricsGroupByTerms('extension.raw'); await PageObjects.visChart.waitForVisualizationRenderingStabilized(); const legendItems1 = await PageObjects.visualBuilder.getLegendItemsContent(); diff --git a/test/functional/apps/visualize/_tsvb_markdown.ts b/test/functional/apps/visualize/_tsvb_markdown.ts index b61fbf967a9b..880255eede5a 100644 --- a/test/functional/apps/visualize/_tsvb_markdown.ts +++ b/test/functional/apps/visualize/_tsvb_markdown.ts @@ -39,6 +39,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); await visualBuilder.markdownSwitchSubTab('options'); await visualBuilder.setMetricsDataTimerangeMode('Last value'); + await visualBuilder.setDropLastBucket(true); await visualBuilder.markdownSwitchSubTab('markdown'); }); diff --git a/test/functional/apps/visualize/_tsvb_table.ts b/test/functional/apps/visualize/_tsvb_table.ts index 36c0e26430ff..662ca59dc192 100644 --- a/test/functional/apps/visualize/_tsvb_table.ts +++ b/test/functional/apps/visualize/_tsvb_table.ts @@ -26,6 +26,7 @@ export default function ({ getPageObjects }: FtrProviderContext) { await visualBuilder.checkTableTabIsPresent(); await visualBuilder.clickPanelOptions('table'); await visualBuilder.setMetricsDataTimerangeMode('Last value'); + await visualBuilder.setDropLastBucket(true); await visualBuilder.clickDataTab('table'); await visualBuilder.selectGroupByField('machine.os.raw'); await visualBuilder.setColumnLabelValue('OS'); diff --git a/test/functional/apps/visualize/_tsvb_time_series.ts b/test/functional/apps/visualize/_tsvb_time_series.ts index bf5a2fc115ac..85d445bc34e6 100644 --- a/test/functional/apps/visualize/_tsvb_time_series.ts +++ b/test/functional/apps/visualize/_tsvb_time_series.ts @@ -26,6 +26,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('Time Series', () => { beforeEach(async () => { await visualBuilder.resetPage(); + await visualBuilder.clickPanelOptions('timeSeries'); + await visualBuilder.setDropLastBucket(true); + await visualBuilder.clickDataTab('timeSeries'); }); it('should render all necessary components', async () => { diff --git a/x-pack/test/api_integration/apis/metrics_ui/metrics.ts b/x-pack/test/api_integration/apis/metrics_ui/metrics.ts index 3bbdfcef071c..5204d7c499aa 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/metrics.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/metrics.ts @@ -69,8 +69,8 @@ export default function ({ getService }: FtrProviderContext) { expect(series).to.have.property('id', 'user'); expect(series).to.have.property('data'); const datapoint = last(series.data) as any; - expect(datapoint).to.have.property('timestamp', 1547571720000); - expect(datapoint).to.have.property('value', 0.0018333333333333333); + expect(datapoint).to.have.property('timestamp', 1547571780000); + expect(datapoint).to.have.property('value', 0.0015); }); });