Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] [ML] Add multi metric job wizard test (#45279) #45394

Merged
merged 1 commit into from
Sep 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const AnomalyChart: FC<Props> = ({
const data = chartType === CHART_TYPE.SCATTER ? flattenData(chartData) : chartData;
const xDomain = getXRange(data);
return (
<div style={{ width, height }} data-test-subj="mlAnomalyChart">
<div style={{ width, height }} data-test-subj={`mlAnomalyChart ${CHART_TYPE[chartType]}`}>
<LoadingWrapper height={height} hasData={data.length > 0} loading={loading}>
<Chart>
<Settings xDomain={xDomain} tooltip={TooltipType.None} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ export const DetectorTitle: FC<DetectorTitleProps> = ({
return (
<EuiFlexGroup gutterSize="s" justifyContent="spaceBetween">
<EuiFlexItem>
<span style={{ fontSize: 'small' }}>{getTitle(agg, field, splitField)}</span>
<span style={{ fontSize: 'small' }} data-test-subj="detectorTitle">
{getTitle(agg, field, splitField)}
</span>
</EuiFlexItem>

{children !== false && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const ChartGrid: FC<ChartGridProps> = ({
>
<EuiFlexGrid columns={chartSettings.cols}>
{aggFieldPairList.map((af, i) => (
<EuiFlexItem key={i}>
<EuiFlexItem key={i} data-test-subj={`detector ${i}`}>
<Fragment>
<DetectorTitle
index={i}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ export const MultiMetricSettings: FC<Props> = ({ setIsValid }) => {
return (
<Fragment>
<EuiFlexGroup gutterSize="xl">
<EuiFlexItem>
<EuiFlexItem data-test-subj="mlJobWizardSplitFieldSelection">
<SplitFieldSelector />
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexItem data-test-subj="mlJobWizardInfluencerSelection">
<Influencers />
</EuiFlexItem>
</EuiFlexGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,13 @@ export const SplitCards: FC<Props> = memo(
};
return (
<div key={fieldName} ref={ref => storePanels(ref, marginBottom)} style={style}>
<EuiPanel paddingSize="m" style={{ paddingTop: '4px' }}>
<div style={{ fontWeight: 'bold', fontSize: 'small' }}>{fieldName}</div>
<EuiPanel paddingSize="m" style={{ paddingTop: '4px' }} data-test-subj="splitCard back">
<div
style={{ fontWeight: 'bold', fontSize: 'small' }}
data-test-subj="splitCardTitle"
>
{fieldName}
</div>
</EuiPanel>
</div>
);
Expand All @@ -80,13 +85,16 @@ export const SplitCards: FC<Props> = memo(

return (
<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexItem data-test-subj="dataSplit">
{(fieldValues.length === 0 || numberOfDetectors === 0) && <Fragment>{children}</Fragment>}
{fieldValues.length > 0 && numberOfDetectors > 0 && splitField !== null && (
<Fragment>
{jobType === JOB_TYPE.MULTI_METRIC && (
<Fragment>
<div style={{ fontSize: 'small' }}>
<div
style={{ fontSize: 'small' }}
data-test-subj={`dataSplitTitle ${splitField.name}`}
>
<FormattedMessage
id="xpack.ml.newJob.wizard.pickFieldsStep.splitCards.dataSplitBy"
defaultMessage="Data split by {field}"
Expand All @@ -98,8 +106,17 @@ export const SplitCards: FC<Props> = memo(
)}

{getBackPanels()}
<EuiPanel paddingSize="m" style={{ paddingTop: '4px' }}>
<div style={{ fontWeight: 'bold', fontSize: 'small' }}>{fieldValues[0]}</div>
<EuiPanel
paddingSize="m"
style={{ paddingTop: '4px' }}
data-test-subj="splitCard front"
>
<div
style={{ fontWeight: 'bold', fontSize: 'small' }}
data-test-subj="splitCardTitle"
>
{fieldValues[0]}
</div>
<EuiHorizontalRule margin="s" />
{children}
</EuiPanel>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
/*
* 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_multi_1_${Date.now()}`;
const jobDescription =
'Create multi metric job based on the farequote dataset with 15m bucketspan and min/max/mean(responsetime) split by airline';
const jobGroups = ['automated', 'farequote', 'multi-metric'];
const aggAndFieldIdentifiers = ['Min(responsetime)', 'Max(responsetime)', 'Mean(responsetime)'];
const splitField = 'airline';
const bucketSpan = '15m';
const memoryLimit = '20MB';

describe('multi metric job creation', function() {
this.tags(['smoke', 'mlqa']);
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.selectMultiMetricJob();
});

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 detectors and displays detector previews', async () => {
for (const [index, aggAndFieldIdentifier] of aggAndFieldIdentifiers.entries()) {
await ml.jobWizardCommon.assertAggAndFieldInputExists();
await ml.jobWizardCommon.selectAggAndField(aggAndFieldIdentifier);
await ml.jobWizardCommon.assertDetectorPreviewExists(aggAndFieldIdentifier, index, 'LINE');
}
});

it('inputs the split field and displays split cards', async () => {
await ml.jobWizardCommon.assertMultiMetricSplitFieldInputExists();
await ml.jobWizardCommon.selectMultiMetricSplitField(splitField);
await ml.jobWizardCommon.assertMultiMetricSplitFieldSelection(splitField);

await ml.jobWizardCommon.assertDetectorSplitExists(splitField);
await ml.jobWizardCommon.assertDetectorSplitFrontCardTitle('AAL');
await ml.jobWizardCommon.assertDetectorSplitNumberOfBackCards(9);

await ml.jobWizardCommon.assertInfluencerSelection([splitField]);
});

it('displays the influencer field', async () => {
await ml.jobWizardCommon.assertInfluencerInputExists();
await ml.jobWizardCommon.assertInfluencerSelection([splitField]);
});

it('inputs the bucket span', async () => {
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 () => {
await ml.jobWizardCommon.assertJobDescriptionInputExists();
await ml.jobWizardCommon.setJobDescription(jobDescription);
await ml.jobWizardCommon.assertJobDescriptionValue(jobDescription);
});

it('inputs job groups', async () => {
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 () => {
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.jobTable.waitForJobsToLoad();
await ml.jobTable.filterWithSearchString(jobId);
const rows = await ml.jobTable.parseJobTable();
expect(rows.filter(row => row.id === jobId)).to.have.length(1);
});

it('displays details for the created job in the job list', async () => {
const expectedRow = {
id: jobId,
description: jobDescription,
jobGroups,
recordCount: '86,274',
memoryStatus: 'ok',
jobState: 'closed',
datafeedState: 'stopped',
latestTimestamp: '2016-02-11 23:59:54',
};
await ml.jobTable.assertJobRowFields(jobId, expectedRow);

const expectedCounts = {
job_id: jobId,
processed_record_count: '86,274',
processed_field_count: '172,548',
input_bytes: '6.4 MB',
input_field_count: '172,548',
invalid_date_count: '0',
missing_field_count: '0',
out_of_order_timestamp_count: '0',
empty_bucket_count: '0',
sparse_bucket_count: '0',
bucket_count: '479',
earliest_record_timestamp: '2016-02-07 00:00:00',
latest_record_timestamp: '2016-02-11 23:59:54',
input_record_count: '86,274',
latest_bucket_timestamp: '2016-02-11 23:45:00',
};
const expectedModelSizeStats = {
job_id: jobId,
result_type: 'model_size_stats',
model_bytes: '1.8 MB',
model_bytes_exceeded: '0',
model_bytes_memory_limit: '20971520',
total_by_field_count: '59',
total_over_field_count: '0',
total_partition_field_count: '58',
bucket_allocation_failures_count: '0',
memory_status: 'ok',
timestamp: '2016-02-11 23:30:00',
};
await ml.jobTable.assertJobRowDetailsCounts(jobId, expectedCounts, expectedModelSizeStats);
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,7 @@ export default function({ getService }: FtrProviderContext) {
});

it('displays details for the created job in the job list', async () => {
const rows = await ml.jobTable.parseJobTable();
const job = rows.filter(row => row.id === jobId)[0];
expect(job).to.eql({
const expectedRow = {
id: jobId,
description: jobDescription,
jobGroups,
Expand All @@ -157,18 +155,10 @@ export default function({ getService }: FtrProviderContext) {
jobState: 'closed',
datafeedState: 'stopped',
latestTimestamp: '2016-02-11 23:56:59',
});

const countDetails = await ml.jobTable.parseJobCounts(jobId);
const counts = countDetails.counts;

// last_data_time holds a runtime timestamp and is hard to predict
// the property is only validated to be present and then removed
// so it doesn't make the counts object validation fail
expect(counts).to.have.property('last_data_time');
delete counts.last_data_time;
};
await ml.jobTable.assertJobRowFields(jobId, expectedRow);

expect(counts).to.eql({
const expectedCounts = {
job_id: jobId,
processed_record_count: '2,399',
processed_field_count: '4,798',
Expand All @@ -184,17 +174,8 @@ export default function({ getService }: FtrProviderContext) {
latest_record_timestamp: '2016-02-11 23:56:59',
input_record_count: '2,399',
latest_bucket_timestamp: '2016-02-11 23:30:00',
});

const modelSizeStats = countDetails.modelSizeStats;

// log_time holds a runtime timestamp and is hard to predict
// the property is only validated to be present and then removed
// so it doesn't make the modelSizeStats object validation fail
expect(modelSizeStats).to.have.property('log_time');
delete modelSizeStats.log_time;

expect(modelSizeStats).to.eql({
};
const expectedModelSizeStats = {
job_id: jobId,
result_type: 'model_size_stats',
model_bytes: '47.6 KB',
Expand All @@ -206,7 +187,8 @@ export default function({ getService }: FtrProviderContext) {
bucket_allocation_failures_count: '0',
memory_status: 'ok',
timestamp: '2016-02-11 23:00:00',
});
};
await ml.jobTable.assertJobRowDetailsCounts(jobId, expectedCounts, expectedModelSizeStats);
});
});
}
1 change: 1 addition & 0 deletions x-pack/test/functional/apps/machine_learning/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export default function({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./feature_controls'));
loadTestFile(require.resolve('./pages'));
loadTestFile(require.resolve('./create_single_metric_job'));
loadTestFile(require.resolve('./create_multi_metric_job'));
});
}
Loading