Replace Discover chart with elastic-charts (#43788) (#46115)

* use elastic-charts for histogram
* add class accessibility
* specify onElementClick type annotation
* set chartElement tooltip type to Follow
* use moment methods for now annotation logic
* move historam inside directive folder
* remove unused timechart directive
* remove dependency from tsvb brush handler
* remove non-required class to fix tooltip overflow
* change the cursor/crosshair
* fix(ie11): add fixed width for header text
* fix: annotation colors on dark theme
* unpdate click and brush ui functional tests
* move functional tests to percy
This commit is contained in:
Marco Vettorello 2019-09-19 14:46:24 +02:00 committed by GitHub
parent 4c2eee1ecf
commit 6283651dc4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 501 additions and 353 deletions

View file

@ -54,7 +54,6 @@ import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query';
import { subscribeWithScope } from 'ui/utils/subscribe_with_scope';
import { getFilterGenerator } from 'ui/filter_manager';
import { SavedObjectsClientProvider } from 'ui/saved_objects';
import { VisualizeLoaderProvider } from 'ui/visualize/loader/visualize_loader';
import { recentlyAccessed } from 'ui/persisted_log';
import { getDocLink } from 'ui/documentation_links';
import '../components/fetch_error';
@ -195,8 +194,6 @@ function discoverController(
localStorage,
uiCapabilities
) {
const visualizeLoader = Private(VisualizeLoaderProvider);
let visualizeHandler;
const Vis = Private(VisProvider);
const responseHandler = vislibSeriesResponseHandlerProvider().handler;
const getUnhashableStates = Private(getUnhashableStatesProvider);
@ -213,6 +210,13 @@ function discoverController(
timefilter.disableTimeRangeSelector();
timefilter.disableAutoRefreshSelector();
$scope.timefilterUpdateHandler = (ranges) => {
timefilter.setTime({
from: moment(ranges.from).toISOString(),
to: moment(ranges.to).toISOString(),
mode: 'absolute',
});
};
$scope.getDocLink = getDocLink;
$scope.intervalOptions = intervalOptions;
@ -793,15 +797,7 @@ function discoverController(
.resolve(buildVislibDimensions($scope.vis, { timeRange: $scope.timeRange, searchSource: $scope.searchSource }))
.then(resp => responseHandler(tabifiedData, resp))
.then(resp => {
visualizeHandler.render({
as: 'visualization',
value: {
visType: $scope.vis.type.name,
visData: resp,
visConfig: $scope.vis.params,
params: {},
}
});
$scope.histogramData = resp;
});
}
@ -1047,13 +1043,6 @@ function discoverController(
$scope.searchSource.setField('aggs', function () {
return $scope.vis.getAggConfig().toDsl();
});
$timeout(async () => {
const visEl = $element.find('#discoverHistogram')[0];
visualizeHandler = await visualizeLoader.embedVisualizationWithSavedObject(visEl, visSavedObject, {
autoFetch: false,
});
});
}
function resolveIndexPatternLoading() {

View file

@ -0,0 +1,4 @@
.dscHistogram__header--partial {
font-weight: $euiFontWeightRegular;
min-width: $euiSize * 12;
}

View file

@ -1 +1,2 @@
@import 'no_results';
@import 'histogram';

View file

@ -0,0 +1,258 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer } from '@elastic/eui';
import moment from 'moment-timezone';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import lightEuiTheme from '@elastic/eui/dist/eui_theme_light.json';
import darkEuiTheme from '@elastic/eui/dist/eui_theme_dark.json';
import {
AnnotationDomainTypes,
Axis,
Chart,
HistogramBarSeries,
GeometryValue,
getAnnotationId,
getAxisId,
getSpecId,
LineAnnotation,
Position,
ScaleType,
Settings,
RectAnnotation,
TooltipValue,
TooltipType,
} from '@elastic/charts';
import { i18n } from '@kbn/i18n';
import { getChartTheme } from 'ui/elastic_charts';
import chrome from 'ui/chrome';
// @ts-ignore: path dynamic for kibana
import { timezoneProvider } from 'ui/vis/lib/timezone';
export interface DiscoverHistogramProps {
chartData: any;
timefilterUpdateHandler: (ranges: { from: number; to: number }) => void;
}
export class DiscoverHistogram extends Component<DiscoverHistogramProps> {
public static propTypes = {
chartData: PropTypes.object,
timefilterUpdateHandler: PropTypes.func,
};
public onBrushEnd = (min: number, max: number) => {
const range = {
from: min,
to: max,
};
this.props.timefilterUpdateHandler(range);
};
public onElementClick = (xInterval: number) => (elementData: GeometryValue[]) => {
const startRange = elementData[0].x;
const range = {
from: startRange,
to: startRange + xInterval,
};
this.props.timefilterUpdateHandler(range);
};
public formatXValue = (val: string) => {
const xAxisFormat = this.props.chartData.xAxisFormat.params.pattern;
return moment(val).format(xAxisFormat);
};
public renderBarTooltip = (xInterval: number, domainStart: number, domainEnd: number) => (
headerData: TooltipValue
): JSX.Element | string => {
const headerDataValue = headerData.value;
const formattedValue = this.formatXValue(headerDataValue);
const partialDataText = i18n.translate('kbn.discover.histogram.partialData.bucketTooltipText', {
defaultMessage:
'The selected time range does not include this entire bucket, it may contain partial data.',
});
if (headerDataValue < domainStart || headerDataValue + xInterval > domainEnd) {
return (
<React.Fragment>
<EuiFlexGroup
alignItems="center"
className="dscHistogram__header--partial"
responsive={false}
gutterSize="xs"
>
<EuiFlexItem grow={false}>
<EuiIcon type="iInCircle" />
</EuiFlexItem>
<EuiFlexItem>{partialDataText}</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="xs" />
<p>{formattedValue}</p>
</React.Fragment>
);
}
return formattedValue;
};
public render() {
const uiSettings = chrome.getUiSettingsClient();
const timeZone = timezoneProvider(uiSettings)();
const { chartData } = this.props;
if (!chartData || !chartData.series[0]) {
return null;
}
const data = chartData.series[0].values;
/**
* Deprecation: [interval] on [date_histogram] is deprecated, use [fixed_interval] or [calendar_interval].
* see https://github.com/elastic/kibana/issues/27410
* TODO: Once the Discover query has been update, we should change the below to use the new field
*/
const xInterval = chartData.ordered.interval;
const xValues = chartData.xAxisOrderedValues;
const lastXValue = xValues[xValues.length - 1];
const domain = chartData.ordered;
const domainStart = domain.min.valueOf();
const domainEnd = domain.max.valueOf();
const domainMin = data[0].x > domainStart ? domainStart : data[0].x;
const domainMax = domainEnd - xInterval > lastXValue ? domainEnd - xInterval : lastXValue;
const xDomain = {
min: domainMin,
max: domainMax,
minInterval: xInterval,
};
// Domain end of 'now' will be milliseconds behind current time, so we extend time by 1 minute and check if
// the annotation is within this range; if so, the line annotation uses the domainEnd as its value
const now = moment();
const isAnnotationAtEdge =
moment(domainEnd)
.add(60000)
.isAfter(now) && now.isAfter(domainEnd);
const lineAnnotationValue = isAnnotationAtEdge ? domainEnd : now;
const lineAnnotationData = [
{
dataValue: lineAnnotationValue,
},
];
const isDarkMode = uiSettings.get('theme:darkMode');
const lineAnnotationStyle = {
line: {
strokeWidth: 2,
stroke: isDarkMode ? darkEuiTheme.euiColorDanger : lightEuiTheme.euiColorDanger,
opacity: 0.7,
},
};
const rectAnnotations = [];
if (domainStart !== domainMin) {
rectAnnotations.push({
coordinates: {
x1: domainStart,
},
});
}
if (domainEnd !== domainMax) {
rectAnnotations.push({
coordinates: {
x0: domainEnd,
},
});
}
const rectAnnotationStyle = {
stroke: isDarkMode ? darkEuiTheme.euiColorLightShade : lightEuiTheme.euiColorDarkShade,
strokeWidth: 0,
opacity: isDarkMode ? 0.6 : 0.2,
fill: isDarkMode ? darkEuiTheme.euiColorLightShade : lightEuiTheme.euiColorDarkShade,
};
const tooltipProps = {
headerFormatter: this.renderBarTooltip(xInterval, domainStart, domainEnd),
type: TooltipType.VerticalCursor,
};
return (
<Chart size="100%">
<Settings
xDomain={xDomain}
onBrushEnd={this.onBrushEnd}
onElementClick={this.onElementClick(xInterval)}
tooltip={tooltipProps}
theme={getChartTheme()}
/>
<Axis
id={getAxisId('discover-histogram-left-axis')}
position={Position.Left}
ticks={5}
title={chartData.yAxisLabel}
/>
<Axis
id={getAxisId('discover-histogram-bottom-axis')}
position={Position.Bottom}
title={chartData.xAxisLabel}
tickFormat={this.formatXValue}
ticks={10}
/>
<LineAnnotation
annotationId={getAnnotationId('line-annotation')}
domainType={AnnotationDomainTypes.XDomain}
dataValues={lineAnnotationData}
hideTooltips={true}
style={lineAnnotationStyle}
/>
<RectAnnotation
dataValues={rectAnnotations}
annotationId={getAnnotationId('rect-annotation')}
zIndex={2}
style={rectAnnotationStyle}
hideTooltips={true}
/>
<HistogramBarSeries
id={getSpecId('discover-histogram')}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
data={data}
timeZone={timeZone}
name={chartData.yAxisLabel}
/>
</Chart>
);
}
}

View file

@ -20,14 +20,10 @@
import 'ngreact';
import { wrapInI18nContext } from 'ui/i18n';
import { uiModules } from 'ui/modules';
import { DiscoverNoResults } from './no_results';
import { DiscoverUninitialized } from './uninitialized';
import { DiscoverUnsupportedIndexPattern } from './unsupported_index_pattern';
import './timechart';
import { DiscoverHistogram } from './histogram';
const app = uiModules.get('apps/discover', ['react']);
@ -42,3 +38,5 @@ app.directive('discoverUninitialized', reactDirective =>
app.directive('discoverUnsupportedIndexPattern', reactDirective =>
reactDirective(wrapInI18nContext(DiscoverUnsupportedIndexPattern), ['unsupportedType'])
);
app.directive('discoverHistogram', reactDirective => reactDirective(DiscoverHistogram));

View file

@ -162,11 +162,13 @@
</header>
<div id="discoverHistogram"
ng-show="vis && rows.length !== 0"
<discover-histogram
style="display: flex; height: 200px"
>
</div>
ng-show="vis && rows.length !== 0"
chart-data="histogramData"
timefilter-update-handler="timefilterUpdateHandler"
watch-depth="reference"
></discover-histogram>
</section>
<section

View file

@ -17,35 +17,10 @@
* under the License.
*/
import VislibProvider from 'ui/vislib';
import { uiModules } from 'ui/modules';
uiModules
.get('apps/discover')
.directive('discoverTimechart', function (Private) {
const vislib = Private(VislibProvider);
import chrome from 'ui/chrome';
import { Theme, LIGHT_THEME, DARK_THEME } from '@elastic/charts';
return {
restrict: 'E',
scope: {
data: '='
},
link: function ($scope, elem) {
const init = function () {
// This elem should already have a height/width
const myChart = new vislib.Chart(elem[0], {
addLegend: false
});
$scope.$watch('data', function (data) {
if (data != null) {
myChart.render(data);
}
});
};
// Start the directive
init();
}
};
});
export function getChartTheme(): Theme {
const isDarkMode = chrome.getUiSettingsClient().get('theme:darkMode');
return isDarkMode ? DARK_THEME : LIGHT_THEME;
}

View file

@ -26,7 +26,7 @@ export default function ({ getService, getPageObjects }) {
const browser = getService('browser');
const kibanaServer = getService('kibanaServer');
const filterBar = getService('filterBar');
const PageObjects = getPageObjects(['common', 'discover', 'header', 'visualize', 'timePicker']);
const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']);
const defaultSettings = {
defaultIndex: 'logstash-*',
};
@ -84,83 +84,30 @@ export default function ({ getService, getPageObjects }) {
});
});
it('should show the correct bar chart', async function () {
const expectedBarChartData = [
35,
189,
694,
1347,
1285,
704,
176,
29,
39,
189,
640,
1276,
1327,
663,
166,
25,
30,
164,
663,
1320,
1270,
681,
188,
27,
];
await verifyChartData(expectedBarChartData);
});
it('should show correct time range string in chart', async function () {
const actualTimeString = await PageObjects.discover.getChartTimespan();
const expectedTimeString = `Sep 19, 2015 @ 06:31:44.000 - Sep 23, 2015 @ 18:31:44.000`;
expect(actualTimeString).to.be(expectedTimeString);
});
it('should show bars in the correct time zone', async function () {
const maxTicks = [
'2015-09-20 00:00',
'2015-09-20 12:00',
'2015-09-21 00:00',
'2015-09-21 12:00',
'2015-09-22 00:00',
'2015-09-22 12:00',
'2015-09-23 00:00',
'2015-09-23 12:00'
];
for (const tick of await PageObjects.discover.getBarChartXTicks()) {
if (!maxTicks.includes(tick)) {
throw new Error(`unexpected x-axis tick "${tick}"`);
}
}
});
it('should modify the time range when a bar is clicked', async function () {
await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
await PageObjects.visualize.waitForVisualization();
await PageObjects.discover.clickHistogramBar(0);
await PageObjects.visualize.waitForVisualization();
await PageObjects.discover.clickHistogramBar();
const time = await PageObjects.timePicker.getTimeConfig();
expect(time.start).to.be('Sep 20, 2015 @ 00:00:00.000');
expect(time.end).to.be('Sep 20, 2015 @ 03:00:00.000');
const rowData = await PageObjects.discover.getDocTableIndex(1);
expect(rowData).to.have.string('Sep 20, 2015 @ 02:57:03.761');
expect(time.start).to.be('Sep 21, 2015 @ 09:00:00.000');
expect(time.end).to.be('Sep 21, 2015 @ 12:00:00.000');
const rowData = await PageObjects.discover.getDocTableField(1);
expect(rowData).to.have.string('Sep 21, 2015 @ 11:59:22.316');
});
it('should modify the time range when the histogram is brushed', async function () {
await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
await PageObjects.visualize.waitForVisualization();
await PageObjects.discover.brushHistogram(0, 1);
await PageObjects.visualize.waitForVisualization();
await PageObjects.discover.brushHistogram();
const newDurationHours = await PageObjects.timePicker.getTimeDurationInHours();
expect(Math.round(newDurationHours)).to.be(3);
expect(Math.round(newDurationHours)).to.be(108);
const rowData = await PageObjects.discover.getDocTableField(1);
expect(rowData).to.have.string('Sep 20, 2015 @ 02:56:02.323');
expect(rowData).to.have.string('Sep 22, 2015 @ 23:50:13.253');
});
it('should show correct initial chart interval of Auto', async function () {
@ -172,167 +119,6 @@ export default function ({ getService, getPageObjects }) {
expect(actualInterval).to.be(expectedInterval);
});
it('should show correct data for chart interval Hourly', async function () {
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await PageObjects.discover.setChartInterval('Hourly');
const expectedBarChartData = [
4,
7,
16,
23,
38,
87,
132,
159,
248,
320,
349,
376,
380,
324,
293,
233,
188,
125,
69,
40,
28,
17,
2,
3,
8,
10,
12,
28,
36,
84,
111,
157,
229,
292,
324,
373,
378,
345,
306,
223,
167,
124,
72,
35,
22,
11,
7,
1,
6,
5,
12,
25,
27,
76,
111,
175,
228,
294,
358,
372,
366,
344,
276,
213,
201,
113,
72,
39,
36,
12,
7,
3,
];
await verifyChartData(expectedBarChartData);
});
it('should show correct data for chart interval Daily', async function () {
const chartInterval = 'Daily';
const expectedBarChartData = [4757, 4614, 4633];
await PageObjects.discover.setChartInterval(chartInterval);
await retry.try(async () => {
await verifyChartData(expectedBarChartData);
});
});
it('should show correct data for chart interval Weekly', async function () {
const chartInterval = 'Weekly';
const expectedBarChartData = [4757, 9247];
await PageObjects.discover.setChartInterval(chartInterval);
await retry.try(async () => {
await verifyChartData(expectedBarChartData);
});
});
it('browser back button should show previous interval Daily', async function () {
const expectedChartInterval = 'Daily';
const expectedBarChartData = [4757, 4614, 4633];
await browser.goBack();
await retry.try(async function tryingForTime() {
const actualInterval = await PageObjects.discover.getChartInterval();
expect(actualInterval).to.be(expectedChartInterval);
});
await verifyChartData(expectedBarChartData);
});
it('should show correct data for chart interval Monthly', async function () {
const chartInterval = 'Monthly';
const expectedBarChartData = [13129];
await PageObjects.discover.setChartInterval(chartInterval);
await verifyChartData(expectedBarChartData);
});
it('should show correct data for chart interval Yearly', async function () {
const chartInterval = 'Yearly';
const expectedBarChartData = [13129];
await PageObjects.discover.setChartInterval(chartInterval);
await verifyChartData(expectedBarChartData);
});
it('should show correct data for chart interval Auto', async function () {
const chartInterval = 'Auto';
const expectedBarChartData = [
35,
189,
694,
1347,
1285,
704,
176,
29,
39,
189,
640,
1276,
1327,
663,
166,
25,
30,
164,
663,
1320,
1270,
681,
188,
27,
];
await PageObjects.discover.setChartInterval(chartInterval);
await verifyChartData(expectedBarChartData);
});
it('should show Auto chart interval', async function () {
const expectedChartInterval = 'Auto';
@ -344,42 +130,6 @@ export default function ({ getService, getPageObjects }) {
const isVisible = await PageObjects.discover.hasNoResults();
expect(isVisible).to.be(false);
});
async function verifyChartData(expectedBarChartData) {
await retry.try(async function tryingForTime() {
const paths = await PageObjects.discover.getBarChartData();
// the largest bars are over 100 pixels high so this is less than 1% tolerance
const barHeightTolerance = 1;
let stringResults = '';
let hasFailure = false;
for (let y = 0; y < expectedBarChartData.length; y++) {
stringResults +=
y +
': expected = ' +
expectedBarChartData[y] +
', actual = ' +
paths[y] +
', Pass = ' +
(Math.abs(expectedBarChartData[y] - paths[y]) <
barHeightTolerance) +
'\n';
if (
Math.abs(expectedBarChartData[y] - paths[y]) > barHeightTolerance
) {
hasFailure = true;
}
}
if (hasFailure) {
log.debug(stringResults);
log.debug(paths);
}
for (let x = 0; x < expectedBarChartData.length; x++) {
expect(
Math.abs(expectedBarChartData[x] - paths[x]) < barHeightTolerance
).to.be.ok();
}
});
}
});
describe('query #2, which has an empty time range', () => {
@ -437,7 +187,8 @@ export default function ({ getService, getPageObjects }) {
});
describe('time zone switch', () => {
it('should show bars in the correct time zone after switching', async function () {
// skipping this until we get an elastic-chart alternative to check the ticks value
it.skip('should show ticks in the correct time zone after switching', async function () {
await kibanaServer.uiSettings.replace({ 'dateFormat:tz': 'America/Phoenix' });
await browser.refresh();
await PageObjects.header.awaitKibanaChrome();
@ -462,6 +213,16 @@ export default function ({ getService, getPageObjects }) {
}
});
});
it('should show bars in the correct time zone after switching', async function () {
await kibanaServer.uiSettings.replace({ 'dateFormat:tz': 'America/Phoenix' });
await browser.refresh();
await PageObjects.header.awaitKibanaChrome();
await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
log.debug('check that the newest doc timestamp is now -7 hours from the UTC time in the first test');
const rowData = await PageObjects.discover.getDocTableIndex(1);
expect(rowData.startsWith('Sep 22, 2015 @ 16:50:13.253')).to.be.ok();
});
});
});
}

