mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[ML] Job import and export functional tests (#110578)
* [ML] Job import export functional tests * adding title check * adding dfa tests * removing export file * adds bad data test * commented code * adding export job tests * adds version to file names * improving tests * removing comment Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
d83c8244a2
commit
71571c5b60
11 changed files with 1043 additions and 72 deletions
|
@ -63,6 +63,7 @@ export const ExportJobsFlyout: FC<Props> = ({ isDisabled, currentTab }) => {
|
|||
const [exporting, setExporting] = useState(false);
|
||||
const [selectedJobType, setSelectedJobType] = useState<JobType>(currentTab);
|
||||
const [switchTabConfirmVisible, setSwitchTabConfirmVisible] = useState(false);
|
||||
const [switchTabNextTab, setSwitchTabNextTab] = useState<JobType>(currentTab);
|
||||
const { displayErrorToast, displaySuccessToast } = useMemo(
|
||||
() => toastNotificationServiceProvider(toasts),
|
||||
[toasts]
|
||||
|
@ -170,16 +171,23 @@ export const ExportJobsFlyout: FC<Props> = ({ isDisabled, currentTab }) => {
|
|||
}
|
||||
}
|
||||
|
||||
const attemptTabSwitch = useCallback(() => {
|
||||
// if the user has already selected some jobs, open a confirm modal
|
||||
// rather than changing tabs
|
||||
if (selectedJobIds.length > 0) {
|
||||
setSwitchTabConfirmVisible(true);
|
||||
return;
|
||||
}
|
||||
const attemptTabSwitch = useCallback(
|
||||
(jobType: JobType) => {
|
||||
if (jobType === selectedJobType) {
|
||||
return;
|
||||
}
|
||||
// if the user has already selected some jobs, open a confirm modal
|
||||
// rather than changing tabs
|
||||
if (selectedJobIds.length > 0) {
|
||||
setSwitchTabNextTab(jobType);
|
||||
setSwitchTabConfirmVisible(true);
|
||||
return;
|
||||
}
|
||||
|
||||
switchTab();
|
||||
}, [selectedJobIds]);
|
||||
switchTab(jobType);
|
||||
},
|
||||
[selectedJobIds]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedJobDependencies(
|
||||
|
@ -187,10 +195,7 @@ export const ExportJobsFlyout: FC<Props> = ({ isDisabled, currentTab }) => {
|
|||
);
|
||||
}, [selectedJobIds]);
|
||||
|
||||
function switchTab() {
|
||||
const jobType =
|
||||
selectedJobType === 'anomaly-detector' ? 'data-frame-analytics' : 'anomaly-detector';
|
||||
|
||||
function switchTab(jobType: JobType) {
|
||||
setSwitchTabConfirmVisible(false);
|
||||
setSelectedJobIds([]);
|
||||
setSelectedJobType(jobType);
|
||||
|
@ -211,7 +216,12 @@ export const ExportJobsFlyout: FC<Props> = ({ isDisabled, currentTab }) => {
|
|||
|
||||
{showFlyout === true && isDisabled === false && (
|
||||
<>
|
||||
<EuiFlyout onClose={() => setShowFlyout(false)} hideCloseButton size="s">
|
||||
<EuiFlyout
|
||||
onClose={() => setShowFlyout(false)}
|
||||
hideCloseButton
|
||||
size="s"
|
||||
data-test-subj="mlJobMgmtExportJobsFlyout"
|
||||
>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<h2>
|
||||
|
@ -227,8 +237,9 @@ export const ExportJobsFlyout: FC<Props> = ({ isDisabled, currentTab }) => {
|
|||
<EuiTabs size="s">
|
||||
<EuiTab
|
||||
isSelected={selectedJobType === 'anomaly-detector'}
|
||||
onClick={attemptTabSwitch}
|
||||
onClick={() => attemptTabSwitch('anomaly-detector')}
|
||||
disabled={exporting}
|
||||
data-test-subj="mlJobMgmtExportJobsADTab"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.importExport.exportFlyout.adTab"
|
||||
|
@ -237,8 +248,9 @@ export const ExportJobsFlyout: FC<Props> = ({ isDisabled, currentTab }) => {
|
|||
</EuiTab>
|
||||
<EuiTab
|
||||
isSelected={selectedJobType === 'data-frame-analytics'}
|
||||
onClick={attemptTabSwitch}
|
||||
onClick={() => attemptTabSwitch('data-frame-analytics')}
|
||||
disabled={exporting}
|
||||
data-test-subj="mlJobMgmtExportJobsDFATab"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.importExport.exportFlyout.dfaTab"
|
||||
|
@ -254,26 +266,40 @@ export const ExportJobsFlyout: FC<Props> = ({ isDisabled, currentTab }) => {
|
|||
<LoadingSpinner />
|
||||
) : (
|
||||
<>
|
||||
<EuiButtonEmpty size="xs" onClick={onSelectAll} isDisabled={isDisabled}>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.importExport.exportFlyout.adSelectAllButton"
|
||||
defaultMessage="Select all"
|
||||
/>
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
onClick={onSelectAll}
|
||||
isDisabled={isDisabled}
|
||||
data-test-subj="mlJobMgmtExportJobsSelectAllButton"
|
||||
>
|
||||
{selectedJobIds.length === adJobIds.length ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ml.importExport.exportFlyout.adDeselectAllButton"
|
||||
defaultMessage="Deselect all"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.ml.importExport.exportFlyout.adSelectAllButton"
|
||||
defaultMessage="Select all"
|
||||
/>
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
|
||||
<EuiSpacer size="xs" />
|
||||
|
||||
{adJobIds.map((id) => (
|
||||
<div key={id}>
|
||||
<EuiCheckbox
|
||||
id={id}
|
||||
label={id}
|
||||
checked={selectedJobIds.includes(id)}
|
||||
onChange={(e) => toggleSelectedJob(e.target.checked, id)}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
</div>
|
||||
))}
|
||||
<div data-test-subj="mlJobMgmtExportJobsADJobList">
|
||||
{adJobIds.map((id) => (
|
||||
<div key={id}>
|
||||
<EuiCheckbox
|
||||
id={id}
|
||||
label={id}
|
||||
checked={selectedJobIds.includes(id)}
|
||||
onChange={(e) => toggleSelectedJob(e.target.checked, id)}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
@ -284,26 +310,39 @@ export const ExportJobsFlyout: FC<Props> = ({ isDisabled, currentTab }) => {
|
|||
<LoadingSpinner />
|
||||
) : (
|
||||
<>
|
||||
<EuiButtonEmpty size="xs" onClick={onSelectAll} isDisabled={isDisabled}>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.importExport.exportFlyout.dfaSelectAllButton"
|
||||
defaultMessage="Select all"
|
||||
/>
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
onClick={onSelectAll}
|
||||
isDisabled={isDisabled}
|
||||
data-test-subj="mlJobMgmtExportJobsSelectAllButton"
|
||||
>
|
||||
{selectedJobIds.length === dfaJobIds.length ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ml.importExport.exportFlyout.dfaDeselectAllButton"
|
||||
defaultMessage="Deselect all"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.ml.importExport.exportFlyout.dfaSelectAllButton"
|
||||
defaultMessage="Select all"
|
||||
/>
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
|
||||
<EuiSpacer size="xs" />
|
||||
|
||||
{dfaJobIds.map((id) => (
|
||||
<div key={id}>
|
||||
<EuiCheckbox
|
||||
id={id}
|
||||
label={id}
|
||||
checked={selectedJobIds.includes(id)}
|
||||
onChange={(e) => toggleSelectedJob(e.target.checked, id)}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
</div>
|
||||
))}
|
||||
<div data-test-subj="mlJobMgmtExportJobsDFAJobList">
|
||||
{dfaJobIds.map((id) => (
|
||||
<div key={id}>
|
||||
<EuiCheckbox
|
||||
id={id}
|
||||
label={id}
|
||||
checked={selectedJobIds.includes(id)}
|
||||
onChange={(e) => toggleSelectedJob(e.target.checked, id)}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
@ -329,6 +368,7 @@ export const ExportJobsFlyout: FC<Props> = ({ isDisabled, currentTab }) => {
|
|||
disabled={selectedJobIds.length === 0 || exporting === true}
|
||||
onClick={onExport}
|
||||
fill
|
||||
data-test-subj="mlJobMgmtExportExportButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.importExport.exportFlyout.exportButton"
|
||||
|
@ -343,7 +383,7 @@ export const ExportJobsFlyout: FC<Props> = ({ isDisabled, currentTab }) => {
|
|||
{switchTabConfirmVisible === true ? (
|
||||
<SwitchTabsConfirm
|
||||
onCancel={setSwitchTabConfirmVisible.bind(null, false)}
|
||||
onConfirm={switchTab}
|
||||
onConfirm={() => switchTab(switchTabNextTab)}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
|
|
|
@ -30,6 +30,7 @@ export const CannotImportJobsCallout: FC<Props> = ({ jobs, autoExpand = false })
|
|||
values: { num: jobs.length },
|
||||
})}
|
||||
color="warning"
|
||||
data-test-subj="mlJobMgmtImportJobsCannotBeImportedCallout"
|
||||
>
|
||||
{autoExpand ? (
|
||||
<SkippedJobList jobs={jobs} />
|
||||
|
|
|
@ -21,10 +21,12 @@ export const CannotReadFileCallout: FC = () => {
|
|||
})}
|
||||
color="warning"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.importExport.importFlyout.cannotReadFileCallout.body"
|
||||
defaultMessage="Please select a file contained Machine Learning jobs which have been exported from Kibana using the Export Jobs option"
|
||||
/>
|
||||
<div data-test-subj="mlJobMgmtImportJobsFileReadErrorCallout">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.importExport.importFlyout.cannotReadFileCallout.body"
|
||||
defaultMessage="Please select a file contained Machine Learning jobs which have been exported from Kibana using the Export Jobs option"
|
||||
/>
|
||||
</div>
|
||||
</EuiCallOut>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -341,7 +341,12 @@ export const ImportJobsFlyout: FC<Props> = ({ isDisabled }) => {
|
|||
<FlyoutButton onClick={toggleFlyout} isDisabled={isDisabled} />
|
||||
|
||||
{showFlyout === true && isDisabled === false && (
|
||||
<EuiFlyout onClose={setShowFlyout.bind(null, false)} hideCloseButton size="m">
|
||||
<EuiFlyout
|
||||
onClose={setShowFlyout.bind(null, false)}
|
||||
hideCloseButton
|
||||
size="m"
|
||||
data-test-subj="mlJobMgmtImportJobsFlyout"
|
||||
>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="m">
|
||||
<h2>
|
||||
|
@ -373,22 +378,26 @@ export const ImportJobsFlyout: FC<Props> = ({ isDisabled }) => {
|
|||
{showFileReadError ? <CannotReadFileCallout /> : null}
|
||||
|
||||
{totalJobsRead > 0 && jobType !== null && (
|
||||
<>
|
||||
<div data-test-subj="mlJobMgmtImportJobsFileRead">
|
||||
<EuiSpacer size="l" />
|
||||
{jobType === 'anomaly-detector' && (
|
||||
<FormattedMessage
|
||||
id="xpack.ml.importExport.importFlyout.selectedFiles.ad"
|
||||
defaultMessage="{num} anomaly detection {num, plural, one {job} other {jobs}} read from file"
|
||||
values={{ num: totalJobsRead }}
|
||||
/>
|
||||
<div data-test-subj="mlJobMgmtImportJobsADTitle">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.importExport.importFlyout.selectedFiles.ad"
|
||||
defaultMessage="{num} anomaly detection {num, plural, one {job} other {jobs}} read from file"
|
||||
values={{ num: totalJobsRead }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{jobType === 'data-frame-analytics' && (
|
||||
<FormattedMessage
|
||||
id="xpack.ml.importExport.importFlyout.selectedFiles.dfa"
|
||||
defaultMessage="{num} data frame analytics {num, plural, one {job} other {jobs}} read from file"
|
||||
values={{ num: totalJobsRead }}
|
||||
/>
|
||||
<div data-test-subj="mlJobMgmtImportJobsDFATitle">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.importExport.importFlyout.selectedFiles.dfa"
|
||||
defaultMessage="{num} data frame analytics {num, plural, one {job} other {jobs}} read from file"
|
||||
values={{ num: totalJobsRead }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
@ -426,6 +435,7 @@ export const ImportJobsFlyout: FC<Props> = ({ isDisabled }) => {
|
|||
value={jobId.jobId}
|
||||
onChange={(e) => renameJob(e.target.value, i)}
|
||||
isInvalid={jobId.jobIdValid === false}
|
||||
data-test-subj="mlJobMgmtImportJobIdInput"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
|
@ -465,7 +475,7 @@ export const ImportJobsFlyout: FC<Props> = ({ isDisabled }) => {
|
|||
<EuiSpacer size="m" />
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</EuiFlyoutBody>
|
||||
|
@ -484,7 +494,12 @@ export const ImportJobsFlyout: FC<Props> = ({ isDisabled }) => {
|
|||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton disabled={importDisabled} onClick={onImport} fill>
|
||||
<EuiButton
|
||||
disabled={importDisabled}
|
||||
onClick={onImport}
|
||||
fill
|
||||
data-test-subj="mlJobMgmtImportImportButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.importExport.importFlyout.closeButton.importButton"
|
||||
defaultMessage="Import"
|
||||
|
|
|
@ -0,0 +1,314 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { Job, Datafeed } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs';
|
||||
import type { DataFrameAnalyticsConfig } from '../../../../../plugins/ml/public/application/data_frame_analytics/common';
|
||||
|
||||
const testADJobs: Array<{ job: Job; datafeed: Datafeed }> = [
|
||||
{
|
||||
// @ts-expect-error not full interface
|
||||
job: {
|
||||
job_id: 'fq_single_1_smv',
|
||||
groups: ['farequote', 'automated', 'single-metric'],
|
||||
description: 'mean(responsetime) on farequote dataset with 15m bucket span',
|
||||
analysis_config: {
|
||||
bucket_span: '15m',
|
||||
detectors: [
|
||||
{
|
||||
detector_description: 'mean(responsetime)',
|
||||
function: 'mean',
|
||||
field_name: 'responsetime',
|
||||
},
|
||||
],
|
||||
influencers: [],
|
||||
},
|
||||
analysis_limits: {
|
||||
model_memory_limit: '10mb',
|
||||
categorization_examples_limit: 4,
|
||||
},
|
||||
data_description: {
|
||||
time_field: '@timestamp',
|
||||
time_format: 'epoch_ms',
|
||||
},
|
||||
model_plot_config: {
|
||||
enabled: true,
|
||||
annotations_enabled: true,
|
||||
},
|
||||
model_snapshot_retention_days: 10,
|
||||
daily_model_snapshot_retention_after_days: 1,
|
||||
results_index_name: 'shared',
|
||||
allow_lazy_open: false,
|
||||
},
|
||||
datafeed: {
|
||||
datafeed_id: 'datafeed-fq_single_1_smv',
|
||||
job_id: 'fq_single_1_smv',
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
match_all: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
indices: ['ft_farequote'],
|
||||
scroll_size: 1000,
|
||||
delayed_data_check_config: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// @ts-expect-error not full interface
|
||||
job: {
|
||||
job_id: 'fq_single_2_smv',
|
||||
groups: ['farequote', 'automated', 'single-metric'],
|
||||
description: 'low_mean(responsetime) on farequote dataset with 15m bucket span',
|
||||
analysis_config: {
|
||||
bucket_span: '15m',
|
||||
detectors: [
|
||||
{
|
||||
detector_description: 'low_mean(responsetime)',
|
||||
function: 'low_mean',
|
||||
field_name: 'responsetime',
|
||||
},
|
||||
],
|
||||
influencers: ['responsetime'],
|
||||
},
|
||||
analysis_limits: {
|
||||
model_memory_limit: '11mb',
|
||||
categorization_examples_limit: 4,
|
||||
},
|
||||
data_description: {
|
||||
time_field: '@timestamp',
|
||||
time_format: 'epoch_ms',
|
||||
},
|
||||
model_plot_config: {
|
||||
enabled: true,
|
||||
annotations_enabled: true,
|
||||
},
|
||||
model_snapshot_retention_days: 10,
|
||||
daily_model_snapshot_retention_after_days: 1,
|
||||
results_index_name: 'shared',
|
||||
allow_lazy_open: false,
|
||||
},
|
||||
datafeed: {
|
||||
datafeed_id: 'datafeed-fq_single_2_smv',
|
||||
job_id: 'fq_single_2_smv',
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
match_all: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
indices: ['ft_farequote'],
|
||||
scroll_size: 1000,
|
||||
delayed_data_check_config: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// @ts-expect-error not full interface
|
||||
job: {
|
||||
job_id: 'fq_single_3_smv',
|
||||
groups: ['farequote', 'automated', 'single-metric'],
|
||||
description: 'high_mean(responsetime) on farequote dataset with 15m bucket span',
|
||||
analysis_config: {
|
||||
bucket_span: '15m',
|
||||
detectors: [
|
||||
{
|
||||
detector_description: 'high_mean(responsetime)',
|
||||
function: 'high_mean',
|
||||
field_name: 'responsetime',
|
||||
},
|
||||
],
|
||||
influencers: ['responsetime'],
|
||||
},
|
||||
analysis_limits: {
|
||||
model_memory_limit: '11mb',
|
||||
categorization_examples_limit: 4,
|
||||
},
|
||||
data_description: {
|
||||
time_field: '@timestamp',
|
||||
time_format: 'epoch_ms',
|
||||
},
|
||||
model_plot_config: {
|
||||
enabled: true,
|
||||
annotations_enabled: true,
|
||||
},
|
||||
model_snapshot_retention_days: 10,
|
||||
daily_model_snapshot_retention_after_days: 1,
|
||||
results_index_name: 'shared',
|
||||
allow_lazy_open: false,
|
||||
},
|
||||
datafeed: {
|
||||
datafeed_id: 'datafeed-fq_single_3_smv',
|
||||
job_id: 'fq_single_3_smv',
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
match_all: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
indices: ['ft_farequote'],
|
||||
scroll_size: 1000,
|
||||
delayed_data_check_config: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const testDFAJobs: DataFrameAnalyticsConfig[] = [
|
||||
// @ts-expect-error not full interface
|
||||
{
|
||||
id: `bm_1_1`,
|
||||
description:
|
||||
"Classification job based on 'ft_bank_marketing' dataset with dependentVariable 'y' and trainingPercent '20'",
|
||||
source: {
|
||||
index: ['ft_bank_marketing'],
|
||||
query: {
|
||||
match_all: {},
|
||||
},
|
||||
},
|
||||
dest: {
|
||||
index: 'user-bm_1_1',
|
||||
results_field: 'ml',
|
||||
},
|
||||
analysis: {
|
||||
classification: {
|
||||
prediction_field_name: 'test',
|
||||
dependent_variable: 'y',
|
||||
training_percent: 20,
|
||||
},
|
||||
},
|
||||
analyzed_fields: {
|
||||
includes: [],
|
||||
excludes: [],
|
||||
},
|
||||
model_memory_limit: '60mb',
|
||||
allow_lazy_start: false,
|
||||
},
|
||||
// @ts-expect-error not full interface
|
||||
{
|
||||
id: `ihp_1_2`,
|
||||
description: 'This is the job description',
|
||||
source: {
|
||||
index: ['ft_ihp_outlier'],
|
||||
query: {
|
||||
match_all: {},
|
||||
},
|
||||
},
|
||||
dest: {
|
||||
index: 'user-ihp_1_2',
|
||||
results_field: 'ml',
|
||||
},
|
||||
analysis: {
|
||||
outlier_detection: {},
|
||||
},
|
||||
analyzed_fields: {
|
||||
includes: [],
|
||||
excludes: [],
|
||||
},
|
||||
model_memory_limit: '5mb',
|
||||
},
|
||||
// @ts-expect-error not full interface
|
||||
{
|
||||
id: `egs_1_3`,
|
||||
description: 'This is the job description',
|
||||
source: {
|
||||
index: ['ft_egs_regression'],
|
||||
query: {
|
||||
match_all: {},
|
||||
},
|
||||
},
|
||||
dest: {
|
||||
index: 'user-egs_1_3',
|
||||
results_field: 'ml',
|
||||
},
|
||||
analysis: {
|
||||
regression: {
|
||||
prediction_field_name: 'test',
|
||||
dependent_variable: 'stab',
|
||||
training_percent: 20,
|
||||
},
|
||||
},
|
||||
analyzed_fields: {
|
||||
includes: [],
|
||||
excludes: [],
|
||||
},
|
||||
model_memory_limit: '20mb',
|
||||
},
|
||||
];
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const ml = getService('ml');
|
||||
|
||||
describe('export jobs', function () {
|
||||
this.tags(['mlqa']);
|
||||
before(async () => {
|
||||
await ml.api.cleanMlIndices();
|
||||
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
|
||||
await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp');
|
||||
|
||||
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/bm_classification');
|
||||
await ml.testResources.createIndexPatternIfNeeded('ft_bank_marketing', '@timestamp');
|
||||
|
||||
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/ihp_outlier');
|
||||
await ml.testResources.createIndexPatternIfNeeded('ft_ihp_outlier', '@timestamp');
|
||||
|
||||
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/egs_regression');
|
||||
await ml.testResources.createIndexPatternIfNeeded('ft_egs_regression', '@timestamp');
|
||||
|
||||
await ml.testResources.setKibanaTimeZoneToUTC();
|
||||
|
||||
for (const { job, datafeed } of testADJobs) {
|
||||
await ml.api.createAnomalyDetectionJob(job);
|
||||
await ml.api.createDatafeed(datafeed);
|
||||
}
|
||||
for (const job of testDFAJobs) {
|
||||
await ml.api.createDataFrameAnalyticsJob(job);
|
||||
}
|
||||
|
||||
await ml.securityUI.loginAsMlPowerUser();
|
||||
await ml.navigation.navigateToStackManagement();
|
||||
await ml.navigation.navigateToStackManagementJobsListPage();
|
||||
});
|
||||
after(async () => {
|
||||
await ml.api.cleanMlIndices();
|
||||
ml.stackManagementJobs.deleteExportedFiles([
|
||||
'anomaly_detection_jobs',
|
||||
'data_frame_analytics_jobs',
|
||||
]);
|
||||
});
|
||||
|
||||
it('opens export flyout and exports anomaly detector jobs', async () => {
|
||||
await ml.stackManagementJobs.openExportFlyout();
|
||||
await ml.stackManagementJobs.selectExportJobType('anomaly-detector');
|
||||
await ml.stackManagementJobs.selectExportJobSelectAll('anomaly-detector');
|
||||
await ml.stackManagementJobs.selectExportJobs();
|
||||
await ml.stackManagementJobs.assertExportedADJobsAreCorrect(testADJobs);
|
||||
});
|
||||
|
||||
it('opens export flyout and exports data frame analytics jobs', async () => {
|
||||
await ml.stackManagementJobs.openExportFlyout();
|
||||
await ml.stackManagementJobs.selectExportJobType('data-frame-analytics');
|
||||
await ml.stackManagementJobs.selectExportJobSelectAll('data-frame-analytics');
|
||||
await ml.stackManagementJobs.selectExportJobs();
|
||||
await ml.stackManagementJobs.assertExportedDFAJobsAreCorrect(testDFAJobs);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
[
|
||||
{
|
||||
"job": {
|
||||
"job_id": "ad-test1",
|
||||
"description": "",
|
||||
"analysis_config": {
|
||||
"bucket_span": "15m",
|
||||
"summary_count_field_name": "doc_count",
|
||||
"detectors": [
|
||||
{
|
||||
"detector_description": "mean(responsetime)",
|
||||
"function": "mean",
|
||||
"field_name": "responsetime",
|
||||
"detector_index": 0
|
||||
}
|
||||
],
|
||||
"influencers": []
|
||||
},
|
||||
"analysis_limits": {
|
||||
"model_memory_limit": "11mb",
|
||||
"categorization_examples_limit": 4
|
||||
},
|
||||
"data_description": {
|
||||
"time_field": "@timestamp",
|
||||
"time_format": "epoch_ms"
|
||||
},
|
||||
"model_plot_config": {
|
||||
"enabled": true,
|
||||
"annotations_enabled": true
|
||||
},
|
||||
"model_snapshot_retention_days": 10,
|
||||
"daily_model_snapshot_retention_after_days": 1,
|
||||
"results_index_name": "shared",
|
||||
"allow_lazy_open": false
|
||||
},
|
||||
"datafeed": {
|
||||
"datafeed_id": "datafeed-ad-test1",
|
||||
"job_id": "ad-test1",
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{
|
||||
"match_all": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"indices": [
|
||||
"ft_farequote"
|
||||
],
|
||||
"aggregations": {
|
||||
"buckets": {
|
||||
"date_histogram": {
|
||||
"field": "@timestamp",
|
||||
"fixed_interval": "90000ms"
|
||||
},
|
||||
"aggregations": {
|
||||
"responsetime": {
|
||||
"avg": {
|
||||
"field": "responsetime"
|
||||
}
|
||||
},
|
||||
"@timestamp": {
|
||||
"max": {
|
||||
"field": "@timestamp"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"scroll_size": 1000,
|
||||
"delayed_data_check_config": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"job": {
|
||||
"job_id": "ad-test2",
|
||||
"groups": [
|
||||
"newgroup"
|
||||
],
|
||||
"description": "",
|
||||
"analysis_config": {
|
||||
"bucket_span": "15m",
|
||||
"summary_count_field_name": "doc_count",
|
||||
"detectors": [
|
||||
{
|
||||
"detector_description": "mean(responsetime)",
|
||||
"function": "mean",
|
||||
"field_name": "responsetime",
|
||||
"detector_index": 0
|
||||
}
|
||||
],
|
||||
"influencers": []
|
||||
},
|
||||
"analysis_limits": {
|
||||
"model_memory_limit": "11mb",
|
||||
"categorization_examples_limit": 4
|
||||
},
|
||||
"data_description": {
|
||||
"time_field": "@timestamp",
|
||||
"time_format": "epoch_ms"
|
||||
},
|
||||
"model_plot_config": {
|
||||
"enabled": true,
|
||||
"annotations_enabled": true
|
||||
},
|
||||
"model_snapshot_retention_days": 10,
|
||||
"daily_model_snapshot_retention_after_days": 1,
|
||||
"results_index_name": "shared",
|
||||
"allow_lazy_open": false
|
||||
},
|
||||
"datafeed": {
|
||||
"datafeed_id": "datafeed-ad-test2",
|
||||
"job_id": "ad-test2",
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{
|
||||
"match_all": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"indices": [
|
||||
"missing"
|
||||
],
|
||||
"aggregations": {
|
||||
"buckets": {
|
||||
"date_histogram": {
|
||||
"field": "@timestamp",
|
||||
"fixed_interval": "90000ms"
|
||||
},
|
||||
"aggregations": {
|
||||
"responsetime": {
|
||||
"avg": {
|
||||
"field": "responsetime"
|
||||
}
|
||||
},
|
||||
"@timestamp": {
|
||||
"max": {
|
||||
"field": "@timestamp"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"scroll_size": 1000,
|
||||
"delayed_data_check_config": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"job": {
|
||||
"job_id": "ad-test3",
|
||||
"custom_settings": {},
|
||||
"description": "",
|
||||
"analysis_config": {
|
||||
"bucket_span": "15m",
|
||||
"detectors": [
|
||||
{
|
||||
"detector_description": "mean(responsetime) partitionfield=airline",
|
||||
"function": "mean",
|
||||
"field_name": "responsetime",
|
||||
"partition_field_name": "airline",
|
||||
"detector_index": 0
|
||||
}
|
||||
],
|
||||
"influencers": [
|
||||
"airline"
|
||||
]
|
||||
},
|
||||
"analysis_limits": {
|
||||
"model_memory_limit": "11mb",
|
||||
"categorization_examples_limit": 4
|
||||
},
|
||||
"data_description": {
|
||||
"time_field": "@timestamp",
|
||||
"time_format": "epoch_ms"
|
||||
},
|
||||
"model_plot_config": {
|
||||
"enabled": false,
|
||||
"annotations_enabled": false
|
||||
},
|
||||
"model_snapshot_retention_days": 10,
|
||||
"daily_model_snapshot_retention_after_days": 1,
|
||||
"results_index_name": "shared",
|
||||
"allow_lazy_open": false
|
||||
},
|
||||
"datafeed": {
|
||||
"datafeed_id": "datafeed-ad-test3",
|
||||
"job_id": "ad-test3",
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{
|
||||
"match_all": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"indices": [
|
||||
"ft_farequote"
|
||||
],
|
||||
"scroll_size": 1000,
|
||||
"delayed_data_check_config": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
|
@ -0,0 +1 @@
|
|||
Hey! this isn't JSON.
|
|
@ -0,0 +1,60 @@
|
|||
[
|
||||
{
|
||||
"id": "dfa-test1",
|
||||
"description": "Classification job based on 'ft_bank_marketing' dataset with dependentVariable 'y' and trainingPercent '20'",
|
||||
"source": {
|
||||
"index": [
|
||||
"ft_bank_marketing"
|
||||
],
|
||||
"query": {
|
||||
"match_all": {}
|
||||
}
|
||||
},
|
||||
"dest": {
|
||||
"index": "user-dfa-test1",
|
||||
"results_field": "ml"
|
||||
},
|
||||
"analysis": {
|
||||
"classification": {
|
||||
"prediction_field_name": "user-test",
|
||||
"dependent_variable": "y",
|
||||
"training_percent": 20
|
||||
}
|
||||
},
|
||||
"analyzed_fields": {
|
||||
"includes": [],
|
||||
"excludes": []
|
||||
},
|
||||
"model_memory_limit": "60mb",
|
||||
"allow_lazy_start": false
|
||||
},
|
||||
{
|
||||
"id": "dfa-test2",
|
||||
"description": "Classification job based on 'ft_bank_marketing' dataset with dependentVariable 'y' and trainingPercent '20'",
|
||||
"source": {
|
||||
"index": [
|
||||
"missing-index"
|
||||
],
|
||||
"query": {
|
||||
"match_all": {}
|
||||
}
|
||||
},
|
||||
"dest": {
|
||||
"index": "user-dfa-test2",
|
||||
"results_field": "ml"
|
||||
},
|
||||
"analysis": {
|
||||
"classification": {
|
||||
"prediction_field_name": "test",
|
||||
"dependent_variable": "y",
|
||||
"training_percent": 20
|
||||
}
|
||||
},
|
||||
"analyzed_fields": {
|
||||
"includes": [],
|
||||
"excludes": []
|
||||
},
|
||||
"model_memory_limit": "60mb",
|
||||
"allow_lazy_start": false
|
||||
}
|
||||
]
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { JobType } from '../../../../../plugins/ml/common/types/saved_objects';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const ml = getService('ml');
|
||||
const testDataListPositive = [
|
||||
{
|
||||
filePath: path.join(__dirname, 'files_to_import', 'anomaly_detection_jobs_7.16.json'),
|
||||
expected: {
|
||||
jobType: 'anomaly-detector' as JobType,
|
||||
jobIds: ['ad-test1', 'ad-test3'],
|
||||
skippedJobIds: ['ad-test2'],
|
||||
},
|
||||
},
|
||||
{
|
||||
filePath: path.join(__dirname, 'files_to_import', 'data_frame_analytics_jobs_7.16.json'),
|
||||
expected: {
|
||||
jobType: 'data-frame-analytics' as JobType,
|
||||
jobIds: ['dfa-test1'],
|
||||
skippedJobIds: ['dfa-test2'],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('import jobs', function () {
|
||||
this.tags(['mlqa']);
|
||||
before(async () => {
|
||||
await ml.api.cleanMlIndices();
|
||||
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote');
|
||||
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/bm_classification');
|
||||
await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp');
|
||||
await ml.testResources.createIndexPatternIfNeeded('ft_bank_marketing', '@timestamp');
|
||||
await ml.testResources.setKibanaTimeZoneToUTC();
|
||||
|
||||
await ml.securityUI.loginAsMlPowerUser();
|
||||
await ml.navigation.navigateToStackManagement();
|
||||
await ml.navigation.navigateToStackManagementJobsListPage();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await ml.api.cleanMlIndices();
|
||||
});
|
||||
|
||||
for (const testData of testDataListPositive) {
|
||||
it('selects and reads file', async () => {
|
||||
await ml.testExecution.logTestStep('selects job import');
|
||||
await ml.stackManagementJobs.openImportFlyout();
|
||||
await ml.stackManagementJobs.selectFileToImport(testData.filePath);
|
||||
});
|
||||
it('has the correct importable jobs', async () => {
|
||||
await ml.stackManagementJobs.assertCorrectTitle(
|
||||
[...testData.expected.jobIds, ...testData.expected.skippedJobIds].length,
|
||||
testData.expected.jobType
|
||||
);
|
||||
await ml.stackManagementJobs.assertJobIdsExist(testData.expected.jobIds);
|
||||
await ml.stackManagementJobs.assertJobIdsSkipped(testData.expected.skippedJobIds);
|
||||
});
|
||||
|
||||
it('imports jobs', async () => {
|
||||
await ml.stackManagementJobs.importJobs();
|
||||
});
|
||||
|
||||
it('ensures jobs have been imported', async () => {
|
||||
if (testData.expected.jobType === 'anomaly-detector') {
|
||||
await ml.navigation.navigateToStackManagementJobsListPageAnomalyDetectionTab();
|
||||
await ml.jobTable.refreshJobList();
|
||||
for (const id of testData.expected.jobIds) {
|
||||
await ml.jobTable.filterWithSearchString(id);
|
||||
}
|
||||
for (const id of testData.expected.skippedJobIds) {
|
||||
await ml.jobTable.filterWithSearchString(id, 0);
|
||||
}
|
||||
} else {
|
||||
await ml.navigation.navigateToStackManagementJobsListPageAnalyticsTab();
|
||||
await ml.dataFrameAnalyticsTable.refreshAnalyticsTable();
|
||||
for (const id of testData.expected.jobIds) {
|
||||
await ml.dataFrameAnalyticsTable.assertAnalyticsJobDisplayedInTable(id, true);
|
||||
}
|
||||
for (const id of testData.expected.skippedJobIds) {
|
||||
await ml.dataFrameAnalyticsTable.assertAnalyticsJobDisplayedInTable(id, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe('correctly fails to import bad data', async () => {
|
||||
it('selects and reads file', async () => {
|
||||
await ml.testExecution.logTestStep('selects job import');
|
||||
await ml.stackManagementJobs.openImportFlyout();
|
||||
await ml.stackManagementJobs.selectFileToImport(
|
||||
path.join(__dirname, 'files_to_import', 'bad_data.json'),
|
||||
true
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -13,5 +13,7 @@ export default function ({ loadTestFile }: FtrProviderContext) {
|
|||
|
||||
loadTestFile(require.resolve('./synchronize'));
|
||||
loadTestFile(require.resolve('./manage_spaces'));
|
||||
loadTestFile(require.resolve('./import_jobs'));
|
||||
loadTestFile(require.resolve('./export_jobs'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,10 +6,16 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { REPO_ROOT } from '@kbn/utils';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { MlADJobTable } from './job_table';
|
||||
import { MlDFAJobTable } from './data_frame_analytics_table';
|
||||
import type { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import type { MlADJobTable } from './job_table';
|
||||
import type { MlDFAJobTable } from './data_frame_analytics_table';
|
||||
import type { JobType } from '../../../../plugins/ml/common/types/saved_objects';
|
||||
import type { Job, Datafeed } from '../../../../plugins/ml/common/types/anomaly_detection_jobs';
|
||||
import type { DataFrameAnalyticsConfig } from '../../../../plugins/ml/public/application/data_frame_analytics/common';
|
||||
|
||||
type SyncFlyoutObjectType =
|
||||
| 'MissingObjects'
|
||||
|
@ -18,7 +24,7 @@ type SyncFlyoutObjectType =
|
|||
| 'ObjectsUnmatchedDatafeed';
|
||||
|
||||
export function MachineLearningStackManagementJobsProvider(
|
||||
{ getService }: FtrProviderContext,
|
||||
{ getService, getPageObjects }: FtrProviderContext,
|
||||
mlADJobTable: MlADJobTable,
|
||||
mlDFAJobTable: MlDFAJobTable
|
||||
) {
|
||||
|
@ -26,6 +32,9 @@ export function MachineLearningStackManagementJobsProvider(
|
|||
const retry = getService('retry');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const toasts = getService('toasts');
|
||||
const log = getService('log');
|
||||
|
||||
const PageObjects = getPageObjects(['common']);
|
||||
|
||||
return {
|
||||
async openSyncFlyout() {
|
||||
|
@ -194,5 +203,212 @@ export function MachineLearningStackManagementJobsProvider(
|
|||
}
|
||||
await this.assertSpaceSelectionRowSelected(spaceId, shouldSelect);
|
||||
},
|
||||
|
||||
async openImportFlyout() {
|
||||
await retry.tryForTime(5000, async () => {
|
||||
await testSubjects.click('mlJobsImportButton', 1000);
|
||||
await testSubjects.existOrFail('mlJobMgmtImportJobsFlyout');
|
||||
});
|
||||
},
|
||||
|
||||
async openExportFlyout() {
|
||||
await retry.tryForTime(5000, async () => {
|
||||
await testSubjects.click('mlJobsExportButton', 1000);
|
||||
await testSubjects.existOrFail('mlJobMgmtExportJobsFlyout');
|
||||
});
|
||||
},
|
||||
|
||||
async selectFileToImport(filePath: string, expectError: boolean = false) {
|
||||
log.debug(`Importing file '${filePath}' ...`);
|
||||
await PageObjects.common.setFileInputPath(filePath);
|
||||
|
||||
if (expectError) {
|
||||
await testSubjects.existOrFail('~mlJobMgmtImportJobsFileReadErrorCallout');
|
||||
} else {
|
||||
await testSubjects.missingOrFail('~mlJobMgmtImportJobsFileReadErrorCallout');
|
||||
await testSubjects.existOrFail('mlJobMgmtImportJobsFileRead');
|
||||
}
|
||||
},
|
||||
|
||||
async assertJobIdsExist(expectedJobIds: string[]) {
|
||||
const inputs = await testSubjects.findAll('mlJobMgmtImportJobIdInput');
|
||||
const actualJobIds = await Promise.all(inputs.map((i) => i.getAttribute('value')));
|
||||
|
||||
expect(actualJobIds.sort()).to.eql(
|
||||
expectedJobIds.sort(),
|
||||
`Expected job ids to be '${JSON.stringify(expectedJobIds)}' (got '${JSON.stringify(
|
||||
actualJobIds
|
||||
)}')`
|
||||
);
|
||||
},
|
||||
|
||||
async assertCorrectTitle(jobCount: number, jobType: JobType) {
|
||||
const dataTestSubj =
|
||||
jobType === 'anomaly-detector'
|
||||
? 'mlJobMgmtImportJobsADTitle'
|
||||
: 'mlJobMgmtImportJobsDFATitle';
|
||||
const subj = await testSubjects.find(dataTestSubj);
|
||||
const title = (await subj.parseDomContent()).html();
|
||||
|
||||
const jobTypeString =
|
||||
jobType === 'anomaly-detector' ? 'anomaly detection' : 'data frame analytics';
|
||||
|
||||
const results = title.match(
|
||||
/(\d) (anomaly detection|data frame analytics) job[s]? read from file$/
|
||||
);
|
||||
expect(results).to.not.eql(null, `Expected regex results to not be null`);
|
||||
const foundCount = results![1];
|
||||
const foundJobTypeString = results![2];
|
||||
expect(foundCount).to.eql(
|
||||
jobCount,
|
||||
`Expected job count to be '${jobCount}' (got '${foundCount}')`
|
||||
);
|
||||
expect(foundJobTypeString).to.eql(
|
||||
jobTypeString,
|
||||
`Expected job count to be '${jobTypeString}' (got '${foundJobTypeString}')`
|
||||
);
|
||||
},
|
||||
|
||||
async assertJobIdsSkipped(expectedJobIds: string[]) {
|
||||
const subj = await testSubjects.find('mlJobMgmtImportJobsCannotBeImportedCallout');
|
||||
const skippedJobTitles = await subj.findAllByTagName('h5');
|
||||
const actualJobIds = (
|
||||
await Promise.all(skippedJobTitles.map((i) => i.parseDomContent()))
|
||||
).map((t) => t.html());
|
||||
|
||||
expect(actualJobIds.sort()).to.eql(
|
||||
expectedJobIds.sort(),
|
||||
`Expected job ids to be '${JSON.stringify(expectedJobIds)}' (got '${JSON.stringify(
|
||||
actualJobIds
|
||||
)}')`
|
||||
);
|
||||
},
|
||||
|
||||
async importJobs() {
|
||||
await testSubjects.click('mlJobMgmtImportImportButton', 1000);
|
||||
await testSubjects.missingOrFail('mlJobMgmtImportJobsFlyout', { timeout: 60 * 1000 });
|
||||
},
|
||||
|
||||
async assertReadErrorCalloutExists() {
|
||||
await testSubjects.existOrFail('~mlJobMgmtImportJobsFileReadErrorCallout');
|
||||
},
|
||||
|
||||
async selectExportJobType(jobType: JobType) {
|
||||
if (jobType === 'anomaly-detector') {
|
||||
await testSubjects.click('mlJobMgmtExportJobsADTab');
|
||||
await testSubjects.existOrFail('mlJobMgmtExportJobsADJobList');
|
||||
} else {
|
||||
await testSubjects.click('mlJobMgmtExportJobsDFATab');
|
||||
await testSubjects.existOrFail('mlJobMgmtExportJobsDFAJobList');
|
||||
}
|
||||
},
|
||||
|
||||
async selectExportJobSelectAll(jobType: JobType) {
|
||||
await testSubjects.click('mlJobMgmtExportJobsSelectAllButton');
|
||||
const subjLabel =
|
||||
jobType === 'anomaly-detector'
|
||||
? 'mlJobMgmtExportJobsADJobList'
|
||||
: 'mlJobMgmtExportJobsDFAJobList';
|
||||
const subj = await testSubjects.find(subjLabel);
|
||||
const inputs = await subj.findAllByTagName('input');
|
||||
const allInputValues = await Promise.all(inputs.map((input) => input.getAttribute('value')));
|
||||
expect(allInputValues.every((i) => i === 'on')).to.eql(
|
||||
true,
|
||||
`Expected all inputs to be checked`
|
||||
);
|
||||
},
|
||||
|
||||
async getDownload(filePath: string) {
|
||||
return retry.tryForTime(5000, async () => {
|
||||
expect(fs.existsSync(filePath)).to.be(true);
|
||||
return fs.readFileSync(filePath).toString();
|
||||
});
|
||||
},
|
||||
|
||||
getExportedFile(fileName: string) {
|
||||
return path.resolve(REPO_ROOT, `target/functional-tests/downloads/${fileName}.json`);
|
||||
},
|
||||
|
||||
deleteExportedFiles(fileNames: string[]) {
|
||||
fileNames.forEach((file) => {
|
||||
try {
|
||||
fs.unlinkSync(this.getExportedFile(file));
|
||||
} catch (e) {
|
||||
// it might not have been there to begin with
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async selectExportJobs() {
|
||||
await testSubjects.click('mlJobMgmtExportExportButton');
|
||||
await testSubjects.missingOrFail('mlJobMgmtExportJobsFlyout', { timeout: 60 * 1000 });
|
||||
},
|
||||
|
||||
async assertExportedADJobsAreCorrect(expectedJobs: Array<{ job: Job; datafeed: Datafeed }>) {
|
||||
const file = JSON.parse(
|
||||
await this.getDownload(this.getExportedFile('anomaly_detection_jobs'))
|
||||
);
|
||||
const loadedFile = Array.isArray(file) ? file : [file];
|
||||
const sortedActualJobs = loadedFile.sort((a, b) => a.job.job_id.localeCompare(b.job.job_id));
|
||||
|
||||
const sortedExpectedJobs = expectedJobs.sort((a, b) =>
|
||||
a.job.job_id.localeCompare(b.job.job_id)
|
||||
);
|
||||
expect(sortedActualJobs.length).to.eql(
|
||||
sortedExpectedJobs.length,
|
||||
`Expected length of exported jobs to be '${sortedExpectedJobs.length}' (got '${sortedActualJobs.length}')`
|
||||
);
|
||||
|
||||
sortedExpectedJobs.forEach((expectedJob, i) => {
|
||||
expect(sortedActualJobs[i].job.job_id).to.eql(
|
||||
expectedJob.job.job_id,
|
||||
`Expected job id to be '${expectedJob.job.job_id}' (got '${sortedActualJobs[i].job.job_id}')`
|
||||
);
|
||||
expect(sortedActualJobs[i].job.analysis_config.detectors.length).to.eql(
|
||||
expectedJob.job.analysis_config.detectors.length,
|
||||
`Expected detectors length to be '${expectedJob.job.analysis_config.detectors.length}' (got '${sortedActualJobs[i].job.analysis_config.detectors.length}')`
|
||||
);
|
||||
expect(sortedActualJobs[i].job.analysis_config.detectors[0].function).to.eql(
|
||||
expectedJob.job.analysis_config.detectors[0].function,
|
||||
`Expected first detector function to be '${expectedJob.job.analysis_config.detectors[0].function}' (got '${sortedActualJobs[i].job.analysis_config.detectors[0].function}')`
|
||||
);
|
||||
expect(sortedActualJobs[i].datafeed.datafeed_id).to.eql(
|
||||
expectedJob.datafeed.datafeed_id,
|
||||
`Expected job id to be '${expectedJob.datafeed.datafeed_id}' (got '${sortedActualJobs[i].datafeed.datafeed_id}')`
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
async assertExportedDFAJobsAreCorrect(expectedJobs: DataFrameAnalyticsConfig[]) {
|
||||
const file = JSON.parse(
|
||||
await this.getDownload(this.getExportedFile('data_frame_analytics_jobs'))
|
||||
);
|
||||
const loadedFile = Array.isArray(file) ? file : [file];
|
||||
const sortedActualJobs = loadedFile.sort((a, b) => a.id.localeCompare(b.id));
|
||||
|
||||
const sortedExpectedJobs = expectedJobs.sort((a, b) => a.id.localeCompare(b.id));
|
||||
|
||||
expect(sortedActualJobs.length).to.eql(
|
||||
sortedExpectedJobs.length,
|
||||
`Expected length of exported jobs to be '${sortedExpectedJobs.length}' (got '${sortedActualJobs.length}')`
|
||||
);
|
||||
|
||||
sortedExpectedJobs.forEach((expectedJob, i) => {
|
||||
expect(sortedActualJobs[i].id).to.eql(
|
||||
expectedJob.id,
|
||||
`Expected job id to be '${expectedJob.id}' (got '${sortedActualJobs[i].id}')`
|
||||
);
|
||||
const expectedType = Object.keys(expectedJob.analysis)[0];
|
||||
const actualType = Object.keys(sortedActualJobs[i].analysis)[0];
|
||||
expect(actualType).to.eql(
|
||||
expectedType,
|
||||
`Expected job type to be '${expectedType}' (got '${actualType}')`
|
||||
);
|
||||
expect(sortedActualJobs[i].dest.index).to.eql(
|
||||
expectedJob.dest.index,
|
||||
`Expected destination index to be '${expectedJob.dest.index}' (got '${sortedActualJobs[i].dest.index}')`
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue