mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ML] Allow temporary data views in AD job wizards (#170112)
When creating a brand new job, temporary data views can be created and
used in the wizard.
When cloning a job where the data view cannot be found, a new temporary
data view is created to be used in the wizard.
This can happen if the data view used to create the original job has
been deleted or the job was created with a temporary data view.
2b9c2125
-2b0c-449d-a226-82267f64567b
Also overrides the animation for the expanded rows in the AD jobs list
which can cause strange behaviour when changing tabs in the expanded
row.
---------
Co-authored-by: Quynh Nguyen (Quinn) <43350163+qn895@users.noreply.github.com>
This commit is contained in:
parent
1fa5d60cca
commit
a509a3abf8
11 changed files with 60 additions and 41 deletions
|
@ -197,7 +197,7 @@ export const LinksMenuUI = (props: LinksMenuProps) => {
|
|||
const getDataViewId = async () => {
|
||||
const index = job.datafeed_config.indices[0];
|
||||
|
||||
const dataViewId = await getDataViewIdFromName(index);
|
||||
const dataViewId = await getDataViewIdFromName(index, job);
|
||||
|
||||
// If data view doesn't exist for some reasons
|
||||
if (!dataViewId && !unmounted) {
|
||||
|
|
|
@ -399,6 +399,7 @@ export class JobsList extends Component {
|
|||
rowProps={(item) => ({
|
||||
'data-test-subj': `mlJobListRow row-${item.id}`,
|
||||
})}
|
||||
css={{ '.euiTableRow-isExpandedRow .euiTableCellContent': { animation: 'none' } }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ import {
|
|||
import { getApplication, getToastNotifications } from '../../../util/dependency_cache';
|
||||
import { ml } from '../../../services/ml_api_service';
|
||||
import { stringMatch } from '../../../util/string_utils';
|
||||
import { getDataViewNames } from '../../../util/index_utils';
|
||||
import { JOB_STATE, DATAFEED_STATE } from '../../../../../common/constants/states';
|
||||
import { JOB_ACTION } from '../../../../../common/constants/job_actions';
|
||||
import { parseInterval } from '../../../../../common/util/parse_interval';
|
||||
|
@ -222,25 +221,6 @@ export async function cloneJob(jobId) {
|
|||
loadFullJob(jobId, false),
|
||||
]);
|
||||
|
||||
const dataViewNames = await getDataViewNames();
|
||||
const dataViewTitle = datafeed.indices.join(',');
|
||||
const jobIndicesAvailable = dataViewNames.includes(dataViewTitle);
|
||||
|
||||
if (jobIndicesAvailable === false) {
|
||||
const warningText = i18n.translate(
|
||||
'xpack.ml.jobsList.managementActions.noSourceDataViewForClone',
|
||||
{
|
||||
defaultMessage:
|
||||
'Unable to clone the anomaly detection job {jobId}. No data view exists for index {dataViewTitle}.',
|
||||
values: { jobId, dataViewTitle },
|
||||
}
|
||||
);
|
||||
getToastNotificationService().displayDangerToast(warningText, {
|
||||
'data-test-subj': 'mlCloneJobNoDataViewExistsWarningToast',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const createdBy = originalJob?.custom_settings?.created_by;
|
||||
if (
|
||||
cloneableJob !== undefined &&
|
||||
|
|
|
@ -80,7 +80,7 @@ export const Page: FC<PageProps> = ({ nextStepPath }) => {
|
|||
uiSettings,
|
||||
}}
|
||||
>
|
||||
<CreateDataViewButton onDataViewCreated={onObjectSelection} />
|
||||
<CreateDataViewButton onDataViewCreated={onObjectSelection} allowAdHocDataView={true} />
|
||||
</SavedObjectFinder>
|
||||
</EuiPanel>
|
||||
</EuiPageBody>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import type { ApplicationStart } from '@kbn/core/public';
|
||||
import type { DataViewsContract } from '@kbn/data-views-plugin/public';
|
||||
import { mlJobService } from '../../../../services/job_service';
|
||||
import { Datafeed } from '../../../../../../common/types/anomaly_detection_jobs';
|
||||
import type { Job, Datafeed } from '../../../../../../common/types/anomaly_detection_jobs';
|
||||
import { CREATED_BY_LABEL, JOB_TYPE } from '../../../../../../common/constants/new_job';
|
||||
|
||||
export async function preConfiguredJobRedirect(
|
||||
|
@ -19,7 +19,7 @@ export async function preConfiguredJobRedirect(
|
|||
const { createdBy, job, datafeed } = mlJobService.tempJobCloningObjects;
|
||||
|
||||
if (job && datafeed) {
|
||||
const dataViewId = await getDataViewIdFromName(datafeed, dataViewsService);
|
||||
const dataViewId = await getDataViewIdFromDatafeed(job, datafeed, dataViewsService);
|
||||
if (dataViewId === null) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
@ -72,7 +72,8 @@ async function getWizardUrlFromCloningJob(createdBy: string | undefined, dataVie
|
|||
return `jobs/new_job/${page}?index=${dataViewId}&_g=()`;
|
||||
}
|
||||
|
||||
async function getDataViewIdFromName(
|
||||
async function getDataViewIdFromDatafeed(
|
||||
job: Job,
|
||||
datafeed: Datafeed,
|
||||
dataViewsService: DataViewsContract
|
||||
): Promise<string | null> {
|
||||
|
@ -80,9 +81,20 @@ async function getDataViewIdFromName(
|
|||
throw new Error('Data views are not initialized!');
|
||||
}
|
||||
|
||||
const [dv] = await dataViewsService?.find(datafeed.indices.join(','));
|
||||
if (!dv) {
|
||||
return null;
|
||||
const indexPattern = datafeed.indices.join(',');
|
||||
|
||||
const dataViews = await dataViewsService?.find(indexPattern);
|
||||
const dataView = dataViews.find((dv) => dv.getIndexPattern() === indexPattern);
|
||||
if (dataView === undefined) {
|
||||
// create a temporary data view if we can't find one
|
||||
// matching the index pattern
|
||||
const tempDataView = await dataViewsService.create({
|
||||
id: undefined,
|
||||
name: indexPattern,
|
||||
title: indexPattern,
|
||||
timeFieldName: job.data_description.time_field!,
|
||||
});
|
||||
return tempDataView.id ?? null;
|
||||
}
|
||||
return dv.id ?? dv.title;
|
||||
return dataView.id ?? null;
|
||||
}
|
||||
|
|
|
@ -232,11 +232,19 @@ export const Page: FC<PageProps> = ({ existingJobsAndGroups, jobType }) => {
|
|||
|
||||
<div style={{ backgroundColor: 'inherit' }} data-test-subj={`mlPageJobWizard ${jobType}`}>
|
||||
<EuiText size={'s'}>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.newJob.page.createJob.dataViewName"
|
||||
defaultMessage="Using data view {dataViewName}"
|
||||
values={{ dataViewName: jobCreator.indexPatternDisplayName }}
|
||||
/>
|
||||
{dataSourceContext.selectedDataView.isPersisted() ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ml.newJob.page.createJob.dataViewName"
|
||||
defaultMessage="Using data view {dataViewName}"
|
||||
values={{ dataViewName: jobCreator.indexPatternDisplayName }}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.ml.newJob.page.createJob.tempDataViewName"
|
||||
defaultMessage="Using temporary data view {dataViewName}"
|
||||
values={{ dataViewName: jobCreator.indexPatternDisplayName }}
|
||||
/>
|
||||
)}
|
||||
</EuiText>
|
||||
|
||||
<Wizard
|
||||
|
|
|
@ -10,6 +10,7 @@ import type { DataView } from '@kbn/data-views-plugin/public';
|
|||
import type { SavedSearch, SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public';
|
||||
import type { Query, Filter } from '@kbn/es-query';
|
||||
import type { DataViewsContract } from '@kbn/data-views-plugin/common';
|
||||
import type { Job } from '../../../common/types/anomaly_detection_jobs';
|
||||
import { getToastNotifications, getDataViews } from './dependency_cache';
|
||||
|
||||
export async function getDataViewNames() {
|
||||
|
@ -20,7 +21,14 @@ export async function getDataViewNames() {
|
|||
return (await dataViewsService.getIdsWithTitle()).map(({ title }) => title);
|
||||
}
|
||||
|
||||
export async function getDataViewIdFromName(name: string): Promise<string | null> {
|
||||
/**
|
||||
* Retrieves the data view ID from the given name.
|
||||
* If a job is passed in, a temporary data view will be created if the requested data view doesn't exist.
|
||||
* @param name - The name or index pattern of the data view.
|
||||
* @param job - Optional job object.
|
||||
* @returns The data view ID or null if it doesn't exist.
|
||||
*/
|
||||
export async function getDataViewIdFromName(name: string, job?: Job): Promise<string | null> {
|
||||
const dataViewsService = getDataViews();
|
||||
if (dataViewsService === null) {
|
||||
throw new Error('Data views are not initialized!');
|
||||
|
@ -28,6 +36,15 @@ export async function getDataViewIdFromName(name: string): Promise<string | null
|
|||
const dataViews = await dataViewsService.find(name);
|
||||
const dataView = dataViews.find((dv) => dv.getIndexPattern() === name);
|
||||
if (!dataView) {
|
||||
if (job !== undefined) {
|
||||
const tempDataView = await dataViewsService.create({
|
||||
id: undefined,
|
||||
name,
|
||||
title: name,
|
||||
timeFieldName: job.data_description.time_field!,
|
||||
});
|
||||
return tempDataView.id ?? null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return dataView.id ?? dataView.getIndexPattern();
|
||||
|
|
|
@ -22522,7 +22522,6 @@
|
|||
"xpack.ml.jobsList.jobDetails.forecastsTable.viewAriaLabel": "Afficher la prévision créée le {createdDate}",
|
||||
"xpack.ml.jobsList.jobFilterBar.invalidSearchErrorMessage": "Recherche non valide : {errorMessage}",
|
||||
"xpack.ml.jobsList.jobFilterBar.jobGroupTitle": "({jobsCount, plural, one {# tâche} many {# tâches} other {# tâches}})",
|
||||
"xpack.ml.jobsList.managementActions.noSourceDataViewForClone": "Impossible de cloner la tâche de détection des anomalies {jobId}. Il n'existe aucune vue de données pour l'index {dataViewTitle}.",
|
||||
"xpack.ml.jobsList.missingSavedObjectWarning.link": " {link}",
|
||||
"xpack.ml.jobsList.multiJobActions.groupSelector.applyGroupsToJobTitle": "Appliquer des groupes {jobsCount, plural, one {tâche} many {tâches} other {aux tâches}}",
|
||||
"xpack.ml.jobsList.multiJobsActions.closeJobsLabel": "Fermer {jobsCount, plural, one {tâche} many {tâches} other {les tâches}}",
|
||||
|
|
|
@ -22534,7 +22534,6 @@
|
|||
"xpack.ml.jobsList.jobDetails.forecastsTable.viewAriaLabel": "{createdDate}に作成された予測を表示",
|
||||
"xpack.ml.jobsList.jobFilterBar.invalidSearchErrorMessage": "無効な検索:{errorMessage}",
|
||||
"xpack.ml.jobsList.jobFilterBar.jobGroupTitle": "({jobsCount, plural, other {#個のジョブ}})",
|
||||
"xpack.ml.jobsList.managementActions.noSourceDataViewForClone": "異常検知ジョブ{jobId}を複製できません。インデックス{dataViewTitle}のデータビューは存在しません。",
|
||||
"xpack.ml.jobsList.missingSavedObjectWarning.link": " {link}",
|
||||
"xpack.ml.jobsList.multiJobActions.groupSelector.applyGroupsToJobTitle": "{jobsCount, plural, other {ジョブ}}にグループを適用",
|
||||
"xpack.ml.jobsList.multiJobsActions.closeJobsLabel": "{jobsCount, plural, other {ジョブ}}を閉じる",
|
||||
|
|
|
@ -22533,7 +22533,6 @@
|
|||
"xpack.ml.jobsList.jobDetails.forecastsTable.viewAriaLabel": "查看在 {createdDate} 创建的预测",
|
||||
"xpack.ml.jobsList.jobFilterBar.invalidSearchErrorMessage": "无效搜索:{errorMessage}",
|
||||
"xpack.ml.jobsList.jobFilterBar.jobGroupTitle": "({jobsCount, plural, other {# 个作业}})",
|
||||
"xpack.ml.jobsList.managementActions.noSourceDataViewForClone": "无法克隆异常检测作业 {jobId}。对于索引 {dataViewTitle},不存在数据视图。",
|
||||
"xpack.ml.jobsList.missingSavedObjectWarning.link": " {link}",
|
||||
"xpack.ml.jobsList.multiJobActions.groupSelector.applyGroupsToJobTitle": "将组应用到{jobsCount, plural, other {作业}}",
|
||||
"xpack.ml.jobsList.multiJobsActions.closeJobsLabel": "关闭{jobsCount, plural, other {作业}}",
|
||||
|
|
|
@ -228,7 +228,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
await ml.api.assertDetectorResultsExist(jobId, 0);
|
||||
});
|
||||
|
||||
it('job cloning fails in the single metric wizard if a matching data view does not exist', async () => {
|
||||
it('job cloning creates a temporary data view and opens the single metric wizard if a matching data view does not exist', async () => {
|
||||
await ml.testExecution.logTestStep('delete data view used by job');
|
||||
await ml.testResources.deleteIndexPatternByTitle(indexPatternString);
|
||||
|
||||
|
@ -236,15 +236,19 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
await browser.refresh();
|
||||
|
||||
await ml.testExecution.logTestStep(
|
||||
'job cloning clicks the clone action and displays an error toast'
|
||||
'job cloning clicks the clone action and loads the single metric wizard'
|
||||
);
|
||||
await ml.jobTable.clickCloneJobActionWhenNoDataViewExists(jobId);
|
||||
await ml.jobTable.clickCloneJobAction(jobId);
|
||||
await ml.jobTypeSelection.assertSingleMetricJobWizardOpen();
|
||||
});
|
||||
|
||||
it('job cloning opens the existing job in the single metric wizard', async () => {
|
||||
await ml.testExecution.logTestStep('recreate data view used by job');
|
||||
await ml.testResources.createIndexPatternIfNeeded(indexPatternString, '@timestamp');
|
||||
|
||||
await ml.navigation.navigateToMl();
|
||||
await ml.navigation.navigateToJobManagement();
|
||||
|
||||
// Refresh page to ensure page has correct cache of data views
|
||||
await browser.refresh();
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue