Skip to content

Commit 9e24cb2

Browse files
[ML] Adds functional tests for the index based data visualizer (#51832)
* [ML] Adds functional tests for the index based data visualizer * [ML] Address comments from review on data viz tests * [ML] Skip data visualizer functional tests on Firefox
1 parent 05047c3 commit 9e24cb2

File tree

13 files changed

+440
-6
lines changed

13 files changed

+440
-6
lines changed

x-pack/legacy/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,11 @@ export const DatavisualizerSelector: FC = () => {
113113
/>
114114
}
115115
footer={
116-
<EuiButton target="_self" href="#/filedatavisualizer">
116+
<EuiButton
117+
target="_self"
118+
href="#/filedatavisualizer"
119+
data-test-subj="mlDataVisualizerUploadFileButton"
120+
>
117121
<FormattedMessage
118122
id="xpack.ml.datavisualizer.selector.uploadFileButtonLabel"
119123
defaultMessage="Upload file"
@@ -139,7 +143,11 @@ export const DatavisualizerSelector: FC = () => {
139143
/>
140144
}
141145
footer={
142-
<EuiButton target="_self" href="#datavisualizer_index_select">
146+
<EuiButton
147+
target="_self"
148+
href="#datavisualizer_index_select"
149+
data-test-subj="mlDataVisualizerSelectIndexButton"
150+
>
143151
<FormattedMessage
144152
id="xpack.ml.datavisualizer.selector.selectIndexButtonLabel"
145153
defaultMessage="Select index"

x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/field_data_card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export const FieldDataCard: FC<FieldDataCardProps> = ({ config }) => {
7373
}
7474

7575
return (
76-
<div data-test-subj={`mlFieldDataCard_${fieldName}`}>
76+
<div data-test-subj={`mlFieldDataCard ${fieldName} ${type}`}>
7777
<div className="mlFieldDataCard">
7878
<FieldTitleBar card={config} />
7979
<div className="mlFieldDataCard__content">

x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/fields_panel/fields_panel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export const FieldsPanel: FC<Props> = ({
7878
}
7979

8080
return (
81-
<EuiPanel data-test-subj="mlDataVisualizerFieldsPanel">
81+
<EuiPanel data-test-subj={`mlDataVisualizerFieldsPanel ${fieldTypes}`}>
8282
<EuiTitle>
8383
<h2>{title}</h2>
8484
</EuiTitle>

x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/search_panel.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@ export const SearchPanel: FC<Props> = ({
122122
id="xpack.ml.datavisualizer.searchPanel.documentsPerShardLabel"
123123
defaultMessage="documents per shard from a total of {wrappedTotalCount} {totalCount, plural, one {document} other {documents}}"
124124
values={{
125-
wrappedTotalCount: <b>{totalCount}</b>,
125+
wrappedTotalCount: (
126+
<b data-test-subj="mlDataVisualizerTotalDocCount">{totalCount}</b>
127+
),
126128
totalCount,
127129
}}
128130
/>

x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ export const Page: FC = () => {
585585
return (
586586
<Fragment>
587587
<NavigationMenu tabId="datavisualizer" />
588-
<EuiPage data-test-subj="mlPageDataVisualizer">
588+
<EuiPage data-test-subj="mlPageIndexDataVisualizer">
589589
<EuiPageBody>
590590
<EuiPageContentHeader>
591591
<EuiPageContentHeaderSection>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
import { FtrProviderContext } from '../../../ftr_provider_context';
7+
8+
export default function({ loadTestFile }: FtrProviderContext) {
9+
describe('data visualizer', function() {
10+
this.tags(['skipFirefox']);
11+
12+
loadTestFile(require.resolve('./index_data_visualizer'));
13+
});
14+
}
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { FtrProviderContext } from '../../../ftr_provider_context';
8+
import { ML_JOB_FIELD_TYPES } from '../../../../../legacy/plugins/ml/common/constants/field_types';
9+
import { FieldVisConfig } from '../../../../../legacy/plugins/ml/public/application/datavisualizer/index_based/common';
10+
11+
function getFieldTypes(cards: FieldVisConfig[]) {
12+
const fieldTypes: ML_JOB_FIELD_TYPES[] = [];
13+
cards.forEach(card => {
14+
const fieldType = card.type;
15+
if (fieldTypes.includes(fieldType) === false) {
16+
fieldTypes.push(fieldType);
17+
}
18+
});
19+
20+
return fieldTypes.sort();
21+
}
22+
23+
// eslint-disable-next-line import/no-default-export
24+
export default function({ getService }: FtrProviderContext) {
25+
const esArchiver = getService('esArchiver');
26+
const ml = getService('ml');
27+
28+
const testDataList = [
29+
{
30+
suiteTitle: 'with full farequote index',
31+
sourceIndexOrSavedSearch: 'farequote',
32+
metricFieldsFilter: 'document',
33+
nonMetricFieldsFilter: 'airline',
34+
nonMetricFieldsTypeFilter: 'keyword',
35+
expected: {
36+
totalDocCount: 86274,
37+
fieldsPanelCount: 2, // Metrics panel and Fields panel
38+
metricCards: [
39+
{
40+
type: ML_JOB_FIELD_TYPES.NUMBER, // document count card
41+
existsInDocs: true,
42+
aggregatable: true,
43+
loading: false,
44+
},
45+
{
46+
fieldName: 'responsetime',
47+
type: ML_JOB_FIELD_TYPES.NUMBER,
48+
existsInDocs: true,
49+
aggregatable: true,
50+
loading: false,
51+
},
52+
],
53+
nonMetricCards: [
54+
{
55+
fieldName: '@timestamp',
56+
type: ML_JOB_FIELD_TYPES.DATE,
57+
existsInDocs: true,
58+
aggregatable: true,
59+
loading: false,
60+
},
61+
{
62+
fieldName: '@version',
63+
type: ML_JOB_FIELD_TYPES.TEXT,
64+
existsInDocs: true,
65+
aggregatable: false,
66+
loading: false,
67+
},
68+
{
69+
fieldName: '@version.keyword',
70+
type: ML_JOB_FIELD_TYPES.KEYWORD,
71+
existsInDocs: true,
72+
aggregatable: true,
73+
loading: false,
74+
},
75+
{
76+
fieldName: 'airline',
77+
type: ML_JOB_FIELD_TYPES.KEYWORD,
78+
existsInDocs: true,
79+
aggregatable: true,
80+
loading: false,
81+
},
82+
{
83+
fieldName: 'type',
84+
type: ML_JOB_FIELD_TYPES.TEXT,
85+
existsInDocs: true,
86+
aggregatable: false,
87+
loading: false,
88+
},
89+
{
90+
fieldName: 'type.keyword',
91+
type: ML_JOB_FIELD_TYPES.KEYWORD,
92+
existsInDocs: true,
93+
aggregatable: true,
94+
loading: false,
95+
},
96+
],
97+
nonMetricFieldsTypeFilterCardCount: 3,
98+
metricFieldsFilterCardCount: 1,
99+
nonMetricFieldsFilterCardCount: 1,
100+
},
101+
},
102+
{
103+
suiteTitle: 'with lucene query on farequote index',
104+
sourceIndexOrSavedSearch: 'farequote_lucene',
105+
metricFieldsFilter: 'responsetime',
106+
nonMetricFieldsFilter: 'version',
107+
nonMetricFieldsTypeFilter: 'keyword',
108+
expected: {
109+
totalDocCount: 34416,
110+
fieldsPanelCount: 2, // Metrics panel and Fields panel
111+
metricCards: [
112+
{
113+
type: ML_JOB_FIELD_TYPES.NUMBER, // document count card
114+
existsInDocs: true,
115+
aggregatable: true,
116+
loading: false,
117+
},
118+
{
119+
fieldName: 'responsetime',
120+
type: ML_JOB_FIELD_TYPES.NUMBER,
121+
existsInDocs: true,
122+
aggregatable: true,
123+
loading: false,
124+
},
125+
],
126+
nonMetricCards: [
127+
{
128+
fieldName: '@timestamp',
129+
type: ML_JOB_FIELD_TYPES.DATE,
130+
existsInDocs: true,
131+
aggregatable: true,
132+
loading: false,
133+
},
134+
{
135+
fieldName: '@version',
136+
type: ML_JOB_FIELD_TYPES.TEXT,
137+
existsInDocs: true,
138+
aggregatable: false,
139+
loading: false,
140+
},
141+
{
142+
fieldName: '@version.keyword',
143+
type: ML_JOB_FIELD_TYPES.KEYWORD,
144+
existsInDocs: true,
145+
aggregatable: true,
146+
loading: false,
147+
},
148+
{
149+
fieldName: 'airline',
150+
type: ML_JOB_FIELD_TYPES.KEYWORD,
151+
existsInDocs: true,
152+
aggregatable: true,
153+
loading: false,
154+
},
155+
{
156+
fieldName: 'type',
157+
type: ML_JOB_FIELD_TYPES.TEXT,
158+
existsInDocs: true,
159+
aggregatable: false,
160+
loading: false,
161+
},
162+
{
163+
fieldName: 'type.keyword',
164+
type: ML_JOB_FIELD_TYPES.KEYWORD,
165+
existsInDocs: true,
166+
aggregatable: true,
167+
loading: false,
168+
},
169+
],
170+
nonMetricFieldsTypeFilterCardCount: 3,
171+
metricFieldsFilterCardCount: 2,
172+
nonMetricFieldsFilterCardCount: 1,
173+
},
174+
},
175+
];
176+
177+
describe('index based', function() {
178+
this.tags(['smoke', 'mlqa']);
179+
before(async () => {
180+
await esArchiver.load('ml/farequote');
181+
});
182+
183+
after(async () => {
184+
await esArchiver.unload('ml/farequote');
185+
});
186+
187+
// TODO - add tests for
188+
// - validating metrics displayed inside the cards
189+
// - selecting a document sample size
190+
// - clicking on the link to the Advanced job wizard
191+
// - a test suite using a KQL based saved search
192+
for (const testData of testDataList) {
193+
describe(`${testData.suiteTitle}`, function() {
194+
it('loads the data visualizer selector page', async () => {
195+
await ml.navigation.navigateToMl();
196+
await ml.navigation.navigateToDataVisualizer();
197+
});
198+
199+
it('loads the saved search selection page', async () => {
200+
await ml.dataVisualizer.navigateToIndexPatternSelection();
201+
});
202+
203+
it('loads the index data visualizer page', async () => {
204+
await ml.jobSourceSelection.selectSourceForIndexBasedDataVisualizer(
205+
testData.sourceIndexOrSavedSearch
206+
);
207+
});
208+
209+
it('displays the time range step', async () => {
210+
await ml.dataVisualizerIndexBased.assertTimeRangeSelectorSectionExists();
211+
});
212+
213+
it('loads data for full time range', async () => {
214+
await ml.dataVisualizerIndexBased.clickUseFullDataButton(testData.expected.totalDocCount);
215+
});
216+
217+
it('displays the panels of fields', async () => {
218+
await ml.dataVisualizerIndexBased.assertFieldsPanelsExist(
219+
testData.expected.fieldsPanelCount
220+
);
221+
});
222+
223+
if (testData.expected.metricCards && testData.expected.metricCards.length > 0) {
224+
it('displays the Metrics panel', async () => {
225+
await ml.dataVisualizerIndexBased.assertFieldsPanelForTypesExist([
226+
ML_JOB_FIELD_TYPES.NUMBER,
227+
]); // document_count not exposed as a type in the panel
228+
});
229+
230+
it('displays the expected metric field cards', async () => {
231+
for (const fieldCard of testData.expected.metricCards) {
232+
await ml.dataVisualizerIndexBased.assertCardExists(
233+
fieldCard.type,
234+
fieldCard.fieldName
235+
);
236+
}
237+
});
238+
239+
it('filters metric fields cards with search', async () => {
240+
await ml.dataVisualizerIndexBased.filterFieldsPanelWithSearchString(
241+
['number'],
242+
testData.metricFieldsFilter,
243+
testData.expected.metricFieldsFilterCardCount
244+
);
245+
});
246+
}
247+
248+
if (testData.expected.nonMetricCards && testData.expected.nonMetricCards.length > 0) {
249+
it('displays the non-metric Fields panel', async () => {
250+
await ml.dataVisualizerIndexBased.assertFieldsPanelForTypesExist(
251+
getFieldTypes(testData.expected.nonMetricCards)
252+
);
253+
});
254+
255+
it('displays the expected non-metric field cards', async () => {
256+
for (const fieldCard of testData.expected.nonMetricCards) {
257+
await ml.dataVisualizerIndexBased.assertCardExists(
258+
fieldCard.type,
259+
fieldCard.fieldName
260+
);
261+
}
262+
});
263+
264+
it('sets the non metric field types input', async () => {
265+
const fieldTypes: ML_JOB_FIELD_TYPES[] = getFieldTypes(
266+
testData.expected.nonMetricCards
267+
);
268+
await ml.dataVisualizerIndexBased.assertFieldsPanelTypeInputExists(fieldTypes);
269+
await ml.dataVisualizerIndexBased.setFieldsPanelTypeInputValue(
270+
fieldTypes,
271+
testData.nonMetricFieldsTypeFilter,
272+
testData.expected.nonMetricFieldsTypeFilterCardCount
273+
);
274+
});
275+
276+
it('filters non-metric fields cards with search', async () => {
277+
await ml.dataVisualizerIndexBased.filterFieldsPanelWithSearchString(
278+
getFieldTypes(testData.expected.nonMetricCards),
279+
testData.nonMetricFieldsFilter,
280+
testData.expected.nonMetricFieldsFilterCardCount
281+
);
282+
});
283+
}
284+
});
285+
}
286+
});
287+
}

x-pack/test/functional/apps/machine_learning/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ export default function({ loadTestFile }: FtrProviderContext) {
1212
loadTestFile(require.resolve('./feature_controls'));
1313
loadTestFile(require.resolve('./pages'));
1414
loadTestFile(require.resolve('./anomaly_detection'));
15+
loadTestFile(require.resolve('./data_visualizer'));
1516
});
1617
}

x-pack/test/functional/services/machine_learning/data_visualizer.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,10 @@ export function MachineLearningDataVisualizerProvider({ getService }: FtrProvide
1717
async assertDataVisualizerIndexDataCardExists() {
1818
await testSubjects.existOrFail('mlDataVisualizerCardIndexData');
1919
},
20+
21+
async navigateToIndexPatternSelection() {
22+
await testSubjects.click('mlDataVisualizerSelectIndexButton');
23+
await testSubjects.existOrFail('mlPageSourceSelection');
24+
},
2025
};
2126
}

0 commit comments

Comments
 (0)