mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[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:
parent
04f0774977
commit
2605bd81cf
10 changed files with 256 additions and 16 deletions
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
206
test/functional/apps/visualize/_timelion.ts
Normal file
206
test/functional/apps/visualize/_timelion.ts
Normal 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,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
|
@ -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 () {
|
||||
|
|
|
@ -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 ?? [];
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue