[ML] Add single metric job wizard test (#42709) (#42823)

This PR adds functional UI tests to create a machine learning job using the single metric wizard.
This commit is contained in:
Robert Oskamp 2019-08-07 15:31:19 +02:00 committed by GitHub
parent d6b89fe76f
commit c76b0c18e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 1923 additions and 131 deletions

View file

@ -3,6 +3,7 @@
exports[`FullTimeRangeSelector renders the selector 1`] = `
<EuiButton
color="primary"
data-test-subj="mlButtonUseFullData"
fill={true}
iconSide="left"
isDisabled={false}

View file

@ -30,7 +30,12 @@ export const FullTimeRangeSelector: FC<Props> = ({ indexPattern, query, disabled
}
}
return (
<EuiButton fill isDisabled={disabled} onClick={() => setRange(indexPattern, query)}>
<EuiButton
fill
isDisabled={disabled}
onClick={() => setRange(indexPattern, query)}
data-test-subj="mlButtonUseFullData"
>
<FormattedMessage
id="xpack.ml.fullTimeRangeSelector.useFullDataButtonLabel"
defaultMessage="Use full {indexPatternTitle} data"

View file

@ -2,7 +2,7 @@
<ml-new-job class="index-or-saved-search-selection">
<ml-message-bar></ml-message-bar>
<div class='kuiViewContent kuiViewContent--constrainedWidth kuiViewContentItem' >
<div ng-controller="MlNewJobStepIndexOrSearch" class="visWizard">
<div ng-controller="MlNewJobStepIndexOrSearch" class="visWizard" data-test-subj="mlPageSourceSelection">
<div class="visWizard__column visWizard__column--small">
<h3
class="kuiTitle kuiVerticalRhythm"

View file

@ -1,7 +1,7 @@
<ml-nav-menu name="new_job" />
<ml-new-job class="job-type-gallery">
<ml-message-bar></ml-message-bar>
<div ng-controller="MlNewJobStepJobType">
<div ng-controller="MlNewJobStepJobType" data-test-subj="mlPageJobTypeSelection">
<!-- Presents the various options for creating a job -->
<div class="job-types-content">
<h1
@ -214,7 +214,7 @@
</a>
</div>
<div class="euiFlexItem" ng-class='{disabled: isTimeBasedIndex===false}'>
<a ng-href="{{getUrl('#jobs/new_job/new_new_job/single_metric')}}" ng-class='{disabled: isTimeBasedIndex===false}' class="euiLink synopsis">
<a ng-href="{{getUrl('#jobs/new_job/new_new_job/single_metric')}}" ng-class='{disabled: isTimeBasedIndex===false}' class="euiLink synopsis" data-test-subj="mlJobTypeLinkSingleMetricJob">
<div class="euiPanel euiPanel--paddingMedium synopsisPanel">
<div class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--responsive">
<div class="euiFlexItem euiFlexItem--flexGrowZero">
@ -246,7 +246,7 @@
</a>
</div>
<div class="euiFlexItem" ng-class='{disabled: isTimeBasedIndex===false}'>
<a ng-href="{{getUrl('#jobs/new_job/new_new_job/multi_metric')}}" ng-class='{disabled: isTimeBasedIndex===false}' class="euiLink synopsis">
<a ng-href="{{getUrl('#jobs/new_job/new_new_job/multi_metric')}}" ng-class='{disabled: isTimeBasedIndex===false}' class="euiLink synopsis" data-test-subj="mlJobTypeLinkMultiMetricJob">
<div class="euiPanel euiPanel--paddingMedium synopsisPanel">
<div class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--responsive">
<div class="euiFlexItem euiFlexItem--flexGrowZero">
@ -278,7 +278,7 @@
</a>
</div>
<div class="euiFlexItem" ng-class='{disabled: isTimeBasedIndex===false}'>
<a ng-href="{{getUrl('#jobs/new_job/new_new_job/population')}}" ng-class='{disabled: isTimeBasedIndex===false}' class="euiLink synopsis">
<a ng-href="{{getUrl('#jobs/new_job/new_new_job/population')}}" ng-class='{disabled: isTimeBasedIndex===false}' class="euiLink synopsis" data-test-subj="mlJobTypeLinkPopulationJob">
<div class="euiPanel euiPanel--paddingMedium synopsisPanel">
<div class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--responsive">
<div class="euiFlexItem euiFlexItem--flexGrowZero">

View file

@ -44,7 +44,7 @@ export const AnomalyChart: FC<Props> = ({
const xDomain = getXRange(data);
return (
<div style={{ width, height }}>
<div style={{ width, height }} data-test-subj="mlAnomalyChart">
<Chart>
<Settings xDomain={xDomain} tooltip={TooltipType.None} />
<Axes chartData={data} />

View file

@ -22,7 +22,7 @@ const SPEC_ID = 'event_rate';
export const EventRateChart: FC<Props> = ({ eventRateChartData, height, width, showAxis }) => {
return (
<div style={{ width, height }}>
<div style={{ width, height }} data-test-subj="mlEventRateChart">
<Chart>
{showAxis === true && <Axes />}

View file

@ -24,9 +24,14 @@ export const AdvancedSection: FC<Props> = ({ advancedExpanded, setAdvancedExpand
buttonContent={ButtonContent}
onToggle={setAdvancedExpanded}
initialIsOpen={advancedExpanded}
data-test-subj="mlJobWizardToggleAdvancedSection"
>
<EuiSpacer />
<EuiFlexGroup gutterSize="xl" style={{ marginLeft: '0px', marginRight: '0px' }}>
<EuiFlexGroup
gutterSize="xl"
style={{ marginLeft: '0px', marginRight: '0px' }}
data-test-subj="mlJobWizardAdvancedSection"
>
<EuiFlexItem>
<ModelPlotSwitch />
<ModelMemoryLimitInput />

View file

@ -24,7 +24,12 @@ export const DedicatedIndexSwitch: FC = () => {
return (
<Description>
<EuiSwitch name="switch" checked={useDedicatedIndex} onChange={toggleModelPlot} />
<EuiSwitch
name="switch"
checked={useDedicatedIndex}
onChange={toggleModelPlot}
data-test-subj="mlJobWizardSwitchUseDedicatedIndex"
/>
</Description>
);
};

View file

@ -33,6 +33,7 @@ export const ModelMemoryLimitInput: FC = () => {
value={modelMemoryLimit}
onChange={e => setModelMemoryLimit(e.target.value)}
isInvalid={validation.valid === false}
data-test-subj="mlJobWizardInputModelMemoryLimit"
/>
</Description>
);

View file

@ -24,7 +24,12 @@ export const ModelPlotSwitch: FC = () => {
return (
<Description>
<EuiSwitch name="switch" checked={modelPlotEnabled} onChange={toggleModelPlot} />
<EuiSwitch
name="switch"
checked={modelPlotEnabled}
onChange={toggleModelPlot}
data-test-subj="mlJobWizardSwitchModelPlot"
/>
</Description>
);
};

View file

@ -77,6 +77,7 @@ export const GroupsInput: FC = () => {
onCreateOption={onCreateGroup}
isClearable={true}
isInvalid={validation.valid === false}
data-test-subj="mlJobWizardComboBoxJobGroups"
/>
</Description>
);

View file

@ -24,6 +24,7 @@ export const JobDescriptionInput: FC = () => {
placeholder="Job description"
value={jobDescription}
onChange={e => setJobDescription(e.target.value)}
data-test-subj="mlJobWizardInputJobDescription"
/>
</Description>
);

View file

@ -36,6 +36,7 @@ export const JobIdInput: FC = () => {
value={jobId}
onChange={e => setJobId(e.target.value)}
isInvalid={validation.valid === false}
data-test-subj="mlJobWizardInputJobId"
/>
</Description>
);

View file

@ -64,7 +64,11 @@ export const AggSelect: FC<Props> = ({ fields, changeHandler, selectedOptions, r
}, [jobValidatorUpdated]);
return (
<EuiFormRow error={validation.message} isInvalid={validation.valid === false}>
<EuiFormRow
error={validation.message}
isInvalid={validation.valid === false}
data-test-subj="mlJobWizardAggSelection"
>
<EuiComboBox
singleSelection={{ asPlainText: true }}
options={options}

View file

@ -20,6 +20,7 @@ export const BucketSpanInput: FC<Props> = ({ bucketSpan, setBucketSpan, isInvali
value={bucketSpan}
onChange={e => setBucketSpan(e.target.value)}
isInvalid={isInvalid}
data-test-subj="mlJobWizardInputBucketSpan"
/>
);
};

View file

@ -77,11 +77,21 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
<EuiHorizontalRule />
{progress < 100 && (
<Fragment>
<EuiButton onClick={start} isDisabled={progress > 0} disabled={isValid === false}>
<EuiButton
onClick={start}
isDisabled={progress > 0}
disabled={isValid === false}
data-test-subj="mlJobWizardButtonCreateJob"
>
Create job
</EuiButton>
&emsp;
<EuiButtonEmpty size="s" onClick={toggleJsonFlyout} isDisabled={progress > 0}>
<EuiButtonEmpty
size="s"
onClick={toggleJsonFlyout}
isDisabled={progress > 0}
data-test-subj="mlJobWizardButtonPreviewJobJson"
>
Preview job JSON
</EuiButtonEmpty>
{showJsonFlyout && (
@ -92,7 +102,9 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
)}
{progress > 0 && (
<Fragment>
<EuiButton onClick={viewResults}>View results</EuiButton>
<EuiButton onClick={viewResults} data-test-subj="mlJobWizardButtonViewResults">
View results
</EuiButton>
</Fragment>
)}
</Fragment>

View file

@ -27,7 +27,13 @@ export const WizardNav: FC<StepsNavProps> = ({
<EuiFlexItem />
{previous && (
<EuiFlexItem grow={false}>
<EuiButton disabled={!previousActive} onClick={previous} iconType="arrowLeft" size="s">
<EuiButton
disabled={!previousActive}
onClick={previous}
iconType="arrowLeft"
size="s"
data-test-subj="mlJobWizardNavButtonPrevious"
>
{i18n.translate('xpack.ml.newJob.wizard.previousStepButton', {
defaultMessage: 'Previous',
})}
@ -36,7 +42,13 @@ export const WizardNav: FC<StepsNavProps> = ({
)}
{next && (
<EuiFlexItem grow={false}>
<EuiButton disabled={!nextActive} onClick={next} iconType="arrowRight" size="s">
<EuiButton
disabled={!nextActive}
onClick={next}
iconType="arrowRight"
size="s"
data-test-subj="mlJobWizardNavButtonNext"
>
{i18n.translate('xpack.ml.newJob.wizard.nextStepButton', {
defaultMessage: 'Next',
})}

View file

@ -95,7 +95,7 @@ export const Page: FC<PageProps> = ({ existingJobsAndGroups, jobType }) => {
return (
<Fragment>
<EuiPage style={{ backgroundColor: '#FFF' }}>
<EuiPage style={{ backgroundColor: '#FFF' }} data-test-subj="mlPageJobWizard">
<EuiPageBody>
<EuiPageContentBody>
<Wizard

View file

@ -176,7 +176,7 @@ export const Wizard: FC<Props> = ({
{currentStep === WIZARD_STEPS.TIME_RANGE && (
<Fragment>
<Title>Time range</Title>
<Title data-test-subj="mlJobWizardStepTitleTimeRange">Time range</Title>
<TimeRangeStep
isCurrentStep={currentStep === WIZARD_STEPS.TIME_RANGE}
setCurrentStep={setCurrentStep}
@ -185,7 +185,7 @@ export const Wizard: FC<Props> = ({
)}
{currentStep === WIZARD_STEPS.PICK_FIELDS && (
<Fragment>
<Title>Pick fields</Title>
<Title data-test-subj="mlJobWizardStepTitlePickFields">Pick fields</Title>
<PickFieldsStep
isCurrentStep={currentStep === WIZARD_STEPS.PICK_FIELDS}
setCurrentStep={setCurrentStep}
@ -194,7 +194,7 @@ export const Wizard: FC<Props> = ({
)}
{currentStep === WIZARD_STEPS.JOB_DETAILS && (
<Fragment>
<Title>Job details</Title>
<Title data-test-subj="mlJobWizardStepTitleJobDetails">Job details</Title>
<JobDetailsStep
isCurrentStep={currentStep === WIZARD_STEPS.JOB_DETAILS}
setCurrentStep={setCurrentStep}
@ -207,7 +207,7 @@ export const Wizard: FC<Props> = ({
)}
{currentStep === WIZARD_STEPS.VALIDATION && (
<Fragment>
<Title>Validation</Title>
<Title data-test-subj="mlJobWizardStepTitleValidation">Validation</Title>
<ValidationStep
isCurrentStep={currentStep === WIZARD_STEPS.VALIDATION}
setCurrentStep={setCurrentStep}
@ -216,7 +216,7 @@ export const Wizard: FC<Props> = ({
)}
{currentStep === WIZARD_STEPS.SUMMARY && (
<Fragment>
<Title>Summary</Title>
<Title data-test-subj="mlJobWizardStepTitleSummary">Summary</Title>
<SummaryStep
isCurrentStep={currentStep === WIZARD_STEPS.SUMMARY}
setCurrentStep={setCurrentStep}
@ -227,11 +227,11 @@ export const Wizard: FC<Props> = ({
);
};
const Title: FC = ({ children }) => {
const Title: FC<{ 'data-test-subj': string }> = ({ 'data-test-subj': dataTestSubj, children }) => {
return (
<Fragment>
<EuiTitle>
<h2>{children}</h2>
<h2 data-test-subj={dataTestSubj}>{children}</h2>
</EuiTitle>
<EuiSpacer />
</Fragment>

View file

@ -0,0 +1,146 @@
/*
* 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';
// eslint-disable-next-line import/no-default-export
export default function({ getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const ml = getService('ml');
const jobId = `fq_single_1_${Date.now()}`;
describe('single metric job creation', function() {
this.tags('smoke');
before(async () => {
await esArchiver.loadIfNeeded('ml/farequote');
});
after(async () => {
await esArchiver.unload('ml/farequote');
await ml.api.cleanMlIndices();
await ml.api.cleanDataframeIndices();
});
it('loads the job management page', async () => {
await ml.navigation.navigateToMl();
await ml.navigation.navigateToJobManagement();
});
it('loads the new job source selection page', async () => {
await ml.jobManagement.navigateToNewJobSourceSelection();
});
it('loads the job type selection page', async () => {
await ml.jobSourceSelection.selectSourceIndexPattern('farequote');
});
it('loads the single metric job wizard page', async () => {
await ml.jobTypeSelection.selectSingleMetricJob();
});
it('displays the time range step', async () => {
await ml.jobWizardCommon.assertTimeRangeSectionExists();
});
it('displays the event rate chart', async () => {
await ml.jobWizardCommon.clickUseFullDataButton();
await ml.jobWizardCommon.assertEventRateChartExists();
});
it('displays the pick fields step', async () => {
await ml.jobWizardCommon.clickNextButton();
await ml.jobWizardCommon.assertPickFieldsSectionExists();
});
it('selects field and aggregation', async () => {
const identifier = 'Mean(responsetime)';
await ml.jobWizardCommon.assertAggAndFieldInputExists();
await ml.jobWizardCommon.selectAggAndField(identifier);
await ml.jobWizardCommon.assertAggAndFieldSelection(identifier);
});
it('inputs the bucket span', async () => {
const bucketSpan = '30m';
await ml.jobWizardCommon.assertBucketSpanInputExists();
await ml.jobWizardCommon.setBucketSpan(bucketSpan);
await ml.jobWizardCommon.assertBucketSpanValue(bucketSpan);
});
it('displays the job details step', async () => {
await ml.jobWizardCommon.clickNextButton();
await ml.jobWizardCommon.assertJobDetailsSectionExists();
});
it('inputs the job id', async () => {
await ml.jobWizardCommon.assertJobIdInputExists();
await ml.jobWizardCommon.setJobId(jobId);
await ml.jobWizardCommon.assertJobIdValue(jobId);
});
it('inputs the job description', async () => {
const jobDescription =
'Create single metric job based on the farequote dataset with 30m bucketspan and mean(responsetime)';
await ml.jobWizardCommon.assertJobDescriptionInputExists();
await ml.jobWizardCommon.setJobDescription(jobDescription);
await ml.jobWizardCommon.assertJobDescriptionValue(jobDescription);
});
it('inputs job groups', async () => {
const jobGroups = ['automated', 'farequote', 'single-metric'];
await ml.jobWizardCommon.assertJobGroupInputExists();
for (const jobGroup of jobGroups) {
await ml.jobWizardCommon.addJobGroup(jobGroup);
}
await ml.jobWizardCommon.assertJobGroupSelection(jobGroups);
});
it('opens the advanced section', async () => {
await ml.jobWizardCommon.ensureAdvancedSectionOpen();
});
it('displays the model plot switch', async () => {
await ml.jobWizardCommon.assertModelPlotSwitchExists();
});
it('enables the dedicated index switch', async () => {
await ml.jobWizardCommon.assertDedicatedIndexSwitchExists();
await ml.jobWizardCommon.activateDedicatedIndexSwitch();
await ml.jobWizardCommon.assertDedicatedIndexSwitchCheckedState(true);
});
it('inputs the model memory limit', async () => {
const memoryLimit = '15MB';
await ml.jobWizardCommon.assertModelMemoryLimitInputExists();
await ml.jobWizardCommon.setModelMemoryLimit(memoryLimit);
await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit);
});
it('displays the validation step', async () => {
await ml.jobWizardCommon.clickNextButton();
await ml.jobWizardCommon.assertValidationSectionExists();
});
it('displays the summary step', async () => {
await ml.jobWizardCommon.clickNextButton();
await ml.jobWizardCommon.assertSummarySectionExists();
});
it('creates the job and finishes processing', async () => {
await ml.jobWizardCommon.assertCreateJobButtonExists();
await ml.jobWizardCommon.createJobAndWaitForCompletion();
});
it('displays the created job in the job list', async () => {
await ml.navigation.navigateToMl();
await ml.navigation.navigateToJobManagement();
await ml.jobManagement.filterJobsTable(jobId);
const jobRow = await ml.jobManagement.getJobRowByJobId(jobId);
expect(jobRow).to.not.be(null);
});
});
}

View file

@ -11,5 +11,6 @@ export default function({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./feature_controls'));
loadTestFile(require.resolve('./pages'));
loadTestFile(require.resolve('./create_single_metric_job'));
});
}

View file

@ -21,41 +21,41 @@ export default function({ getService }: FtrProviderContext) {
});
it('loads the home page', async () => {
await ml.navigateTo();
await ml.navigation.navigateToMl();
});
it('loads the job management page', async () => {
await ml.navigateToJobManagement();
await ml.assertJobStatsBarExists();
await ml.assertJobTableExists();
await ml.assertCreateNewJobButtonExists();
await ml.navigation.navigateToJobManagement();
await ml.jobManagement.assertJobStatsBarExists();
await ml.jobManagement.assertJobTableExists();
await ml.jobManagement.assertCreateNewJobButtonExists();
});
it('loads the anomaly explorer page', async () => {
await ml.navigateToAnomalyExplorert();
await ml.assertAnomalyExplorerEmptyListMessageExists();
await ml.navigation.navigateToAnomalyExplorert();
await ml.anomalyExplorer.assertAnomalyExplorerEmptyListMessageExists();
});
it('loads the single metric viewer page', async () => {
await ml.navigateToSingleMetricViewer();
await ml.assertSingleMetricViewerEmptyListMessageExsist();
await ml.navigation.navigateToSingleMetricViewer();
await ml.singleMetricViewer.assertSingleMetricViewerEmptyListMessageExsist();
});
it('loads the data frame page', async () => {
await ml.navigateToDataFrames();
await ml.assertDataFrameEmptyListMessageExists();
await ml.navigation.navigateToDataFrames();
await ml.dataFrames.assertDataFrameEmptyListMessageExists();
});
it('loads the data visualizer page', async () => {
await ml.navigateToDataVisualizer();
await ml.assertDataVisualizerImportDataCardExists();
await ml.assertDataVisualizerIndexDataCardExists();
await ml.navigation.navigateToDataVisualizer();
await ml.dataVisualizer.assertDataVisualizerImportDataCardExists();
await ml.dataVisualizer.assertDataVisualizerIndexDataCardExists();
});
it('loads the settings page', async () => {
await ml.navigateToSettings();
await ml.assertSettingsCalendarLinkExists();
await ml.assertSettingsFilterlistLinkExists();
await ml.navigation.navigateToSettings();
await ml.settings.assertSettingsCalendarLinkExists();
await ml.settings.assertSettingsFilterlistLinkExists();
});
});
}

File diff suppressed because it is too large Load diff

View file

@ -46,7 +46,7 @@ import { UserMenuProvider } from './user_menu';
import { UptimeProvider } from './uptime';
import { InfraSourceConfigurationFlyoutProvider } from './infra_source_configuration_flyout';
import { InfraLogStreamProvider } from './infra_log_stream';
import { MachineLearningProvider } from './machine_learning';
import { MachineLearningProvider } from './ml';
import { SecurityServiceProvider, SpacesServiceProvider } from '../../common/services';

View file

@ -1,88 +0,0 @@
/*
* 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 { FtrProviderContext } from '../ftr_provider_context';
export function MachineLearningProvider({ getService, getPageObjects }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['common']);
return {
async navigateTo() {
return await PageObjects.common.navigateToApp('ml');
},
async navigateToJobManagement() {
await testSubjects.click('mlTabJobManagement');
await testSubjects.exists('mlPageJobManagement');
},
async navigateToAnomalyExplorert() {
await testSubjects.click('mlTabAnomalyExplorer');
await testSubjects.exists('mlPageAnomalyExplorer');
},
async navigateToSingleMetricViewer() {
await testSubjects.click('mlTabSingleMetricViewer');
await testSubjects.exists('mlPageSingleMetricViewer');
},
async navigateToDataFrames() {
await testSubjects.click('mlTabDataFrames');
await testSubjects.exists('mlPageDataFrame');
},
async navigateToDataVisualizer() {
await testSubjects.click('mlTabDataVisualizer');
await testSubjects.exists('mlPageDataVisualizerSelector');
},
async navigateToSettings() {
await testSubjects.click('mlTabSettings');
await testSubjects.exists('mlPageSettings');
},
async assertJobTableExists() {
await testSubjects.existOrFail('mlJobListTable');
},
async assertCreateNewJobButtonExists() {
await testSubjects.existOrFail('mlCreateNewJobButton');
},
async assertJobStatsBarExists() {
await testSubjects.existOrFail('mlJobStatsBar');
},
async assertAnomalyExplorerEmptyListMessageExists() {
await testSubjects.existOrFail('mlNoJobsFound');
},
async assertSingleMetricViewerEmptyListMessageExsist() {
await testSubjects.existOrFail('mlNoSingleMetricJobsFound');
},
async assertDataFrameEmptyListMessageExists() {
await testSubjects.existOrFail('mlNoDataFrameTransformsFound');
},
async assertDataVisualizerImportDataCardExists() {
await testSubjects.existOrFail('mlDataVisualizerCardImportData');
},
async assertDataVisualizerIndexDataCardExists() {
await testSubjects.existOrFail('mlDataVisualizerCardIndexData');
},
async assertSettingsCalendarLinkExists() {
await testSubjects.existOrFail('ml_calendar_mng_button');
},
async assertSettingsFilterlistLinkExists() {
await testSubjects.existOrFail('ml_filter_lists_button');
},
};
}

View file

@ -0,0 +1,17 @@
/*
* 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 { FtrProviderContext } from '../../ftr_provider_context';
export function MachineLearningAnomalyExplorerProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
return {
async assertAnomalyExplorerEmptyListMessageExists() {
await testSubjects.existOrFail('mlNoJobsFound');
},
};
}

View file

@ -0,0 +1,47 @@
/*
* 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 { isEmpty } from 'lodash';
import { FtrProviderContext } from '../../ftr_provider_context';
export function MachineLearningAPIProvider({ getService }: FtrProviderContext) {
const es = getService('es');
const log = getService('log');
const retry = getService('retry');
return {
async deleteIndices(indices: string) {
log.debug(`Deleting indices: '${indices}'...`);
const deleteResponse = await es.indices.delete({
index: indices,
});
expect(deleteResponse)
.to.have.property('acknowledged')
.eql(true);
await retry.waitForWithTimeout(`'${indices}' indices to be deleted`, 30 * 1000, async () => {
const getRepsonse = await es.indices.get({
index: indices,
});
if (isEmpty(getRepsonse)) {
return true;
} else {
throw new Error(`expected indices '${indices}' to be deleted`);
}
});
},
async cleanMlIndices() {
await this.deleteIndices('.ml-*');
},
async cleanDataframeIndices() {
await this.deleteIndices('.data-frame-*');
},
};
}

View file

@ -0,0 +1,17 @@
/*
* 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 { FtrProviderContext } from '../../ftr_provider_context';
export function MachineLearningDataFramesProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
return {
async assertDataFrameEmptyListMessageExists() {
await testSubjects.existOrFail('mlNoDataFrameTransformsFound');
},
};
}

View file

@ -0,0 +1,21 @@
/*
* 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 { FtrProviderContext } from '../../ftr_provider_context';
export function MachineLearningDataVisualizerProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
return {
async assertDataVisualizerImportDataCardExists() {
await testSubjects.existOrFail('mlDataVisualizerCardImportData');
},
async assertDataVisualizerIndexDataCardExists() {
await testSubjects.existOrFail('mlDataVisualizerCardIndexData');
},
};
}

View file

@ -0,0 +1,17 @@
/*
* 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.
*/
export { MachineLearningAnomalyExplorerProvider } from './anomaly_explorer';
export { MachineLearningAPIProvider } from './api';
export { MachineLearningDataFramesProvider } from './data_frames';
export { MachineLearningDataVisualizerProvider } from './data_visualizer';
export { MachineLearningJobManagementProvider } from './job_management';
export { MachineLearningJobSourceSelectionProvider } from './job_source_selection';
export { MachineLearningJobTypeSelectionProvider } from './job_type_selection';
export { MachineLearningJobWizardCommonProvider } from './job_wizard_common';
export { MachineLearningNavigationProvider } from './navigation';
export { MachineLearningSettingsProvider } from './settings';
export { MachineLearningSingleMetricViewerProvider } from './single_metric_viewer';

View file

@ -0,0 +1,74 @@
/*
* 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 { FtrProviderContext } from '../../ftr_provider_context';
import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
export function MachineLearningJobManagementProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const retry = getService('retry');
return {
async getJobsTable(): Promise<WebElementWrapper> {
const tableContainer = await testSubjects.find('mlJobListTable');
return await tableContainer.findByTagName('table');
},
async isJobsTableLoadingIndicatorDisplayed(): Promise<boolean> {
const mlJobListTable = await testSubjects.find('mlJobListTable');
const innerText = await mlJobListTable.getVisibleText();
return innerText.includes('Loading jobs...');
},
async isNoItemsFoundMessageDisplayed(): Promise<boolean> {
const mlJobListTable = await testSubjects.find('mlJobListTable');
const innerText = await mlJobListTable.getVisibleText();
return innerText.includes('No jobs found');
},
async navigateToNewJobSourceSelection() {
await testSubjects.clickWhenNotDisabled('mlCreateNewJobButton');
await testSubjects.existOrFail('mlPageSourceSelection');
},
async assertJobTableExists() {
await testSubjects.existOrFail('mlJobListTable');
},
async assertCreateNewJobButtonExists() {
await testSubjects.existOrFail('mlCreateNewJobButton');
},
async assertJobStatsBarExists() {
await testSubjects.existOrFail('mlJobStatsBar');
},
async waitForJobsTableToLoad() {
await retry.waitFor(
'jobs table to exist',
async () => await testSubjects.exists('mlJobListTable')
);
await retry.waitFor(
'jobs table loading indicator to be invisible',
async () => (await this.isJobsTableLoadingIndicatorDisplayed()) === false
);
},
async filterJobsTable(jobId: string) {
await this.waitForJobsTableToLoad();
const searchBar = await testSubjects.find('mlJobListSearchBar');
const searchBarInput = await searchBar.findByTagName('input');
await searchBarInput.clearValueWithKeyboard();
await searchBarInput.type(jobId);
},
async getJobRowByJobId(jobId: string): Promise<WebElementWrapper> {
const table = await this.getJobsTable();
return await table.findByCssSelector(`[data-row-id=${jobId}]`);
},
};
}

View file

@ -0,0 +1,19 @@
/*
* 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 { FtrProviderContext } from '../../ftr_provider_context';
export function MachineLearningJobSourceSelectionProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
return {
async selectSourceIndexPattern(indexPattern: string) {
const subj = 'paginatedListItem-' + indexPattern;
await testSubjects.clickWhenNotDisabled(subj);
await testSubjects.existOrFail('mlPageJobTypeSelection');
},
};
}

View file

@ -0,0 +1,18 @@
/*
* 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 { FtrProviderContext } from '../../ftr_provider_context';
export function MachineLearningJobTypeSelectionProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
return {
async selectSingleMetricJob() {
await testSubjects.clickWhenNotDisabled('mlJobTypeLinkSingleMetricJob');
await testSubjects.existOrFail('mlPageJobWizard');
},
};
}

View file

@ -0,0 +1,202 @@
/*
* 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';
export function MachineLearningJobWizardCommonProvider({ getService }: FtrProviderContext) {
const comboBox = getService('comboBox');
const retry = getService('retry');
const testSubjects = getService('testSubjects');
return {
async assertTimeRangeSectionExists() {
await testSubjects.existOrFail('mlJobWizardStepTitleTimeRange');
},
async assertPickFieldsSectionExists() {
await testSubjects.existOrFail('mlJobWizardStepTitlePickFields');
},
async assertJobDetailsSectionExists() {
await testSubjects.existOrFail('mlJobWizardStepTitleJobDetails');
},
async assertValidationSectionExists() {
await testSubjects.existOrFail('mlJobWizardStepTitleValidation');
},
async assertSummarySectionExists() {
await testSubjects.existOrFail('mlJobWizardStepTitleSummary');
},
async assertEventRateChartExists() {
await testSubjects.existOrFail('mlEventRateChart');
},
async assertAggAndFieldInputExists() {
await testSubjects.existOrFail('mlJobWizardAggSelection comboBoxInput');
},
async assertAggAndFieldSelection(identifier: string) {
const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
'mlJobWizardAggSelection comboBoxInput'
);
expect(comboBoxSelectedOptions.length).to.eql(1);
expect(comboBoxSelectedOptions[0]).to.eql(identifier);
},
async assertBucketSpanInputExists() {
await testSubjects.existOrFail('mlJobWizardInputBucketSpan');
},
async assertBucketSpanValue(expectedValue: string) {
const actualBucketSpan = await testSubjects.getAttribute(
'mlJobWizardInputBucketSpan',
'value'
);
expect(actualBucketSpan).to.eql(expectedValue);
},
async assertJobIdInputExists() {
await testSubjects.existOrFail('mlJobWizardInputJobId');
},
async assertJobIdValue(expectedValue: string) {
const actualJobId = await testSubjects.getAttribute('mlJobWizardInputJobId', 'value');
expect(actualJobId).to.eql(expectedValue);
},
async assertJobDescriptionInputExists() {
await testSubjects.existOrFail('mlJobWizardInputJobDescription');
},
async assertJobDescriptionValue(expectedValue: string) {
const actualJobDescription = await testSubjects.getVisibleText(
'mlJobWizardInputJobDescription'
);
expect(actualJobDescription).to.eql(expectedValue);
},
async assertJobGroupInputExists() {
await testSubjects.existOrFail('mlJobWizardComboBoxJobGroups comboBoxInput');
},
async assertJobGroupSelection(jobGroups: string[]) {
const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
'mlJobWizardComboBoxJobGroups comboBoxInput'
);
expect(comboBoxSelectedOptions.length).to.eql(jobGroups.length);
expect(comboBoxSelectedOptions).to.eql(jobGroups);
},
async assertModelPlotSwitchExists() {
await this.ensureAdvancedSectionOpen();
await testSubjects.existOrFail('mlJobWizardAdvancedSection mlJobWizardSwitchModelPlot', {
allowHidden: true,
});
},
async assertDedicatedIndexSwitchExists() {
await this.ensureAdvancedSectionOpen();
await testSubjects.existOrFail(
'mlJobWizardAdvancedSection mlJobWizardSwitchUseDedicatedIndex',
{ allowHidden: true }
);
},
async assertDedicatedIndexSwitchCheckedState(expectedValue: boolean) {
await this.ensureAdvancedSectionOpen();
const actualCheckedState = await this.getDedicatedIndexSwitchCheckedState();
expect(actualCheckedState).to.eql(expectedValue);
},
async assertCreateJobButtonExists() {
await testSubjects.existOrFail('mlJobWizardButtonCreateJob');
},
async getDedicatedIndexSwitchCheckedState() {
await this.ensureAdvancedSectionOpen();
return await testSubjects.isSelected(
'mlJobWizardAdvancedSection mlJobWizardSwitchUseDedicatedIndex'
);
},
async assertModelMemoryLimitInputExists() {
await this.ensureAdvancedSectionOpen();
await testSubjects.existOrFail('mlJobWizardAdvancedSection mlJobWizardInputModelMemoryLimit');
},
async assertModelMemoryLimitValue(expectedValue: string) {
await this.ensureAdvancedSectionOpen();
const actualModelMemoryLimit = await testSubjects.getAttribute(
'mlJobWizardAdvancedSection mlJobWizardInputModelMemoryLimit',
'value'
);
expect(actualModelMemoryLimit).to.eql(expectedValue);
},
async clickNextButton() {
await testSubjects.clickWhenNotDisabled('mlJobWizardNavButtonNext');
},
async clickPreviousButton() {
await testSubjects.clickWhenNotDisabled('mlJobWizardNavButtonPrevious');
},
async clickUseFullDataButton() {
await testSubjects.clickWhenNotDisabled('mlButtonUseFullData');
},
async selectAggAndField(identifier: string) {
await comboBox.set('mlJobWizardAggSelection comboBoxInput', identifier);
},
async setBucketSpan(bucketSpan: string) {
await testSubjects.setValue('mlJobWizardInputBucketSpan', bucketSpan);
},
async setJobId(jobId: string) {
await testSubjects.setValue('mlJobWizardInputJobId', jobId);
},
async setJobDescription(jobDescription: string) {
await testSubjects.setValue('mlJobWizardInputJobDescription', jobDescription);
},
async addJobGroup(jobGroup: string) {
await comboBox.setCustom('mlJobWizardComboBoxJobGroups comboBoxInput', jobGroup);
},
async ensureAdvancedSectionOpen() {
await retry.try(async () => {
if ((await testSubjects.exists('mlJobWizardAdvancedSection')) === false) {
await testSubjects.click('mlJobWizardToggleAdvancedSection');
await testSubjects.existOrFail('mlJobWizardAdvancedSection');
}
});
},
async activateDedicatedIndexSwitch() {
if ((await this.getDedicatedIndexSwitchCheckedState()) === false) {
await testSubjects.clickWhenNotDisabled('mlJobWizardSwitchUseDedicatedIndex');
}
},
async setModelMemoryLimit(modelMemoryLimit: string) {
await testSubjects.setValue('mlJobWizardInputModelMemoryLimit', modelMemoryLimit);
},
async createJobAndWaitForCompletion() {
await testSubjects.clickWhenNotDisabled('mlJobWizardButtonCreateJob');
await retry.waitForWithTimeout(
'job processing to finish',
5 * 60 * 1000,
async () => (await testSubjects.exists('mlJobWizardButtonCreateJob')) === false
);
},
};
}

View file

@ -0,0 +1,55 @@
/*
* 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 { FtrProviderContext } from '../../ftr_provider_context';
export function MachineLearningNavigationProvider({
getService,
getPageObjects,
}: FtrProviderContext) {
const retry = getService('retry');
const testSubjects = getService('testSubjects');
const PageObjects = getPageObjects(['common']);
return {
async navigateToMl() {
return await PageObjects.common.navigateToApp('ml');
},
async navigateToArea(linkSubject: string, pageSubject: string) {
await retry.try(async () => {
if ((await testSubjects.exists(pageSubject)) === false) {
await testSubjects.click(linkSubject);
await testSubjects.existOrFail(pageSubject);
}
});
},
async navigateToJobManagement() {
await this.navigateToArea('mlTabJobManagement', 'mlPageJobManagement');
},
async navigateToAnomalyExplorert() {
await this.navigateToArea('mlTabAnomalyExplorer', 'mlPageAnomalyExplorer');
},
async navigateToSingleMetricViewer() {
await this.navigateToArea('mlTabSingleMetricViewer', 'mlPageSingleMetricViewer');
},
async navigateToDataFrames() {
await this.navigateToArea('mlTabDataFrames', 'mlPageDataFrame');
},
async navigateToDataVisualizer() {
await this.navigateToArea('mlTabDataVisualizer', 'mlPageDataVisualizerSelector');
},
async navigateToSettings() {
await this.navigateToArea('mlTabSettings', 'mlPageSettings');
},
};
}

View file

@ -0,0 +1,21 @@
/*
* 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 { FtrProviderContext } from '../../ftr_provider_context';
export function MachineLearningSettingsProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
return {
async assertSettingsCalendarLinkExists() {
await testSubjects.existOrFail('ml_calendar_mng_button');
},
async assertSettingsFilterlistLinkExists() {
await testSubjects.existOrFail('ml_filter_lists_button');
},
};
}

View file

@ -0,0 +1,17 @@
/*
* 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 { FtrProviderContext } from '../../ftr_provider_context';
export function MachineLearningSingleMetricViewerProvider({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
return {
async assertSingleMetricViewerEmptyListMessageExsist() {
await testSubjects.existOrFail('mlNoSingleMetricJobsFound');
},
};
}

View file

@ -0,0 +1,49 @@
/*
* 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 { FtrProviderContext } from '../ftr_provider_context';
import {
MachineLearningAnomalyExplorerProvider,
MachineLearningAPIProvider,
MachineLearningDataFramesProvider,
MachineLearningDataVisualizerProvider,
MachineLearningJobManagementProvider,
MachineLearningJobSourceSelectionProvider,
MachineLearningJobTypeSelectionProvider,
MachineLearningJobWizardCommonProvider,
MachineLearningNavigationProvider,
MachineLearningSettingsProvider,
MachineLearningSingleMetricViewerProvider,
} from './machine_learning';
export function MachineLearningProvider(context: FtrProviderContext) {
const anomalyExplorer = MachineLearningAnomalyExplorerProvider(context);
const api = MachineLearningAPIProvider(context);
const dataFrames = MachineLearningDataFramesProvider(context);
const dataVisualizer = MachineLearningDataVisualizerProvider(context);
const jobManagement = MachineLearningJobManagementProvider(context);
const jobSourceSelection = MachineLearningJobSourceSelectionProvider(context);
const jobTypeSelection = MachineLearningJobTypeSelectionProvider(context);
const jobWizardCommon = MachineLearningJobWizardCommonProvider(context);
const navigation = MachineLearningNavigationProvider(context);
const settings = MachineLearningSettingsProvider(context);
const singleMetricViewer = MachineLearningSingleMetricViewerProvider(context);
return {
anomalyExplorer,
api,
dataFrames,
dataVisualizer,
jobManagement,
jobSourceSelection,
jobTypeSelection,
jobWizardCommon,
navigation,
settings,
singleMetricViewer,
};
}