[TSVB] Change the default mode from last value to entire timerange (#93608)

* Make 'enter time range' value as default and add telemetry for 'last value' mode

* Fix telemetry schema

* Fix test

* Add possibility count timeseries created from dashboard

* Fix remark

* Fix remark

* Fix problem with time_range_mode

* Fix tests

* Fix tests

* Fix tests for markdown and table

* exclude TSVB which have type as timeseries

* Add description for field in schema in telemetry

* Fix telemetry schema

* Fix some remarks

* Added check for hits

* fix CI

* fix CI

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Alexey Antonov <alexwizp@gmail.com>
This commit is contained in:
Uladzislau Lasitsa 2021-03-29 17:31:01 +03:00 committed by GitHub
parent d16101f377
commit 0e40b94348
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 459 additions and 9 deletions

View file

@ -9333,6 +9333,16 @@
} }
} }
}, },
"vis_type_timeseries": {
"properties": {
"timeseries_use_last_value_mode_total": {
"type": "long",
"_meta": {
"description": "Number of TSVB visualizations using \"last value\" as a time range"
}
}
}
},
"vis_type_vega": { "vis_type_vega": {
"properties": { "properties": {
"vega_lib_specs_total": { "vega_lib_specs_total": {

View file

@ -5,5 +5,6 @@
"server": true, "server": true,
"ui": true, "ui": true,
"requiredPlugins": ["charts", "data", "expressions", "visualizations", "visualize"], "requiredPlugins": ["charts", "data", "expressions", "visualizations", "visualize"],
"optionalPlugins": ["usageCollection"],
"requiredBundles": ["kibanaUtils", "kibanaReact"] "requiredBundles": ["kibanaUtils", "kibanaReact"]
} }

View file

@ -91,13 +91,6 @@ export const IndexPattern = ({
const handleTextChange = createTextHandler(onChange); const handleTextChange = createTextHandler(onChange);
const timeRangeOptions = [ const timeRangeOptions = [
{
label: i18n.translate('visTypeTimeseries.indexPattern.timeRange.lastValue', {
defaultMessage: 'Last value',
}),
value: TIME_RANGE_DATA_MODES.LAST_VALUE,
disabled: !isTimerangeModeEnabled(TIME_RANGE_DATA_MODES.LAST_VALUE, uiRestrictions),
},
{ {
label: i18n.translate('visTypeTimeseries.indexPattern.timeRange.entireTimeRange', { label: i18n.translate('visTypeTimeseries.indexPattern.timeRange.entireTimeRange', {
defaultMessage: 'Entire time range', defaultMessage: 'Entire time range',
@ -105,6 +98,13 @@ export const IndexPattern = ({
value: TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE, value: TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE,
disabled: !isTimerangeModeEnabled(TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE, uiRestrictions), disabled: !isTimerangeModeEnabled(TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE, uiRestrictions),
}, },
{
label: i18n.translate('visTypeTimeseries.indexPattern.timeRange.lastValue', {
defaultMessage: 'Last value',
}),
value: TIME_RANGE_DATA_MODES.LAST_VALUE,
disabled: !isTimerangeModeEnabled(TIME_RANGE_DATA_MODES.LAST_VALUE, uiRestrictions),
},
]; ];
const defaults = { const defaults = {
@ -141,6 +141,7 @@ export const IndexPattern = ({
})} })}
> >
<EuiComboBox <EuiComboBox
data-test-subj="dataTimeRangeMode"
isClearable={false} isClearable={false}
placeholder={i18n.translate( placeholder={i18n.translate(
'visTypeTimeseries.indexPattern.timeRange.selectTimeRange', 'visTypeTimeseries.indexPattern.timeRange.selectTimeRange',

View file

@ -321,6 +321,7 @@ export class GaugePanelConfig extends Component<
<EuiTab <EuiTab
isSelected={selectedTab === PANEL_CONFIG_TABS.DATA} isSelected={selectedTab === PANEL_CONFIG_TABS.DATA}
onClick={() => this.switchTab(PANEL_CONFIG_TABS.DATA)} onClick={() => this.switchTab(PANEL_CONFIG_TABS.DATA)}
data-test-subj="gaugeEditorDataBtn"
> >
<FormattedMessage <FormattedMessage
id="visTypeTimeseries.gauge.dataTab.dataButtonLabel" id="visTypeTimeseries.gauge.dataTab.dataButtonLabel"
@ -330,6 +331,7 @@ export class GaugePanelConfig extends Component<
<EuiTab <EuiTab
isSelected={selectedTab === PANEL_CONFIG_TABS.OPTIONS} isSelected={selectedTab === PANEL_CONFIG_TABS.OPTIONS}
onClick={() => this.switchTab(PANEL_CONFIG_TABS.OPTIONS)} onClick={() => this.switchTab(PANEL_CONFIG_TABS.OPTIONS)}
data-test-subj="gaugeEditorPanelOptionsBtn"
> >
<FormattedMessage <FormattedMessage
id="visTypeTimeseries.gauge.optionsTab.panelOptionsButtonLabel" id="visTypeTimeseries.gauge.optionsTab.panelOptionsButtonLabel"

View file

@ -165,6 +165,7 @@ export class MetricPanelConfig extends Component<
<EuiTab <EuiTab
isSelected={selectedTab === PANEL_CONFIG_TABS.DATA} isSelected={selectedTab === PANEL_CONFIG_TABS.DATA}
onClick={() => this.switchTab(PANEL_CONFIG_TABS.DATA)} onClick={() => this.switchTab(PANEL_CONFIG_TABS.DATA)}
data-test-subj="metricEditorDataBtn"
> >
<FormattedMessage <FormattedMessage
id="visTypeTimeseries.metric.dataTab.dataButtonLabel" id="visTypeTimeseries.metric.dataTab.dataButtonLabel"

View file

@ -273,6 +273,7 @@ export class TablePanelConfig extends Component<
<EuiTab <EuiTab
isSelected={selectedTab === PANEL_CONFIG_TABS.DATA} isSelected={selectedTab === PANEL_CONFIG_TABS.DATA}
onClick={() => this.switchTab(PANEL_CONFIG_TABS.DATA)} onClick={() => this.switchTab(PANEL_CONFIG_TABS.DATA)}
data-test-subj="tableEditorDataBtn"
> >
<FormattedMessage <FormattedMessage
id="visTypeTimeseries.table.dataTab.columnsButtonLabel" id="visTypeTimeseries.table.dataTab.columnsButtonLabel"
@ -282,6 +283,7 @@ export class TablePanelConfig extends Component<
<EuiTab <EuiTab
isSelected={selectedTab === PANEL_CONFIG_TABS.OPTIONS} isSelected={selectedTab === PANEL_CONFIG_TABS.OPTIONS}
onClick={() => this.switchTab(PANEL_CONFIG_TABS.OPTIONS)} onClick={() => this.switchTab(PANEL_CONFIG_TABS.OPTIONS)}
data-test-subj="tableEditorPanelOptionsBtn"
> >
<FormattedMessage <FormattedMessage
id="visTypeTimeseries.table.optionsTab.panelOptionsButtonLabel" id="visTypeTimeseries.table.optionsTab.panelOptionsButtonLabel"

View file

@ -225,6 +225,7 @@ export class TopNPanelConfig extends Component<
<EuiTab <EuiTab
isSelected={selectedTab === PANEL_CONFIG_TABS.DATA} isSelected={selectedTab === PANEL_CONFIG_TABS.DATA}
onClick={() => this.switchTab(PANEL_CONFIG_TABS.DATA)} onClick={() => this.switchTab(PANEL_CONFIG_TABS.DATA)}
data-test-subj="topNEditorDataBtn"
> >
<FormattedMessage <FormattedMessage
id="visTypeTimeseries.topN.dataTab.dataButtonLabel" id="visTypeTimeseries.topN.dataTab.dataButtonLabel"
@ -234,6 +235,7 @@ export class TopNPanelConfig extends Component<
<EuiTab <EuiTab
isSelected={selectedTab === PANEL_CONFIG_TABS.OPTIONS} isSelected={selectedTab === PANEL_CONFIG_TABS.OPTIONS}
onClick={() => this.switchTab(PANEL_CONFIG_TABS.OPTIONS)} onClick={() => this.switchTab(PANEL_CONFIG_TABS.OPTIONS)}
data-test-subj="topNEditorPanelOptionsBtn"
> >
<FormattedMessage <FormattedMessage
id="visTypeTimeseries.topN.optionsTab.panelOptionsButtonLabel" id="visTypeTimeseries.topN.optionsTab.panelOptionsButtonLabel"

View file

@ -27,6 +27,7 @@ import { Storage } from '../../../../../plugins/kibana_utils/public';
import { VisEditorVisualization } from './vis_editor_visualization'; import { VisEditorVisualization } from './vis_editor_visualization';
import { PanelConfig } from './panel_config'; import { PanelConfig } from './panel_config';
import { extractIndexPatternValues } from '../../../common/index_patterns_utils'; import { extractIndexPatternValues } from '../../../common/index_patterns_utils';
import { TIME_RANGE_DATA_MODES, TIME_RANGE_MODE_KEY } from '../../../common/timerange_data_modes';
import { VisPicker } from './vis_picker'; import { VisPicker } from './vis_picker';
import { fetchFields, VisFields } from '../lib/fetch_fields'; import { fetchFields, VisFields } from '../lib/fetch_fields';
import { getDataStart, getCoreStart } from '../../services'; import { getDataStart, getCoreStart } from '../../services';
@ -64,7 +65,17 @@ export class VisEditor extends Component<TimeseriesEditorProps, TimeseriesEditor
this.state = { this.state = {
autoApply: true, autoApply: true,
dirty: false, dirty: false,
model: this.props.vis.params, model: {
// we should set default value for 'time_range_mode' in model so that when user save visualization
// we set right mode in savedObject
// ternary operator needed because old visualization have 'time_range_mode' as undefined for 'last_value'
// but for creating new visaulization we should use 'entire_timerange' as default.
[TIME_RANGE_MODE_KEY]:
this.props.vis.title && this.props.vis.params.type !== 'timeseries'
? TIME_RANGE_DATA_MODES.LAST_VALUE
: TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE,
...this.props.vis.params,
},
extractedIndexPatterns: [''], extractedIndexPatterns: [''],
}; };

View file

@ -37,6 +37,8 @@ import {
} from './lib/search_strategies'; } from './lib/search_strategies';
import { TimeseriesVisData, VisPayload } from '../common/types'; import { TimeseriesVisData, VisPayload } from '../common/types';
import { registerTimeseriesUsageCollector } from './usage_collector';
export interface LegacySetup { export interface LegacySetup {
server: Server; server: Server;
} }
@ -117,6 +119,10 @@ export class VisTypeTimeseriesPlugin implements Plugin<VisTypeTimeseriesSetup> {
visDataRoutes(router, framework); visDataRoutes(router, framework);
fieldsRoutes(router, framework); fieldsRoutes(router, framework);
if (plugins.usageCollection) {
registerTimeseriesUsageCollector(plugins.usageCollection, globalConfig$);
}
return { return {
getVisData: async ( getVisData: async (
requestContext: VisTypeTimeseriesRequestHandlerContext, requestContext: VisTypeTimeseriesRequestHandlerContext,

View file

@ -6,6 +6,8 @@
* Side Public License, v 1. * Side Public License, v 1.
*/ */
import { Observable } from 'rxjs';
import { SharedGlobalConfig } from 'kibana/server';
import type { IRouter, IUiSettingsClient, KibanaRequest } from 'src/core/server'; import type { IRouter, IUiSettingsClient, KibanaRequest } from 'src/core/server';
import type { import type {
DataRequestHandlerContext, DataRequestHandlerContext,
@ -16,6 +18,8 @@ import type { VisPayload } from '../common/types';
import type { SearchStrategyRegistry } from './lib/search_strategies'; import type { SearchStrategyRegistry } from './lib/search_strategies';
import type { CachedIndexPatternFetcher } from './lib/search_strategies/lib/cached_index_pattern_fetcher'; import type { CachedIndexPatternFetcher } from './lib/search_strategies/lib/cached_index_pattern_fetcher';
export type ConfigObservable = Observable<SharedGlobalConfig>;
export type VisTypeTimeseriesRequestHandlerContext = DataRequestHandlerContext; export type VisTypeTimeseriesRequestHandlerContext = DataRequestHandlerContext;
export type VisTypeTimeseriesRouter = IRouter<VisTypeTimeseriesRequestHandlerContext>; export type VisTypeTimeseriesRouter = IRouter<VisTypeTimeseriesRequestHandlerContext>;
export type VisTypeTimeseriesVisDataRequest = KibanaRequest<{}, {}, VisPayload>; export type VisTypeTimeseriesVisDataRequest = KibanaRequest<{}, {}, VisPayload>;

View file

@ -0,0 +1,14 @@
/*
* 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 const mockStats = { somestat: 1 };
export const mockGetStats = jest.fn().mockResolvedValue(mockStats);
jest.doMock('./get_usage_collector', () => ({
getStats: mockGetStats,
}));

View file

@ -0,0 +1,168 @@
/*
* 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 { getStats } from './get_usage_collector';
import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks';
import { TIME_RANGE_DATA_MODES } from '../../common/timerange_data_modes';
const mockedSavedObjects = [
{
_id: 'visualization:timeseries-123',
_source: {
type: 'visualization',
visualization: {
visState: JSON.stringify({
type: 'metrics',
title: 'TSVB visualization 1',
params: {
time_range_mode: TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE,
},
}),
},
},
},
{
_id: 'visualization:timeseries-321',
_source: {
type: 'visualization',
visualization: {
visState: JSON.stringify({
type: 'metrics',
title: 'TSVB visualization 2',
params: {
time_range_mode: TIME_RANGE_DATA_MODES.LAST_VALUE,
},
}),
},
},
},
{
_id: 'visualization:timeseries-456',
_source: {
type: 'visualization',
visualization: {
visState: JSON.stringify({
type: 'metrics',
title: 'TSVB visualization 3',
params: {
time_range_mode: undefined,
},
}),
},
},
},
];
const mockedSavedObjectsByValue = [
{
attributes: {
panelsJSON: JSON.stringify({
type: 'visualization',
embeddableConfig: {
savedVis: {
type: 'metrics',
params: {
time_range_mode: TIME_RANGE_DATA_MODES.LAST_VALUE,
},
},
},
}),
},
},
{
attributes: {
panelsJSON: JSON.stringify({
type: 'visualization',
embeddableConfig: {
savedVis: {
type: 'metrics',
params: {
time_range_mode: TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE,
},
},
},
}),
},
},
];
const getMockCollectorFetchContext = (hits?: unknown[], savedObjectsByValue: unknown[] = []) => {
const fetchParamsMock = createCollectorFetchContextMock();
fetchParamsMock.esClient.search = jest.fn().mockResolvedValue({ body: { hits: { hits } } });
fetchParamsMock.soClient.find = jest.fn().mockResolvedValue({
saved_objects: savedObjectsByValue,
});
return fetchParamsMock;
};
describe('Timeseries visualization usage collector', () => {
const mockIndex = 'mock_index';
test('Returns undefined when no results found (undefined)', async () => {
const mockCollectorFetchContext = getMockCollectorFetchContext([], []);
const result = await getStats(
mockCollectorFetchContext.esClient,
mockCollectorFetchContext.soClient,
mockIndex
);
expect(result).toBeUndefined();
});
test('Returns undefined when no timeseries saved objects found', async () => {
const mockCollectorFetchContext = getMockCollectorFetchContext(
[
{
_id: 'visualization:myvis-123',
_source: {
type: 'visualization',
visualization: { visState: '{"type": "area"}' },
},
},
],
[
{
attributes: {
panelsJSON: JSON.stringify({
type: 'visualization',
embeddableConfig: {
savedVis: {
type: 'area',
},
},
}),
},
},
]
);
const result = await getStats(
mockCollectorFetchContext.esClient,
mockCollectorFetchContext.soClient,
mockIndex
);
expect(result).toBeUndefined();
});
test('Summarizes visualizations response data', async () => {
const mockCollectorFetchContext = getMockCollectorFetchContext(
mockedSavedObjects,
mockedSavedObjectsByValue
);
const result = await getStats(
mockCollectorFetchContext.esClient,
mockCollectorFetchContext.soClient,
mockIndex
);
expect(result).toMatchObject({
timeseries_use_last_value_mode_total: 3,
});
});
});

View file

@ -0,0 +1,86 @@
/*
* 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 { ElasticsearchClient } from 'src/core/server';
import { SavedObjectsClientContract, ISavedObjectsRepository } from 'kibana/server';
import { TIME_RANGE_DATA_MODES } from '../../common/timerange_data_modes';
import { findByValueEmbeddables } from '../../../dashboard/server';
export interface TimeseriesUsage {
timeseries_use_last_value_mode_total: number;
}
interface VisState {
type?: string;
params?: any;
}
export const getStats = async (
esClient: ElasticsearchClient,
soClient: SavedObjectsClientContract | ISavedObjectsRepository,
index: string
): Promise<TimeseriesUsage | undefined> => {
const timeseriesUsage = {
timeseries_use_last_value_mode_total: 0,
};
const searchParams = {
size: 10000,
index,
ignoreUnavailable: true,
filterPath: ['hits.hits._id', 'hits.hits._source.visualization'],
body: {
query: {
bool: {
filter: { term: { type: 'visualization' } },
},
},
},
};
const { body: esResponse } = await esClient.search<{
visualization: { visState: string };
updated_at: string;
}>(searchParams);
function telemetryUseLastValueMode(visState: VisState) {
if (
visState.type === 'metrics' &&
visState.params.type !== 'timeseries' &&
(!visState.params.time_range_mode ||
visState.params.time_range_mode === TIME_RANGE_DATA_MODES.LAST_VALUE)
) {
timeseriesUsage.timeseries_use_last_value_mode_total++;
}
}
if (esResponse?.hits?.hits?.length) {
for (const hit of esResponse.hits.hits) {
if (hit._source && 'visualization' in hit._source) {
const { visualization } = hit._source!;
let visState: VisState = {};
try {
visState = JSON.parse(visualization?.visState ?? '{}');
} catch (e) {
// invalid visState
}
telemetryUseLastValueMode(visState);
}
}
}
const byValueVisualizations = await findByValueEmbeddables(soClient, 'visualization');
for (const item of byValueVisualizations) {
telemetryUseLastValueMode(item.savedVis as VisState);
}
return timeseriesUsage.timeseries_use_last_value_mode_total ? timeseriesUsage : undefined;
};

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export { registerTimeseriesUsageCollector } from './register_timeseries_collector';

View file

@ -0,0 +1,61 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { of } from 'rxjs';
import { mockStats, mockGetStats } from './get_usage_collector.mock';
import { createUsageCollectionSetupMock } from 'src/plugins/usage_collection/server/usage_collection.mock';
import { createCollectorFetchContextMock } from 'src/plugins/usage_collection/server/mocks';
import { registerTimeseriesUsageCollector } from './register_timeseries_collector';
import { ConfigObservable } from '../types';
describe('registerTimeseriesUsageCollector', () => {
const mockIndex = 'mock_index';
const mockConfig = of({ kibana: { index: mockIndex } }) as ConfigObservable;
it('makes a usage collector and registers it`', () => {
const mockCollectorSet = createUsageCollectionSetupMock();
registerTimeseriesUsageCollector(mockCollectorSet, mockConfig);
expect(mockCollectorSet.makeUsageCollector).toBeCalledTimes(1);
expect(mockCollectorSet.registerCollector).toBeCalledTimes(1);
});
it('makeUsageCollector configs fit the shape', () => {
const mockCollectorSet = createUsageCollectionSetupMock();
registerTimeseriesUsageCollector(mockCollectorSet, mockConfig);
expect(mockCollectorSet.makeUsageCollector).toHaveBeenCalledWith({
type: 'vis_type_timeseries',
isReady: expect.any(Function),
fetch: expect.any(Function),
schema: expect.any(Object),
});
const usageCollectorConfig = mockCollectorSet.makeUsageCollector.mock.calls[0][0];
expect(usageCollectorConfig.isReady()).toBe(true);
});
it('makeUsageCollector config.isReady returns true', () => {
const mockCollectorSet = createUsageCollectionSetupMock();
registerTimeseriesUsageCollector(mockCollectorSet, mockConfig);
const usageCollectorConfig = mockCollectorSet.makeUsageCollector.mock.calls[0][0];
expect(usageCollectorConfig.isReady()).toBe(true);
});
it('makeUsageCollector config.fetch calls getStats', async () => {
const mockCollectorSet = createUsageCollectionSetupMock();
registerTimeseriesUsageCollector(mockCollectorSet, mockConfig);
const usageCollector = mockCollectorSet.makeUsageCollector.mock.results[0].value;
const mockedCollectorFetchContext = createCollectorFetchContextMock();
const fetchResult = await usageCollector.fetch(mockedCollectorFetchContext);
expect(mockGetStats).toBeCalledTimes(1);
expect(mockGetStats).toBeCalledWith(
mockedCollectorFetchContext.esClient,
mockedCollectorFetchContext.soClient,
mockIndex
);
expect(fetchResult).toBe(mockStats);
});
});

View file

@ -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 { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import { first } from 'rxjs/operators';
import { getStats, TimeseriesUsage } from './get_usage_collector';
import { ConfigObservable } from '../types';
export function registerTimeseriesUsageCollector(
collectorSet: UsageCollectionSetup,
config: ConfigObservable
) {
const collector = collectorSet.makeUsageCollector<TimeseriesUsage | undefined>({
type: 'vis_type_timeseries',
isReady: () => true,
schema: {
timeseries_use_last_value_mode_total: {
type: 'long',
_meta: { description: 'Number of TSVB visualizations using "last value" as a time range' },
},
},
fetch: async ({ esClient, soClient }) => {
const { index } = (await config.pipe(first()).toPromise()).kibana;
return await getStats(esClient, soClient, index);
},
});
collectorSet.registerCollector(collector);
}

View file

@ -43,6 +43,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.visualBuilder.resetPage(); await PageObjects.visualBuilder.resetPage();
await PageObjects.visualBuilder.clickMetric(); await PageObjects.visualBuilder.clickMetric();
await PageObjects.visualBuilder.checkMetricTabIsPresent(); await PageObjects.visualBuilder.checkMetricTabIsPresent();
await PageObjects.visualBuilder.clickPanelOptions('metric');
await PageObjects.visualBuilder.setMetricsDataTimerangeMode('Last value');
await PageObjects.visualBuilder.clickDataTab('metric');
}); });
it('should not have inspector enabled', async () => { it('should not have inspector enabled', async () => {
@ -81,12 +84,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.visualBuilder.checkGaugeTabIsPresent(); await PageObjects.visualBuilder.checkGaugeTabIsPresent();
}); });
it('should "Entire time range" selected as timerange mode for new visualization', async () => {
await PageObjects.visualBuilder.clickPanelOptions('gauge');
await PageObjects.visualBuilder.checkSelectedDataTimerangeMode('Entire time range');
await PageObjects.visualBuilder.clickDataTab('gauge');
});
it('should verify gauge label and count display', async () => { it('should verify gauge label and count display', async () => {
await PageObjects.visChart.waitForVisualizationRenderingStabilized(); await PageObjects.visChart.waitForVisualizationRenderingStabilized();
const labelString = await PageObjects.visualBuilder.getGaugeLabel(); const labelString = await PageObjects.visualBuilder.getGaugeLabel();
expect(labelString).to.be('Count'); expect(labelString).to.be('Count');
const gaugeCount = await PageObjects.visualBuilder.getGaugeCount(); const gaugeCount = await PageObjects.visualBuilder.getGaugeCount();
expect(gaugeCount).to.be('156'); expect(gaugeCount).to.be('13,830');
}); });
}); });
@ -95,6 +104,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.visualBuilder.resetPage(); await PageObjects.visualBuilder.resetPage();
await PageObjects.visualBuilder.clickTopN(); await PageObjects.visualBuilder.clickTopN();
await PageObjects.visualBuilder.checkTopNTabIsPresent(); await PageObjects.visualBuilder.checkTopNTabIsPresent();
await PageObjects.visualBuilder.clickPanelOptions('topN');
await PageObjects.visualBuilder.setMetricsDataTimerangeMode('Last value');
await PageObjects.visualBuilder.clickDataTab('topN');
}); });
it('should verify topN label and count display', async () => { it('should verify topN label and count display', async () => {
@ -115,6 +127,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.visualBuilder.resetPage(); await PageObjects.visualBuilder.resetPage();
await PageObjects.visualBuilder.clickMetric(); await PageObjects.visualBuilder.clickMetric();
await PageObjects.visualBuilder.checkMetricTabIsPresent(); await PageObjects.visualBuilder.checkMetricTabIsPresent();
await PageObjects.visualBuilder.clickPanelOptions('metric');
await PageObjects.visualBuilder.setMetricsDataTimerangeMode('Last value');
await PageObjects.visualBuilder.clickDataTab('metric');
await PageObjects.timePicker.setAbsoluteRange( await PageObjects.timePicker.setAbsoluteRange(
'Sep 22, 2019 @ 00:00:00.000', 'Sep 22, 2019 @ 00:00:00.000',
'Sep 23, 2019 @ 00:00:00.000' 'Sep 23, 2019 @ 00:00:00.000'

View file

@ -37,6 +37,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
'Sep 22, 2015 @ 06:00:00.000', 'Sep 22, 2015 @ 06:00:00.000',
'Sep 22, 2015 @ 11:00:00.000' 'Sep 22, 2015 @ 11:00:00.000'
); );
await visualBuilder.markdownSwitchSubTab('options');
await visualBuilder.setMetricsDataTimerangeMode('Last value');
await visualBuilder.markdownSwitchSubTab('markdown');
}); });
it('should render subtabs and table variables markdown components', async () => { it('should render subtabs and table variables markdown components', async () => {

View file

@ -24,6 +24,9 @@ export default function ({ getPageObjects }: FtrProviderContext) {
await visualBuilder.clickTable(); await visualBuilder.clickTable();
await visualBuilder.checkTableTabIsPresent(); await visualBuilder.checkTableTabIsPresent();
await visualBuilder.clickPanelOptions('table');
await visualBuilder.setMetricsDataTimerangeMode('Last value');
await visualBuilder.clickDataTab('table');
await visualBuilder.selectGroupByField('machine.os.raw'); await visualBuilder.selectGroupByField('machine.os.raw');
await visualBuilder.setColumnLabelValue('OS'); await visualBuilder.setColumnLabelValue('OS');
await visChart.waitForVisualizationRenderingStabilized(); await visChart.waitForVisualizationRenderingStabilized();

View file

@ -431,6 +431,11 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro
await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.header.waitUntilLoadingHasFinished();
} }
public async clickDataTab(tabName: string) {
await testSubjects.click(`${tabName}EditorDataBtn`);
await PageObjects.header.waitUntilLoadingHasFinished();
}
public async switchIndexPatternSelectionMode(useKibanaIndices: boolean) { public async switchIndexPatternSelectionMode(useKibanaIndices: boolean) {
await testSubjects.click('switchIndexPatternSelectionModePopover'); await testSubjects.click('switchIndexPatternSelectionModePopover');
await testSubjects.setEuiSwitch( await testSubjects.setEuiSwitch(
@ -638,6 +643,16 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro
); );
return await comboBox.isOptionSelected(groupBy, value); return await comboBox.isOptionSelected(groupBy, value);
} }
public async setMetricsDataTimerangeMode(value: string) {
const dataTimeRangeMode = await testSubjects.find('dataTimeRangeMode');
return await comboBox.setElement(dataTimeRangeMode, value);
}
public async checkSelectedDataTimerangeMode(value: string) {
const dataTimeRangeMode = await testSubjects.find('dataTimeRangeMode');
return await comboBox.isOptionSelected(dataTimeRangeMode, value);
}
} }
return new VisualBuilderPage(); return new VisualBuilderPage();

View file

@ -84,6 +84,7 @@ export default function ({ getService, getPageObjects }) {
); );
await PageObjects.visualBuilder.clickPanelOptions('metric'); await PageObjects.visualBuilder.clickPanelOptions('metric');
await PageObjects.visualBuilder.setIndexPatternValue(rollupTargetIndexName, false); await PageObjects.visualBuilder.setIndexPatternValue(rollupTargetIndexName, false);
await PageObjects.visualBuilder.setMetricsDataTimerangeMode('Last value');
await PageObjects.visualBuilder.setIntervalValue('1d'); await PageObjects.visualBuilder.setIntervalValue('1d');
await PageObjects.visualBuilder.setDropLastBucket(false); await PageObjects.visualBuilder.setDropLastBucket(false);
await PageObjects.common.sleep(3000); await PageObjects.common.sleep(3000);