mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[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:
parent
d16101f377
commit
0e40b94348
21 changed files with 459 additions and 9 deletions
|
@ -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": {
|
||||
"properties": {
|
||||
"vega_lib_specs_total": {
|
||||
|
|
|
@ -5,5 +5,6 @@
|
|||
"server": true,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["charts", "data", "expressions", "visualizations", "visualize"],
|
||||
"optionalPlugins": ["usageCollection"],
|
||||
"requiredBundles": ["kibanaUtils", "kibanaReact"]
|
||||
}
|
||||
|
|
|
@ -91,13 +91,6 @@ export const IndexPattern = ({
|
|||
const handleTextChange = createTextHandler(onChange);
|
||||
|
||||
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', {
|
||||
defaultMessage: 'Entire time range',
|
||||
|
@ -105,6 +98,13 @@ export const IndexPattern = ({
|
|||
value: TIME_RANGE_DATA_MODES.ENTIRE_TIME_RANGE,
|
||||
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 = {
|
||||
|
@ -141,6 +141,7 @@ export const IndexPattern = ({
|
|||
})}
|
||||
>
|
||||
<EuiComboBox
|
||||
data-test-subj="dataTimeRangeMode"
|
||||
isClearable={false}
|
||||
placeholder={i18n.translate(
|
||||
'visTypeTimeseries.indexPattern.timeRange.selectTimeRange',
|
||||
|
|
|
@ -321,6 +321,7 @@ export class GaugePanelConfig extends Component<
|
|||
<EuiTab
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.DATA}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.DATA)}
|
||||
data-test-subj="gaugeEditorDataBtn"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.gauge.dataTab.dataButtonLabel"
|
||||
|
@ -330,6 +331,7 @@ export class GaugePanelConfig extends Component<
|
|||
<EuiTab
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.OPTIONS}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.OPTIONS)}
|
||||
data-test-subj="gaugeEditorPanelOptionsBtn"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.gauge.optionsTab.panelOptionsButtonLabel"
|
||||
|
|
|
@ -165,6 +165,7 @@ export class MetricPanelConfig extends Component<
|
|||
<EuiTab
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.DATA}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.DATA)}
|
||||
data-test-subj="metricEditorDataBtn"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.metric.dataTab.dataButtonLabel"
|
||||
|
|
|
@ -273,6 +273,7 @@ export class TablePanelConfig extends Component<
|
|||
<EuiTab
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.DATA}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.DATA)}
|
||||
data-test-subj="tableEditorDataBtn"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.table.dataTab.columnsButtonLabel"
|
||||
|
@ -282,6 +283,7 @@ export class TablePanelConfig extends Component<
|
|||
<EuiTab
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.OPTIONS}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.OPTIONS)}
|
||||
data-test-subj="tableEditorPanelOptionsBtn"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.table.optionsTab.panelOptionsButtonLabel"
|
||||
|
|
|
@ -225,6 +225,7 @@ export class TopNPanelConfig extends Component<
|
|||
<EuiTab
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.DATA}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.DATA)}
|
||||
data-test-subj="topNEditorDataBtn"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.topN.dataTab.dataButtonLabel"
|
||||
|
@ -234,6 +235,7 @@ export class TopNPanelConfig extends Component<
|
|||
<EuiTab
|
||||
isSelected={selectedTab === PANEL_CONFIG_TABS.OPTIONS}
|
||||
onClick={() => this.switchTab(PANEL_CONFIG_TABS.OPTIONS)}
|
||||
data-test-subj="topNEditorPanelOptionsBtn"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="visTypeTimeseries.topN.optionsTab.panelOptionsButtonLabel"
|
||||
|
|
|
@ -27,6 +27,7 @@ import { Storage } from '../../../../../plugins/kibana_utils/public';
|
|||
import { VisEditorVisualization } from './vis_editor_visualization';
|
||||
import { PanelConfig } from './panel_config';
|
||||
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 { fetchFields, VisFields } from '../lib/fetch_fields';
|
||||
import { getDataStart, getCoreStart } from '../../services';
|
||||
|
@ -64,7 +65,17 @@ export class VisEditor extends Component<TimeseriesEditorProps, TimeseriesEditor
|
|||
this.state = {
|
||||
autoApply: true,
|
||||
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: [''],
|
||||
};
|
||||
|
||||
|
|
|
@ -37,6 +37,8 @@ import {
|
|||
} from './lib/search_strategies';
|
||||
import { TimeseriesVisData, VisPayload } from '../common/types';
|
||||
|
||||
import { registerTimeseriesUsageCollector } from './usage_collector';
|
||||
|
||||
export interface LegacySetup {
|
||||
server: Server;
|
||||
}
|
||||
|
@ -117,6 +119,10 @@ export class VisTypeTimeseriesPlugin implements Plugin<VisTypeTimeseriesSetup> {
|
|||
visDataRoutes(router, framework);
|
||||
fieldsRoutes(router, framework);
|
||||
|
||||
if (plugins.usageCollection) {
|
||||
registerTimeseriesUsageCollector(plugins.usageCollection, globalConfig$);
|
||||
}
|
||||
|
||||
return {
|
||||
getVisData: async (
|
||||
requestContext: VisTypeTimeseriesRequestHandlerContext,
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
* 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 {
|
||||
DataRequestHandlerContext,
|
||||
|
@ -16,6 +18,8 @@ import type { VisPayload } from '../common/types';
|
|||
import type { SearchStrategyRegistry } from './lib/search_strategies';
|
||||
import type { CachedIndexPatternFetcher } from './lib/search_strategies/lib/cached_index_pattern_fetcher';
|
||||
|
||||
export type ConfigObservable = Observable<SharedGlobalConfig>;
|
||||
|
||||
export type VisTypeTimeseriesRequestHandlerContext = DataRequestHandlerContext;
|
||||
export type VisTypeTimeseriesRouter = IRouter<VisTypeTimeseriesRequestHandlerContext>;
|
||||
export type VisTypeTimeseriesVisDataRequest = KibanaRequest<{}, {}, VisPayload>;
|
||||
|
|
|
@ -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,
|
||||
}));
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
};
|
|
@ -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';
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
|
@ -43,6 +43,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
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.clickDataTab('metric');
|
||||
});
|
||||
|
||||
it('should not have inspector enabled', async () => {
|
||||
|
@ -81,12 +84,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
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 () => {
|
||||
await PageObjects.visChart.waitForVisualizationRenderingStabilized();
|
||||
const labelString = await PageObjects.visualBuilder.getGaugeLabel();
|
||||
expect(labelString).to.be('Count');
|
||||
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.clickTopN();
|
||||
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 () => {
|
||||
|
@ -115,6 +127,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
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.clickDataTab('metric');
|
||||
await PageObjects.timePicker.setAbsoluteRange(
|
||||
'Sep 22, 2019 @ 00:00:00.000',
|
||||
'Sep 23, 2019 @ 00:00:00.000'
|
||||
|
|
|
@ -37,6 +37,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
'Sep 22, 2015 @ 06: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 () => {
|
||||
|
|
|
@ -24,6 +24,9 @@ export default function ({ getPageObjects }: FtrProviderContext) {
|
|||
await visualBuilder.clickTable();
|
||||
|
||||
await visualBuilder.checkTableTabIsPresent();
|
||||
await visualBuilder.clickPanelOptions('table');
|
||||
await visualBuilder.setMetricsDataTimerangeMode('Last value');
|
||||
await visualBuilder.clickDataTab('table');
|
||||
await visualBuilder.selectGroupByField('machine.os.raw');
|
||||
await visualBuilder.setColumnLabelValue('OS');
|
||||
await visChart.waitForVisualizationRenderingStabilized();
|
||||
|
|
|
@ -431,6 +431,11 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro
|
|||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
}
|
||||
|
||||
public async clickDataTab(tabName: string) {
|
||||
await testSubjects.click(`${tabName}EditorDataBtn`);
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
}
|
||||
|
||||
public async switchIndexPatternSelectionMode(useKibanaIndices: boolean) {
|
||||
await testSubjects.click('switchIndexPatternSelectionModePopover');
|
||||
await testSubjects.setEuiSwitch(
|
||||
|
@ -638,6 +643,16 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro
|
|||
);
|
||||
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();
|
||||
|
|
|
@ -84,6 +84,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
);
|
||||
await PageObjects.visualBuilder.clickPanelOptions('metric');
|
||||
await PageObjects.visualBuilder.setIndexPatternValue(rollupTargetIndexName, false);
|
||||
await PageObjects.visualBuilder.setMetricsDataTimerangeMode('Last value');
|
||||
await PageObjects.visualBuilder.setIntervalValue('1d');
|
||||
await PageObjects.visualBuilder.setDropLastBucket(false);
|
||||
await PageObjects.common.sleep(3000);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue