[Timelion Viz] Add functional tests (#107287)

* First draft migrate timelion to elastic-charts

* Some refactoring. Added brush event.

* Added title. Some refactoring

* Fixed some type problems. Added logic for yaxes function

* Fixed some types, added missing functionality for yaxes

* Fixed some types, added missing functionality for stack property

* Fixed unit test

* Removed unneeded code

* Some refactoring

* Some refactoring

* Fixed some remarks.

* Fixed some styles

* Added themes. Removed unneeded styles in BarSeries

* removed unneeded code.

* Fixed some comments

* Fixed vertical cursor across Timelion visualizations of a dashboad

* Fix some problems with styles

* Use RxJS instead of jQuery

* Remove unneeded code

* Fixed some problems

* Fixed unit test

* Fix CI

* Fix eslint

* Fix some gaps

* Fix legend columns

* Some fixes

* add 2 versions of Timeline app

* fix CI

* cleanup code

* fix CI

* fix legend position

* fix some cases

* fix some cases

* remove extra casting

* cleanup code

* fix issue with static

* fix header formatter

* fix points

* fix ts error

* Fix yaxis behavior

* Fix some case with yaxis

* Add deprecation message and update asciidoc

* Fix title

* some text improvements

* [Timelion Viz] Add functional tests

* Add more complex cases for _timelion

* Update test expected data

Co-authored-by: Uladzislau Lasitsa <Uladzislau_Lasitsa@epam.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Alexey Antonov <alexwizp@gmail.com>
This commit is contained in:
Diana Derevyankina 2021-08-04 21:56:46 +03:00 committed by GitHub
parent 04f0774977
commit 2605bd81cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 256 additions and 16 deletions

View file

@ -126,6 +126,7 @@ function TimelionInterval({ value, setValue, setValidity }: TimelionIntervalProp
placeholder={i18n.translate('timelion.vis.selectIntervalPlaceholder', {
defaultMessage: 'Select an interval',
})}
data-test-subj="timelionIntervalComboBox"
/>
</EuiFormRow>
);

View file

@ -43,6 +43,15 @@ import type { Series } from '../helpers/timelion_request_handler';
import './timelion_vis.scss';
declare global {
interface Window {
/**
* Flag used to enable debugState on elastic charts
*/
_echDebugStateFlag?: boolean;
}
}
interface TimelionVisComponentProps {
interval: string;
seriesList: Sheet;
@ -174,7 +183,7 @@ const TimelionVisComponent = ({
}, [chart]);
return (
<div className="timelionChart">
<div className="timelionChart" data-test-subj="visTypeXyChart">
{title && (
<EuiTitle className="timelionChart__topTitle" size="xxxs">
<h4>{title}</h4>
@ -182,6 +191,7 @@ const TimelionVisComponent = ({
)}
<Chart ref={chartRef} renderer="canvas" size={{ width: '100%' }}>
<Settings
debugState={window._echDebugStateFlag ?? false}
onBrushEnd={brushEndListener}
showLegend={legend.showLegend}
showLegendExtra={true}

View file

@ -151,7 +151,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
it('should put secondary axis on the right', async function () {
const length = await PageObjects.visChart.getRightValueAxesCount();
const length = await PageObjects.visChart.getAxesCountByPosition('right');
expect(length).to.be(1);
});
});

View file

@ -0,0 +1,206 @@
/*
* 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 expect from '@kbn/expect';
import type { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const { timePicker, visChart, visEditor, visualize } = getPageObjects([
'timePicker',
'visChart',
'visEditor',
'visualize',
]);
const monacoEditor = getService('monacoEditor');
const kibanaServer = getService('kibanaServer');
const elasticChart = getService('elasticChart');
const find = getService('find');
describe('Timelion visualization', () => {
before(async () => {
await kibanaServer.uiSettings.update({
'timelion:legacyChartsLibrary': false,
});
await visualize.initTests(true);
await visualize.navigateToNewAggBasedVisualization();
await visualize.clickTimelion();
await timePicker.setDefaultAbsoluteRange();
});
const initVisualization = async (expression: string, interval: string = '12h') => {
await visEditor.setTimelionInterval(interval);
await monacoEditor.setCodeEditorValue(expression);
await visEditor.clickGo();
};
it('should display correct data for specified index pattern and timefield', async () => {
await initVisualization('.es(index=long-window-logstash-*,timefield=@timestamp)');
const chartData = await visChart.getAreaChartData('q:* > count');
expect(chartData).to.eql([3, 5, 2, 6, 1, 6, 1, 7, 0, 0]);
});
it('should display correct chart colors for multiple expressions', async () => {
const expectedColors = ['#01A4A4', '#FFCFDF', '#BFA7DA', '#AD7DE6'];
await initVisualization(
'.es(*), .es(*).color("#FFCFDF"), .es(*).color("#BFA7DA"), .es(*).color("#AD7DE6")'
);
const areas = (await elasticChart.getChartDebugData())?.areas;
expect(areas?.map(({ color }) => color)).to.eql(expectedColors);
});
it('should display correct chart data for average, min, max and cardinality aggregations', async () => {
await initVisualization(
'.es(index=logstash-*,metric=avg:bytes), .es(index=logstash-*,metric=min:bytes),' +
'.es(index=logstash-*,metric=max:bytes,), .es(index=logstash-*,metric=cardinality:bytes)',
'36h'
);
const firstAreaChartData = await visChart.getAreaChartData('q:* > avg(bytes)');
const secondAreaChartData = await visChart.getAreaChartData('q:* > min(bytes)');
const thirdAreaChartData = await visChart.getAreaChartData('q:* > max(bytes)');
const forthAreaChartData = await visChart.getAreaChartData('q:* > cardinality(bytes)');
expect(firstAreaChartData).to.eql([5732.783676366217, 5721.775973559419]);
expect(secondAreaChartData).to.eql([0, 0]);
expect(thirdAreaChartData).to.eql([19985, 19986]);
expect(forthAreaChartData).to.eql([5019, 4958, 0, 0]);
});
it('should display correct chart data for expressions using functions', async () => {
const firstAreaExpectedChartData = [3, 2421, 2343, 2294, 2327, 2328, 2312, 7, 0, 0];
const thirdAreaExpectedChartData = [200, 167, 199, 200, 200, 198, 108, 200, 200];
const forthAreaExpectedChartData = [150, 50, 50, 50, 50, 50, 50, 150, 150, 150];
await initVisualization(
'.es(*).label("initial"),' +
'.es(*).add(term=.es(*).multiply(-1).abs()).divide(2).label("add multiply abs divide"),' +
'.es(q="bytes<100").derivative().sum(200).min(200).label("query derivative min sum"),' +
'.es(*).if(operator=gt,if=200,then=50,else=150).label("condition")'
);
const firstAreaChartData = await visChart.getAreaChartData('initial');
const secondAreaChartData = await visChart.getAreaChartData('add multiply abs divide');
const thirdAreaChartData = await visChart.getAreaChartData('query derivative min sum');
const forthAreaChartData = await visChart.getAreaChartData('condition');
expect(firstAreaChartData).to.eql(firstAreaExpectedChartData);
expect(secondAreaChartData).to.eql(firstAreaExpectedChartData);
expect(thirdAreaChartData).to.eql(thirdAreaExpectedChartData);
expect(forthAreaChartData).to.eql(forthAreaExpectedChartData);
});
it('should display correct chart title, data and labels for expressions with custom labels, yaxis and offset', async () => {
const firstAreaExpectedChartData = [13112352443.375292, 13095637741.055172];
const secondAreaExpectedChartData = [
[1442642400000, 5732.783676366217],
[1442772000000, 5721.775973559419],
];
const thirdAreaExpectedChartData = [
[1442772000000, 5732.783676366217],
[1442901600000, 5721.775973559419],
];
await initVisualization(
'.es(index=logstash*,timefield="@timestamp",metric=avg:machine.ram).label("Average Machine RAM amount").yaxis(2,units=bytes,position=right),' +
'.es(index=logstash*,timefield="@timestamp",metric=avg:bytes).label("Average Bytes for request").yaxis(1,units=bytes,position=left),' +
'.es(index=logstash*,timefield="@timestamp",metric=avg:bytes, offset=-12h).label("Average Bytes for request with offset").yaxis(3,units=custom:BYTES_,position=right)',
'36h'
);
const leftAxesCount = await visChart.getAxesCountByPosition('left');
const rightAxesCount = await visChart.getAxesCountByPosition('right');
const firstAxesLabels = await visChart.getYAxisLabels();
const secondAxesLabels = await visChart.getYAxisLabels(1);
const thirdAxesLabels = await visChart.getYAxisLabels(2);
const firstAreaChartData = await visChart.getAreaChartData('Average Machine RAM amount');
const secondAreaChartData = await visChart.getAreaChartData(
'Average Bytes for request',
undefined,
true
);
const thirdAreaChartData = await visChart.getAreaChartData(
'Average Bytes for request with offset',
undefined,
true
);
expect(leftAxesCount).to.be(1);
expect(rightAxesCount).to.be(2);
expect(firstAreaChartData).to.eql(firstAreaExpectedChartData);
expect(secondAreaChartData).to.eql(secondAreaExpectedChartData);
expect(thirdAreaChartData).to.eql(thirdAreaExpectedChartData);
expect(firstAxesLabels).to.eql(['12.19GB', '12.2GB', '12.21GB']);
expect(secondAxesLabels).to.eql(['5.59KB', '5.6KB']);
expect(thirdAxesLabels.toString()).to.be(
'BYTES_5721,BYTES_5722,BYTES_5723,BYTES_5724,BYTES_5725,BYTES_5726,BYTES_5727,BYTES_5728,BYTES_5729,BYTES_5730,BYTES_5731,BYTES_5732,BYTES_5733'
);
});
it('should display correct chart data for split expression', async () => {
await initVisualization('.es(index=logstash-*, split=geo.dest:3)', '1 day');
const firstAreaChartData = await visChart.getAreaChartData('q:* > geo.dest:CN > count');
const secondAreaChartData = await visChart.getAreaChartData('q:* > geo.dest:IN > count');
const thirdAreaChartData = await visChart.getAreaChartData('q:* > geo.dest:US > count');
expect(firstAreaChartData).to.eql([0, 905, 910, 850, 0]);
expect(secondAreaChartData).to.eql([0, 763, 699, 825, 0]);
expect(thirdAreaChartData).to.eql([0, 423, 386, 389, 0]);
});
it('should display two areas and one bar chart items', async () => {
await initVisualization('.es(*), .es(*), .es(*).bars(stack=true)');
const areasChartsCount = await visChart.getAreaSeriesCount();
const barsChartsCount = await visChart.getHistogramSeriesCount();
expect(areasChartsCount).to.be(2);
expect(barsChartsCount).to.be(1);
});
describe('Legend', () => {
it('should correctly display the legend items names and position', async () => {
await initVisualization('.es(*).label("first series"), .es(*).label("second series")');
const legendNames = await visChart.getLegendEntries();
const legendElement = await find.byClassName('echLegend');
const isLegendTopPositioned = await legendElement.elementHasClass('echLegend--top');
const isLegendLeftPositioned = await legendElement.elementHasClass('echLegend--left');
expect(legendNames).to.eql(['first series', 'second series']);
expect(isLegendTopPositioned).to.be(true);
expect(isLegendLeftPositioned).to.be(true);
});
it('should correctly display the legend position', async () => {
await initVisualization('.es(*).legend(position=se)');
const legendElement = await find.byClassName('echLegend');
const isLegendBottomPositioned = await legendElement.elementHasClass('echLegend--bottom');
const isLegendRightPositioned = await legendElement.elementHasClass('echLegend--right');
expect(isLegendBottomPositioned).to.be(true);
expect(isLegendRightPositioned).to.be(true);
});
it('should not display the legend', async () => {
await initVisualization('.es(*), .es(*).label("second series").legend(position=false)');
const isLegendElementExists = await find.existsByCssSelector('.echLegend');
expect(isLegendElementExists).to.be(false);
});
});
after(
async () =>
await kibanaServer.uiSettings.update({
'timelion:legacyChartsLibrary': true,
})
);
});
}

View file

@ -52,6 +52,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./_vertical_bar_chart'));
loadTestFile(require.resolve('./_vertical_bar_chart_nontimeindex'));
loadTestFile(require.resolve('./_pie_chart'));
loadTestFile(require.resolve('./_timelion'));
});
describe('visualize ciGroup9', function () {

View file

@ -115,10 +115,10 @@ export class VisualizeChartPageObject extends FtrService {
.map((tick) => $(tick).text().trim());
}
public async getYAxisLabels() {
public async getYAxisLabels(nth = 0) {
if (await this.isNewLibraryChart(xyChartSelector)) {
const [yAxis] = (await this.getEsChartDebugState(xyChartSelector))?.axes?.y ?? [];
return yAxis?.labels;
const yAxis = (await this.getEsChartDebugState(xyChartSelector))?.axes?.y ?? [];
return yAxis[nth]?.labels;
}
const yAxis = await this.find.byCssSelector('.visAxis__column--y.visAxis__column--left');
@ -141,14 +141,19 @@ export class VisualizeChartPageObject extends FtrService {
* Gets the chart data and scales it based on chart height and label.
* @param dataLabel data-label value
* @param axis axis value, 'ValueAxis-1' by default
* @param shouldContainXAxisData boolean value for mapping points, false by default
*
* Returns an array of height values
*/
public async getAreaChartData(dataLabel: string, axis = 'ValueAxis-1') {
public async getAreaChartData(
dataLabel: string,
axis = 'ValueAxis-1',
shouldContainXAxisData = false
) {
if (await this.isNewLibraryChart(xyChartSelector)) {
const areas = (await this.getEsChartDebugState(xyChartSelector))?.areas ?? [];
const points = areas.find(({ name }) => name === dataLabel)?.lines.y1.points ?? [];
return points.map(({ y }) => y);
return shouldContainXAxisData ? points.map(({ x, y }) => [x, y]) : points.map(({ y }) => y);
}
const yAxisRatio = await this.getChartYAxisRatio(axis);
@ -556,12 +561,12 @@ export class VisualizeChartPageObject extends FtrService {
return values.filter((item) => item.length > 0);
}
public async getRightValueAxesCount() {
public async getAxesCountByPosition(axesPosition: typeof Position[keyof typeof Position]) {
if (await this.isNewLibraryChart(xyChartSelector)) {
const yAxes = (await this.getEsChartDebugState(xyChartSelector))?.axes?.y ?? [];
return yAxes.filter(({ position }) => position === Position.Right).length;
return yAxes.filter(({ position }) => position === axesPosition).length;
}
const axes = await this.find.allByCssSelector('.visAxis__column--right g.axis');
const axes = await this.find.allByCssSelector(`.visAxis__column--${axesPosition} g.axis`);
return axes.length;
}
@ -576,6 +581,16 @@ export class VisualizeChartPageObject extends FtrService {
await gauge.clickMouseButton({ xOffset: 0, yOffset });
}
public async getAreaSeriesCount() {
if (await this.isNewLibraryChart(xyChartSelector)) {
const areas = (await this.getEsChartDebugState(xyChartSelector))?.areas ?? [];
return areas.filter((area) => area.lines.y1.visible).length;
}
const series = await this.find.allByCssSelector('.points.area');
return series.length;
}
public async getHistogramSeriesCount() {
if (await this.isNewLibraryChart(xyChartSelector)) {
const bars = (await this.getEsChartDebugState(xyChartSelector))?.bars ?? [];

View file

@ -532,4 +532,10 @@ export class VisualizeEditorPageObject extends FtrService {
public async setSeriesType(seriesNth: number, type: string) {
await this.find.selectValue(`select#seriesType${seriesNth}`, type);
}
public async setTimelionInterval(interval: string) {
const timelionIntervalComboBoxSelector = 'timelionIntervalComboBox';
await this.comboBox.clearInputField(timelionIntervalComboBoxSelector);
await this.comboBox.setCustom(timelionIntervalComboBoxSelector, interval);
}
}

View file

@ -197,6 +197,10 @@ export class VisualizePageObject extends FtrService {
return await this.hasVisType('tile_map');
}
public async clickTimelion() {
await this.clickVisType('timelion');
}
public async clickTagCloud() {
await this.clickVisType('tagcloud');
}

View file

@ -27,13 +27,13 @@ export class MonacoEditorService extends FtrService {
return values[nthIndex] as string;
}
public async setCodeEditorValue(nthIndex: number, value: string) {
public async setCodeEditorValue(value: string, nthIndex = 0) {
await this.retry.try(async () => {
await this.browser.execute(
(editorIndex, codeEditorValue) => {
const editor = (window as any).MonacoEnvironment.monaco.editor;
const instance = editor.getModels()[editorIndex];
instance.setValue(JSON.parse(codeEditorValue));
instance.setValue(codeEditorValue);
},
nthIndex,
value

View file

@ -485,10 +485,7 @@ export class SecurityPageObject extends FtrService {
if (roleObj.elasticsearch.indices[0].query) {
await this.testSubjects.click('restrictDocumentsQuery0');
await this.monacoEditor.setCodeEditorValue(
0,
JSON.stringify(roleObj.elasticsearch.indices[0].query)
);
await this.monacoEditor.setCodeEditorValue(roleObj.elasticsearch.indices[0].query);
}
const globalPrivileges = (roleObj.kibana as any).global;