[ML] Add functional tests for file data visualizer (#60413)
This PR adds basic functional tests for the file data visualizer, covering a file import and error messages for non-log files. It also moves the file input path handling to a common location in order to avoid code duplication.
This commit is contained in:
parent
9cd0a36740
commit
836b3d00ef
|
@ -514,6 +514,12 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
async setFileInputPath(path: string) {
|
||||
log.debug(`Setting the path '${path}' on the file input`);
|
||||
const input = await find.byCssSelector('.euiFilePicker__input');
|
||||
await input.type(path);
|
||||
}
|
||||
}
|
||||
|
||||
return new CommonPage();
|
||||
|
|
|
@ -612,9 +612,7 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider
|
|||
|
||||
log.debug(`Clicking importObjects`);
|
||||
await testSubjects.click('importObjects');
|
||||
log.debug(`Setting the path on the file input`);
|
||||
const input = await find.byCssSelector('.euiFilePicker__input');
|
||||
await input.type(path);
|
||||
await PageObjects.common.setFileInputPath(path);
|
||||
|
||||
if (!overwriteAll) {
|
||||
log.debug(`Toggling overwriteAll`);
|
||||
|
|
|
@ -25,7 +25,7 @@ import { WelcomeContent } from './welcome_content';
|
|||
|
||||
export function AboutPanel({ onFilePickerChange }) {
|
||||
return (
|
||||
<EuiPage restrictWidth={1000}>
|
||||
<EuiPage restrictWidth={1000} data-test-subj="mlPageFileDataVisualizerUpload">
|
||||
<EuiPageBody>
|
||||
<EuiPageContent className="file-datavisualizer-about-panel__content">
|
||||
<EuiFlexGroup gutterSize="xl">
|
||||
|
@ -58,7 +58,7 @@ export function AboutPanel({ onFilePickerChange }) {
|
|||
|
||||
export function LoadingPanel() {
|
||||
return (
|
||||
<EuiPage restrictWidth={400}>
|
||||
<EuiPage restrictWidth={400} data-test-subj="mlPageFileDataVisLoading">
|
||||
<EuiPageBody>
|
||||
<EuiPageContent className="file-datavisualizer-about-panel__content" paddingSize="l">
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
|
|
|
@ -48,6 +48,7 @@ export const BottomBar: FC<BottomBarProps> = ({ mode, onChangeMode, onCancel, di
|
|||
fill
|
||||
isDisabled={disableImport}
|
||||
onClick={() => onChangeMode(DATAVISUALIZER_MODE.IMPORT)}
|
||||
data-test-subj="mlFileDataVisOpenImportPageButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fileDatavisualizer.bottomBar.readMode.importButtonLabel"
|
||||
|
|
|
@ -62,6 +62,7 @@ export function FileTooLarge({ fileSize, maxFileSize }) {
|
|||
}
|
||||
color="danger"
|
||||
iconType="cross"
|
||||
data-test-subj="mlFileUploadErrorCallout fileTooLarge"
|
||||
>
|
||||
{errorText}
|
||||
</EuiCallOut>
|
||||
|
@ -79,6 +80,7 @@ export function FileCouldNotBeRead({ error, loaded }) {
|
|||
}
|
||||
color="danger"
|
||||
iconType="cross"
|
||||
data-test-subj="mlFileUploadErrorCallout fileCouldNotBeRead"
|
||||
>
|
||||
{error !== undefined && <p>{error}</p>}
|
||||
{loaded && (
|
||||
|
|
|
@ -47,6 +47,7 @@ export const SimpleSettings = ({
|
|||
defaultMessage: 'Index name, required field',
|
||||
}
|
||||
)}
|
||||
data-test-subj="mlFileDataVisIndexNameInput"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
|
@ -63,6 +64,7 @@ export const SimpleSettings = ({
|
|||
checked={createIndexPattern === true}
|
||||
disabled={initialized === true}
|
||||
onChange={onCreateIndexPatternChange}
|
||||
data-test-subj="mlFileDataVisCreateIndexPatternCheckbox"
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
|
|
@ -39,6 +39,7 @@ export function ImportSummary({
|
|||
}
|
||||
color="success"
|
||||
iconType="check"
|
||||
data-test-subj="mlFileImportSuccessCallout"
|
||||
>
|
||||
<EuiDescriptionList type="column" listItems={items} className="import-summary-list" />
|
||||
</EuiCallOut>
|
||||
|
|
|
@ -462,7 +462,7 @@ export class ImportView extends Component {
|
|||
initialized === true;
|
||||
|
||||
return (
|
||||
<EuiPage>
|
||||
<EuiPage data-test-subj="mlPageFileDataVisImport">
|
||||
<EuiPageBody>
|
||||
<EuiPageContentHeader>
|
||||
<EuiTitle>
|
||||
|
@ -470,7 +470,7 @@ export class ImportView extends Component {
|
|||
</EuiTitle>
|
||||
</EuiPageContentHeader>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiPanel>
|
||||
<EuiPanel data-test-subj="mlFileDataVisImportSettingsPanel">
|
||||
<EuiTitle size="s">
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
|
@ -516,6 +516,7 @@ export class ImportView extends Component {
|
|||
isLoading={importing}
|
||||
iconSide="right"
|
||||
fill
|
||||
data-test-subj="mlFileDataVisImportButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.fileDatavisualizer.importView.importButtonLabel"
|
||||
|
|
|
@ -36,16 +36,16 @@ export const ResultsView = ({ data, fileName, results, showEditFlyout }) => {
|
|||
];
|
||||
|
||||
return (
|
||||
<EuiPage>
|
||||
<EuiPage data-test-subj="mlPageFileDataVisResults">
|
||||
<EuiPageBody>
|
||||
<EuiPageContentHeader>
|
||||
<EuiTitle>
|
||||
<h1>{fileName}</h1>
|
||||
<h1 data-test-subj="mlFileDataVisResultsTitle">{fileName}</h1>
|
||||
</EuiTitle>
|
||||
</EuiPageContentHeader>
|
||||
<EuiSpacer size="m" />
|
||||
<div className="results">
|
||||
<EuiPanel>
|
||||
<EuiPanel data-test-subj="mlFileDataVisFileContentPanel">
|
||||
<FileContents
|
||||
data={data}
|
||||
format={results.format}
|
||||
|
@ -55,7 +55,7 @@ export const ResultsView = ({ data, fileName, results, showEditFlyout }) => {
|
|||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiPanel>
|
||||
<EuiPanel data-test-subj="mlFileDataVisSummaryPanel">
|
||||
<AnalysisSummary results={results} />
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
@ -70,7 +70,7 @@ export const ResultsView = ({ data, fileName, results, showEditFlyout }) => {
|
|||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiPanel>
|
||||
<EuiPanel data-test-subj="mlFileDataVisFileStatsPanel">
|
||||
<EuiTabbedContent tabs={tabs} initialSelectedTab={tabs[0]} onTabClick={() => {}} />
|
||||
</EuiPanel>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function({ getService }: FtrProviderContext) {
|
||||
const ml = getService('ml');
|
||||
|
||||
const testDataListPositive = [
|
||||
{
|
||||
suiteSuffix: 'with an artificial server log',
|
||||
filePath: path.join(__dirname, 'files_to_import', 'artificial_server_log'),
|
||||
indexName: 'user-import_1',
|
||||
createIndexPattern: false,
|
||||
expected: {
|
||||
results: {
|
||||
title: 'artificial_server_log',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const testDataListNegative = [
|
||||
{
|
||||
suiteSuffix: 'with a non-log file',
|
||||
filePath: path.join(__dirname, 'files_to_import', 'not_a_log_file'),
|
||||
},
|
||||
];
|
||||
|
||||
describe('file based', function() {
|
||||
this.tags(['smoke', 'mlqa']);
|
||||
before(async () => {
|
||||
await ml.securityUI.loginAsMlPowerUser();
|
||||
await ml.navigation.navigateToMl();
|
||||
});
|
||||
|
||||
for (const testData of testDataListPositive) {
|
||||
describe(testData.suiteSuffix, function() {
|
||||
after(async () => {
|
||||
await ml.api.deleteIndices(testData.indexName);
|
||||
});
|
||||
|
||||
it('loads the data visualizer selector page', async () => {
|
||||
await ml.navigation.navigateToDataVisualizer();
|
||||
});
|
||||
|
||||
it('loads the file upload page', async () => {
|
||||
await ml.dataVisualizer.navigateToFileUpload();
|
||||
});
|
||||
|
||||
it('selects a file and loads visualizer results', async () => {
|
||||
await ml.dataVisualizerFileBased.selectFile(testData.filePath);
|
||||
});
|
||||
|
||||
it('displays the components of the file details page', async () => {
|
||||
await ml.dataVisualizerFileBased.assertFileTitle(testData.expected.results.title);
|
||||
await ml.dataVisualizerFileBased.assertFileContentPanelExists();
|
||||
await ml.dataVisualizerFileBased.assertSummaryPanelExists();
|
||||
await ml.dataVisualizerFileBased.assertFileStatsPanelExists();
|
||||
});
|
||||
|
||||
it('loads the import settings page', async () => {
|
||||
await ml.dataVisualizerFileBased.navigateToFileImport();
|
||||
});
|
||||
|
||||
it('sets the index name', async () => {
|
||||
await ml.dataVisualizerFileBased.setIndexName(testData.indexName);
|
||||
});
|
||||
|
||||
it('sets the create index pattern checkbox', async () => {
|
||||
await ml.dataVisualizerFileBased.setCreateIndexPatternCheckboxState(
|
||||
testData.createIndexPattern
|
||||
);
|
||||
});
|
||||
|
||||
it('imports the file', async () => {
|
||||
await ml.dataVisualizerFileBased.startImportAndWaitForProcessing();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
for (const testData of testDataListNegative) {
|
||||
describe(testData.suiteSuffix, function() {
|
||||
it('loads the data visualizer selector page', async () => {
|
||||
await ml.navigation.navigateToDataVisualizer();
|
||||
});
|
||||
|
||||
it('loads the file upload page', async () => {
|
||||
await ml.dataVisualizer.navigateToFileUpload();
|
||||
});
|
||||
|
||||
it('selects a file and displays an error', async () => {
|
||||
await ml.dataVisualizerFileBased.selectFile(testData.filePath, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
2018-01-06 16:56:14.295748 INFO host:'Server A' Incoming connection from ip 123.456.789.0
|
||||
2018-01-06 16:56:15.295748 INFO host:'Server A' Incoming connection from ip 123.456.789.1
|
||||
2018-01-06 16:56:16.295748 INFO host:'Server A' Incoming connection from ip 123.456.789.2
|
||||
2018-01-06 16:56:17.295748 INFO host:'Server A' Incoming connection from ip 123.456.789.3
|
||||
2018-01-06 16:56:18.295748 INFO host:'Server B' Incoming connection from ip 123.456.789.0
|
||||
2018-01-06 16:56:19.295748 INFO host:'Server B' Incoming connection from ip 123.456.789.2
|
||||
2018-01-06 16:56:20.295748 INFO host:'Server B' Incoming connection from ip 123.456.789.3
|
||||
2018-01-06 16:56:21.295748 INFO host:'Server B' Incoming connection from ip 123.456.789.4
|
||||
2018-01-06 16:56:22.295748 WARN host:'Server A' Disk watermark 80%
|
||||
2018-01-06 17:16:23.295748 WARN host:'Server A' Disk watermark 90%
|
||||
2018-01-06 17:36:10.295748 ERROR host:'Server A' Main process crashed
|
||||
2018-01-06 17:36:14.295748 INFO host:'Server A' Connection from ip 123.456.789.0 closed
|
||||
2018-01-06 17:36:15.295748 INFO host:'Server A' Connection from ip 123.456.789.1 closed
|
||||
2018-01-06 17:36:16.295748 INFO host:'Server A' Connection from ip 123.456.789.2 closed
|
||||
2018-01-06 17:36:17.295748 INFO host:'Server A' Connection from ip 123.456.789.3 closed
|
||||
2018-01-06 17:46:11.295748 INFO host:'Server B' Some special characters °!"§$%&/()=?`'^²³{[]}\+*~#'-_.:,;µ|<>äöüß
|
||||
2018-01-06 17:46:12.295748 INFO host:'Server B' Shutting down
|
||||
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
This
|
||||
is
|
||||
not
|
||||
a
|
||||
log
|
||||
file
|
||||
|
||||
|
|
@ -10,5 +10,6 @@ export default function({ loadTestFile }: FtrProviderContext) {
|
|||
this.tags(['skipFirefox']);
|
||||
|
||||
loadTestFile(require.resolve('./index_data_visualizer'));
|
||||
loadTestFile(require.resolve('./file_data_visualizer'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -515,9 +515,7 @@ export function GisPageProvider({ getService, getPageObjects }) {
|
|||
}
|
||||
|
||||
async uploadJsonFileForIndexing(path) {
|
||||
log.debug(`Setting the path on the file input`);
|
||||
const input = await find.byCssSelector('.euiFilePicker__input');
|
||||
await input.type(path);
|
||||
await PageObjects.common.setFileInputPath(path);
|
||||
log.debug(`File selected`);
|
||||
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
|
|
|
@ -22,5 +22,10 @@ export function MachineLearningDataVisualizerProvider({ getService }: FtrProvide
|
|||
await testSubjects.click('mlDataVisualizerSelectIndexButton');
|
||||
await testSubjects.existOrFail('mlPageSourceSelection');
|
||||
},
|
||||
|
||||
async navigateToFileUpload() {
|
||||
await testSubjects.click('mlDataVisualizerUploadFileButton');
|
||||
await testSubjects.existOrFail('mlPageFileDataVisualizerUpload');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { MlCommon } from './common';
|
||||
|
||||
export function MachineLearningDataVisualizerFileBasedProvider(
|
||||
{ getService, getPageObjects }: FtrProviderContext,
|
||||
mlCommon: MlCommon
|
||||
) {
|
||||
const log = getService('log');
|
||||
const retry = getService('retry');
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
const PageObjects = getPageObjects(['common']);
|
||||
|
||||
return {
|
||||
async selectFile(path: string, expectError: boolean = false) {
|
||||
log.debug(`Importing file '${path}' ...`);
|
||||
await PageObjects.common.setFileInputPath(path);
|
||||
|
||||
await testSubjects.waitForDeleted('mlPageFileDataVisLoading');
|
||||
|
||||
if (expectError) {
|
||||
await testSubjects.existOrFail('~mlFileUploadErrorCallout');
|
||||
} else {
|
||||
await testSubjects.missingOrFail('~mlFileUploadErrorCallout');
|
||||
await testSubjects.existOrFail('mlPageFileDataVisResults');
|
||||
}
|
||||
},
|
||||
|
||||
async assertFileTitle(expectedTitle: string) {
|
||||
const actualTitle = await testSubjects.getVisibleText('mlFileDataVisResultsTitle');
|
||||
expect(actualTitle).to.eql(
|
||||
expectedTitle,
|
||||
`Expected file title to be '${expectedTitle}' (got '${actualTitle}')`
|
||||
);
|
||||
},
|
||||
|
||||
async assertFileContentPanelExists() {
|
||||
await testSubjects.existOrFail('mlFileDataVisFileContentPanel');
|
||||
},
|
||||
|
||||
async assertSummaryPanelExists() {
|
||||
await testSubjects.existOrFail('mlFileDataVisSummaryPanel');
|
||||
},
|
||||
|
||||
async assertFileStatsPanelExists() {
|
||||
await testSubjects.existOrFail('mlFileDataVisFileStatsPanel');
|
||||
},
|
||||
|
||||
async navigateToFileImport() {
|
||||
await testSubjects.click('mlFileDataVisOpenImportPageButton');
|
||||
await testSubjects.existOrFail('mlPageFileDataVisImport');
|
||||
},
|
||||
|
||||
async assertImportSettingsPanelExists() {
|
||||
await testSubjects.existOrFail('mlFileDataVisImportSettingsPanel');
|
||||
},
|
||||
|
||||
async assertIndexNameValue(expectedValue: string) {
|
||||
const actualIndexName = await testSubjects.getAttribute(
|
||||
'mlFileDataVisIndexNameInput',
|
||||
'value'
|
||||
);
|
||||
expect(actualIndexName).to.eql(
|
||||
expectedValue,
|
||||
`Expected index name to be '${expectedValue}' (got '${actualIndexName}')`
|
||||
);
|
||||
},
|
||||
|
||||
async setIndexName(indexName: string) {
|
||||
await mlCommon.setValueWithChecks('mlFileDataVisIndexNameInput', indexName, {
|
||||
clearWithKeyboard: true,
|
||||
});
|
||||
await this.assertIndexNameValue(indexName);
|
||||
},
|
||||
|
||||
async assertCreateIndexPatternCheckboxValue(expectedValue: boolean) {
|
||||
const isChecked = await testSubjects.isChecked('mlFileDataVisCreateIndexPatternCheckbox');
|
||||
expect(isChecked).to.eql(
|
||||
expectedValue,
|
||||
`Expected create index pattern checkbox to be ${expectedValue ? 'checked' : 'unchecked'}`
|
||||
);
|
||||
},
|
||||
|
||||
async setCreateIndexPatternCheckboxState(newState: boolean) {
|
||||
const isChecked = await testSubjects.isChecked('mlFileDataVisCreateIndexPatternCheckbox');
|
||||
if (isChecked !== newState) {
|
||||
// this checkbox can't be clicked directly, instead click the corresponding label
|
||||
const panel = await testSubjects.find('mlFileDataVisImportSettingsPanel');
|
||||
const label = await panel.findByCssSelector('[for="createIndexPattern"]');
|
||||
await label.click();
|
||||
}
|
||||
await this.assertCreateIndexPatternCheckboxValue(newState);
|
||||
},
|
||||
|
||||
async startImportAndWaitForProcessing() {
|
||||
await testSubjects.click('mlFileDataVisImportButton');
|
||||
await retry.tryForTime(60 * 1000, async () => {
|
||||
await testSubjects.existOrFail('mlFileImportSuccessCallout');
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
|
@ -13,6 +13,7 @@ export { MachineLearningDataFrameAnalyticsProvider } from './data_frame_analytic
|
|||
export { MachineLearningDataFrameAnalyticsCreationProvider } from './data_frame_analytics_creation';
|
||||
export { MachineLearningDataFrameAnalyticsTableProvider } from './data_frame_analytics_table';
|
||||
export { MachineLearningDataVisualizerProvider } from './data_visualizer';
|
||||
export { MachineLearningDataVisualizerFileBasedProvider } from './data_visualizer_file_based';
|
||||
export { MachineLearningDataVisualizerIndexBasedProvider } from './data_visualizer_index_based';
|
||||
export { MachineLearningJobManagementProvider } from './job_management';
|
||||
export { MachineLearningJobSelectionProvider } from './job_selection';
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
MachineLearningDataFrameAnalyticsCreationProvider,
|
||||
MachineLearningDataFrameAnalyticsTableProvider,
|
||||
MachineLearningDataVisualizerProvider,
|
||||
MachineLearningDataVisualizerFileBasedProvider,
|
||||
MachineLearningDataVisualizerIndexBasedProvider,
|
||||
MachineLearningJobManagementProvider,
|
||||
MachineLearningJobSelectionProvider,
|
||||
|
@ -48,6 +49,7 @@ export function MachineLearningProvider(context: FtrProviderContext) {
|
|||
);
|
||||
const dataFrameAnalyticsTable = MachineLearningDataFrameAnalyticsTableProvider(context);
|
||||
const dataVisualizer = MachineLearningDataVisualizerProvider(context);
|
||||
const dataVisualizerFileBased = MachineLearningDataVisualizerFileBasedProvider(context, common);
|
||||
const dataVisualizerIndexBased = MachineLearningDataVisualizerIndexBasedProvider(context);
|
||||
const jobManagement = MachineLearningJobManagementProvider(context, api);
|
||||
const jobSelection = MachineLearningJobSelectionProvider(context);
|
||||
|
@ -75,6 +77,7 @@ export function MachineLearningProvider(context: FtrProviderContext) {
|
|||
dataFrameAnalyticsCreation,
|
||||
dataFrameAnalyticsTable,
|
||||
dataVisualizer,
|
||||
dataVisualizerFileBased,
|
||||
dataVisualizerIndexBased,
|
||||
jobManagement,
|
||||
jobSelection,
|
||||
|
|
Loading…
Reference in a new issue