View file

@ -129,13 +129,13 @@ export default function ({ getService, getPageObjects }) {
const toTime = '2015-09-18 18:31:44.000';
await PageObjects.common.navigateToApp('discover');
await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
await PageObjects.visualize.waitForVisualization();
await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName);
await retry.try(async function () {
await PageObjects.discover.clickFieldListItemAdd(scriptedPainlessFieldName);
});
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.waitForVisualization();
await retry.try(async function () {
const rowData = await PageObjects.discover.getDocTableIndex(1);
expect(rowData).to.be('Sep 18, 2015 @ 18:20:57.916\n18');
@ -147,7 +147,7 @@ export default function ({ getService, getPageObjects }) {
await log.debug('filter by the first value (14) in the expanded scripted field list');
await PageObjects.discover.clickFieldListPlusFilter(scriptedPainlessFieldName, '14');
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.waitForVisualization();
await retry.try(async function () {
expect(await PageObjects.discover.getHitCount()).to.be('31');
});
@ -161,7 +161,7 @@ export default function ({ getService, getPageObjects }) {
await filterBar.removeAllFilters();
await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName);
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.waitForVisualization();
await inspector.open();
await inspector.setTablePageSize(50);
await inspector.expectTableData(expectedChartValues);
@ -191,13 +191,13 @@ export default function ({ getService, getPageObjects }) {
const toTime = '2015-09-18 18:31:44.000';
await PageObjects.common.navigateToApp('discover');
await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
await PageObjects.visualize.waitForVisualization();
await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2);
await retry.try(async function () {
await PageObjects.discover.clickFieldListItemAdd(scriptedPainlessFieldName2);
});
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.waitForVisualization();
await retry.try(async function () {
const rowData = await PageObjects.discover.getDocTableIndex(1);
expect(rowData).to.be('Sep 18, 2015 @ 18:20:57.916\ngood');
@ -210,7 +210,7 @@ export default function ({ getService, getPageObjects }) {
await log.debug('filter by "bad" in the expanded scripted field list');
await PageObjects.discover.clickFieldListPlusFilter(scriptedPainlessFieldName2, 'bad');
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.waitForVisualization();
await retry.try(async function () {
expect(await PageObjects.discover.getHitCount()).to.be('27');
});
@ -220,7 +220,6 @@ export default function ({ getService, getPageObjects }) {
it('should visualize scripted field in vertical bar chart', async function () {
await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2);
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.waitForVisualization();
await inspector.open();
await inspector.expectTableData([
['good', '359'],
@ -252,13 +251,13 @@ export default function ({ getService, getPageObjects }) {
const toTime = '2015-09-18 18:31:44.000';
await PageObjects.common.navigateToApp('discover');
await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
await PageObjects.visualize.waitForVisualization();
await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2);
await retry.try(async function () {
await PageObjects.discover.clickFieldListItemAdd(scriptedPainlessFieldName2);
});
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.waitForVisualization();
await retry.try(async function () {
const rowData = await PageObjects.discover.getDocTableIndex(1);
expect(rowData).to.be('Sep 18, 2015 @ 18:20:57.916\ntrue');
@ -271,7 +270,7 @@ export default function ({ getService, getPageObjects }) {
await log.debug('filter by "true" in the expanded scripted field list');
await PageObjects.discover.clickFieldListPlusFilter(scriptedPainlessFieldName2, 'true');
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.waitForVisualization();
await retry.try(async function () {
expect(await PageObjects.discover.getHitCount()).to.be('359');
});
@ -281,7 +280,6 @@ export default function ({ getService, getPageObjects }) {
it('should visualize scripted field in vertical bar chart', async function () {
await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2);
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.waitForVisualization();
await inspector.open();
await inspector.expectTableData([
['true', '359'],
@ -314,13 +312,13 @@ export default function ({ getService, getPageObjects }) {
const toTime = '2015-09-18 07:00:00.000';
await PageObjects.common.navigateToApp('discover');
await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
await PageObjects.visualize.waitForVisualization();
await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2);
await retry.try(async function () {
await PageObjects.discover.clickFieldListItemAdd(scriptedPainlessFieldName2);
});
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.waitForVisualization();
await retry.try(async function () {
const rowData = await PageObjects.discover.getDocTableIndex(1);
expect(rowData).to.be('Sep 18, 2015 @ 06:52:55.953\n2015-09-18 07:00');
@ -332,7 +330,7 @@ export default function ({ getService, getPageObjects }) {
await log.debug('filter by "2015-09-17 23:00" in the expanded scripted field list');
await PageObjects.discover.clickFieldListPlusFilter(scriptedPainlessFieldName2, '2015-09-17 23:00');
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.waitForVisualization();
await retry.try(async function () {
expect(await PageObjects.discover.getHitCount()).to.be('1');
});
@ -342,7 +340,6 @@ export default function ({ getService, getPageObjects }) {
it('should visualize scripted field in vertical bar chart', async function () {
await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2);
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.visualize.waitForVisualization();
await inspector.open();
await inspector.setTablePageSize(50);
await inspector.expectTableData([

View file

@ -116,16 +116,20 @@ export function DiscoverPageProvider({ getService, getPageObjects }) {
await testSubjects.click('discoverOpenButton');
}
async clickHistogramBar(i) {
const bars = await find.allByCssSelector(`.series.histogram rect`);
await bars[i].click();
async clickHistogramBar() {
const el = await find.byCssSelector('.echChart canvas:last-of-type');
await browser.getActions()
.move({ x: 200, y: 20, origin: el._webElement })
.click()
.perform();
}
async brushHistogram(from, to) {
const bars = await find.allByCssSelector('.series.histogram rect');
async brushHistogram() {
const el = await find.byCssSelector('.echChart canvas:last-of-type');
await browser.dragAndDrop(
{ location: bars[from], offset: { x: 0, y: -5 } },
{ location: bars[to], offset: { x: 0, y: -5 } }
{ location: el, offset: { x: 200, y: 20 } },
{ location: el, offset: { x: 400, y: 30 } }
);
}
@ -133,12 +137,6 @@ export function DiscoverPageProvider({ getService, getPageObjects }) {
return await globalNav.getLastBreadcrumb();
}
async getBarChartXTicks() {
const xAxis = await find.byCssSelector('.x.axis.CategoryAxis-1');
const $ = await xAxis.parseDomContent();
return $('.tick > text').toArray().map(tick => $(tick).text().trim());
}
async getBarChartData() {
let yAxisLabel = 0;

View file

@ -26,7 +26,7 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) {
return {
...functionalConfig.getAll(),
testFiles: [require.resolve('./tests/console_app')],
testFiles: [require.resolve('./tests/console_app'), require.resolve('./tests/discover')],
services,

View file

@ -27,15 +27,15 @@ import { FtrProviderContext } from '../../ftr_provider_context';
// @ts-ignore internal js that is passed to the browser as is
import { takePercySnapshot, takePercySnapshotWithAgent } from './take_percy_snapshot';
export const DEFAULT_OPTIONS = {
widths: [1200],
};
export async function VisualTestingProvider({ getService }: FtrProviderContext) {
const browser = getService('browser');
const log = getService('log');
const lifecycle = getService('lifecycle');
const DEFAULT_OPTIONS = {
widths: [1200],
};
let currentTest: Test | undefined;
lifecycle.on('beforeEachTest', (test: Test) => {
currentTest = test;

View file

@ -0,0 +1,123 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from '@kbn/expect';
export default function ({ getService, getPageObjects }) {
const log = getService('log');
const retry = getService('retry');
const esArchiver = getService('esArchiver');
const browser = getService('browser');
const kibanaServer = getService('kibanaServer');
const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']);
const visualTesting = getService('visualTesting');
const defaultSettings = {
defaultIndex: 'logstash-*',
};
describe('discover', function describeIndexTests() {
const fromTime = '2015-09-19 06:31:44.000';
const toTime = '2015-09-23 18:31:44.000';
before(async function () {
log.debug('load kibana index with default index pattern');
await esArchiver.load('discover');
// and load a set of makelogs data
await esArchiver.loadIfNeeded('logstash_functional');
await kibanaServer.uiSettings.replace(defaultSettings);
log.debug('discover');
await PageObjects.common.navigateToApp('discover');
await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
});
describe('query', function () {
this.tags(['skipFirefox']);
it('should show bars in the correct time zone', async function () {
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await visualTesting.snapshot();
});
it('should show correct data for chart interval Hourly', async function () {
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await PageObjects.discover.setChartInterval('Hourly');
await visualTesting.snapshot();
});
it('should show correct data for chart interval Daily', async function () {
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await PageObjects.discover.setChartInterval('Daily');
await retry.try(async () => {
await visualTesting.snapshot();
});
});
it('should show correct data for chart interval Weekly', async function () {
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await PageObjects.discover.setChartInterval('Weekly');
await retry.try(async () => {
await visualTesting.snapshot();
});
});
it('browser back button should show previous interval Daily', async function () {
await browser.goBack();
await retry.try(async function tryingForTime() {
const actualInterval = await PageObjects.discover.getChartInterval();
expect(actualInterval).to.be('Daily');
});
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await visualTesting.snapshot();
});
it('should show correct data for chart interval Monthly', async function () {
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await PageObjects.discover.setChartInterval('Monthly');
await visualTesting.snapshot();
});
it('should show correct data for chart interval Yearly', async function () {
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await PageObjects.discover.setChartInterval('Yearly');
await visualTesting.snapshot();
});
it('should show correct data for chart interval Auto', async function () {
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await PageObjects.discover.setChartInterval('Auto');
await visualTesting.snapshot();
});
});
describe('time zone switch', () => {
it('should show bars in the correct time zone after switching', async function () {
await kibanaServer.uiSettings.replace({ 'dateFormat:tz': 'America/Phoenix' });
await browser.refresh();
await PageObjects.header.awaitKibanaChrome();
await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await retry.try(async function () {
await visualTesting.snapshot();
});
});
});
});
}

View file

@ -0,0 +1,42 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { DEFAULT_OPTIONS } from '../../services/visual_testing/visual_testing';
// Width must be the same as visual_testing or canvas image widths will get skewed
const [SCREEN_WIDTH] = DEFAULT_OPTIONS.widths || [];
export default function ({ getService, loadTestFile }) {
const esArchiver = getService('esArchiver');
const browser = getService('browser');
describe('discover app', function () {
this.tags('ciGroup6');
before(function () {
return browser.setWindowSize(SCREEN_WIDTH, 1000);
});
after(function unloadMakelogs() {
return esArchiver.unload('logstash_functional');
});
loadTestFile(require.resolve('./chart_visualization'));
});
}