Skip to content

Commit

Permalink
[Timelion Viz] Add functional tests (elastic#107287)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
4 people authored and vadimkibana committed Aug 8, 2021
1 parent f3873d5 commit 8a805bf
Show file tree
Hide file tree
Showing 10 changed files with 256 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -174,14 +183,15 @@ const TimelionVisComponent = ({
}, [chart]);

return (
<div className="timelionChart">
<div className="timelionChart" data-test-subj="visTypeXyChart">
{title && (
<EuiTitle className="timelionChart__topTitle" size="xxxs">
<h4>{title}</h4>
</EuiTitle>
)}
<Chart ref={chartRef} renderer="canvas" size={{ width: '100%' }}>
<Settings
debugState={window._echDebugStateFlag ?? false}
onBrushEnd={brushEndListener}
showLegend={legend.showLegend}
showLegendExtra={true}
Expand Down
2 changes: 1 addition & 1 deletion test/functional/apps/visualize/_point_series_options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
Expand Down
206 changes: 206 additions & 0 deletions test/functional/apps/visualize/_timelion.ts
Original file line number Diff line number Diff line change
@@ -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,
})
);
});
}
1 change: 1 addition & 0 deletions test/functional/apps/visualize/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down
31 changes: 23 additions & 8 deletions test/functional/page_objects/visualize_chart_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}

Expand All @@ -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 ?? [];
Expand Down
6 changes: 6 additions & 0 deletions test/functional/page_objects/visualize_editor_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
4 changes: 4 additions & 0 deletions test/functional/page_objects/visualize_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand Down
4 changes: 2 additions & 2 deletions test/functional/services/monaco_editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 1 addition & 4 deletions x-pack/test/functional/page_objects/security_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 8a805bf

Please sign in to comment.