[ML] Job service refactor (#191724)

Follow on from dependancy cache removal
https://github.com/elastic/kibana/pull/189729

Moving and removing as much as possible out of the `JobService` class.

- Moving the job cloning functions to a separate class as they do not
require the same dependancies as `JobService`
- Removing api basic wrapper functions like `saveNewJob`, `openJob`,
`forceStartDatafeeds` etc
- Removes toast dependency. This might be controversial, but I think it
is unnecessary for these basic job/datafeed loading functions to show a
toast if they fail. If we cannot load the basic list of jobs then there
is something else very wrong with the system. We also throw the errors,
so they will not be lost. It should be up to the calling function to
display a toast. Removing this dependency cleans up the code quite a
lot.
- The `JobCreator` classes no longer use `JobService`
This commit is contained in:
James Gowdy 2024-09-10 17:58:14 +01:00 committed by GitHub
parent 08f70b7037
commit 2d40fa3311
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 417 additions and 708 deletions

View file

@ -33,7 +33,6 @@ import { withKibana } from '@kbn/kibana-react-plugin/public';
import { addItemToRecentlyAccessed } from '../../../util/recently_accessed';
import { mlJobServiceFactory } from '../../../services/job_service';
import { toastNotificationServiceProvider } from '../../../services/toast_notification_service';
import { mlTableService } from '../../../services/table_service';
import { ANNOTATIONS_TABLE_DEFAULT_QUERY_SIZE } from '../../../../../common/constants/search';
import {
@ -101,10 +100,7 @@ class AnnotationsTableUI extends Component {
this.sorting = {
sort: { field: 'timestamp', direction: 'asc' },
};
this.mlJobService = mlJobServiceFactory(
toastNotificationServiceProvider(props.kibana.services.notifications.toasts),
props.kibana.services.mlServices.mlApi
);
this.mlJobService = mlJobServiceFactory(props.kibana.services.mlServices.mlApi);
}
getAnnotations() {

View file

@ -272,7 +272,7 @@ export const LinksMenuUI = (props: LinksMenuProps) => {
if (dataView === null) {
return;
}
dataView.getIndexPattern();
const field = findMessageField(dataView);
if (field !== null) {
setMessageField(field);

View file

@ -11,7 +11,7 @@ import {
ANOMALY_DETECTION_DEFAULT_TIME_RANGE,
ANOMALY_DETECTION_ENABLE_TIME_RANGE,
} from '../../../../common/constants/settings';
import { useMlJobService } from '../../services/job_service';
import { createResultsUrlForJobs } from '../../util/results_url';
export const useCreateADLinks = () => {
const {
@ -19,13 +19,12 @@ export const useCreateADLinks = () => {
http: { basePath },
},
} = useMlKibana();
const mlJobService = useMlJobService();
const useUserTimeSettings = useUiSettings().get(ANOMALY_DETECTION_ENABLE_TIME_RANGE);
const userTimeSettings = useUiSettings().get(ANOMALY_DETECTION_DEFAULT_TIME_RANGE);
const createLinkWithUserDefaults = useCallback(
(location: any, jobList: any) => {
const resultsUrl = mlJobService.createResultsUrlForJobs(
(location, jobList) => {
const resultsUrl = createResultsUrlForJobs(
jobList,
location,
useUserTimeSettings === true && userTimeSettings !== undefined

View file

@ -7,6 +7,7 @@
import type { DataFrameAnalyticsConfig } from '@kbn/ml-data-frame-analytics-utils';
import { createDatafeedId } from '../../../../../common/util/job_utils';
import type { JobType } from '../../../../../common/types/saved_objects';
import type { Job, Datafeed } from '../../../../../common/types/anomaly_detection_jobs';
import type { Filter } from '../../../../../common/types/filters';
@ -105,7 +106,7 @@ export class JobImportService {
const { jobId } = jobIds[i];
j.job.job_id = jobId;
j.datafeed.job_id = jobId;
j.datafeed.datafeed_id = `datafeed-${jobId}`;
j.datafeed.datafeed_id = createDatafeedId(jobId);
return j;
});
}

View file

@ -81,10 +81,7 @@ class RuleEditorFlyoutUI extends Component {
this.partitioningFieldNames = [];
this.canGetFilters = checkPermission('canGetFilters');
this.mlJobService = mlJobServiceFactory(
toastNotificationServiceProvider(props.kibana.services.notifications.toasts),
props.kibana.services.mlServices.mlApi
);
this.mlJobService = mlJobServiceFactory(props.kibana.services.mlServices.mlApi);
}
componentDidMount() {

View file

@ -19,10 +19,9 @@ import {
EuiButton,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useMlKibana } from '../../../../contexts/kibana';
import { useMlApi, useMlKibana } from '../../../../contexts/kibana';
import type { MlSummaryJob } from '../../../../../../common/types/anomaly_detection_jobs';
import { isManagedJob } from '../../../jobs_utils';
import { useMlJobService } from '../../../../services/job_service';
import { closeJobs } from '../utils';
import { ManagedJobsWarningCallout } from './managed_jobs_warning_callout';
@ -44,7 +43,7 @@ export const CloseJobsConfirmModal: FC<Props> = ({
notifications: { toasts },
},
} = useMlKibana();
const mlJobService = useMlJobService();
const mlApi = useMlApi();
const [modalVisible, setModalVisible] = useState(false);
const [hasManagedJob, setHasManaged] = useState(true);
const [jobsToReset, setJobsToReset] = useState<MlSummaryJob[]>([]);
@ -121,7 +120,7 @@ export const CloseJobsConfirmModal: FC<Props> = ({
<EuiButton
onClick={() => {
closeJobs(toasts, mlJobService, jobsToReset, refreshJobs);
closeJobs(toasts, mlApi, jobsToReset, refreshJobs);
closeModal();
}}
fill

View file

@ -19,10 +19,9 @@ import {
EuiButton,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useMlKibana } from '../../../../contexts/kibana';
import { useMlApi, useMlKibana } from '../../../../contexts/kibana';
import type { MlSummaryJob } from '../../../../../../common/types/anomaly_detection_jobs';
import { isManagedJob } from '../../../jobs_utils';
import { useMlJobService } from '../../../../services/job_service';
import { stopDatafeeds } from '../utils';
import { ManagedJobsWarningCallout } from './managed_jobs_warning_callout';
@ -45,7 +44,7 @@ export const StopDatafeedsConfirmModal: FC<Props> = ({
notifications: { toasts },
},
} = useMlKibana();
const mlJobService = useMlJobService();
const mlApi = useMlApi();
const [modalVisible, setModalVisible] = useState(false);
const [hasManagedJob, setHasManaged] = useState(true);
const [jobsToStop, setJobsToStop] = useState<MlSummaryJob[]>([]);
@ -122,7 +121,7 @@ export const StopDatafeedsConfirmModal: FC<Props> = ({
<EuiButton
onClick={() => {
stopDatafeeds(toasts, mlJobService, jobsToStop, refreshJobs);
stopDatafeeds(toasts, mlApi, jobsToStop, refreshJobs);
closeModal();
}}
fill

View file

@ -23,11 +23,10 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useMlKibana } from '../../../../contexts/kibana';
import { useMlApi, useMlKibana } from '../../../../contexts/kibana';
import { deleteJobs } from '../utils';
import { BLOCKED_JOBS_REFRESH_INTERVAL_MS } from '../../../../../../common/constants/jobs_list';
import { DeleteSpaceAwareItemCheckModal } from '../../../../components/delete_space_aware_item_check_modal';
import { useMlJobService } from '../../../../services/job_service';
import type { MlSummaryJob } from '../../../../../../common/types/anomaly_detection_jobs';
import { isManagedJob } from '../../../jobs_utils';
import { ManagedJobsWarningCallout } from '../confirm_modals/managed_jobs_warning_callout';
@ -46,7 +45,7 @@ export const DeleteJobModal: FC<Props> = ({ setShowFunction, unsetShowFunction,
notifications: { toasts },
},
} = useMlKibana();
const mlJobService = useMlJobService();
const mlApi = useMlApi();
const [deleting, setDeleting] = useState(false);
const [modalVisible, setModalVisible] = useState(false);
const [adJobs, setAdJobs] = useState<MlSummaryJob[]>([]);
@ -92,7 +91,7 @@ export const DeleteJobModal: FC<Props> = ({ setShowFunction, unsetShowFunction,
setDeleting(true);
deleteJobs(
toasts,
mlJobService,
mlApi,
jobIds.map((id) => ({ id })),
deleteUserAnnotations,
deleteAlertingRules
@ -102,9 +101,7 @@ export const DeleteJobModal: FC<Props> = ({ setShowFunction, unsetShowFunction,
closeModal();
refreshJobs();
}, BLOCKED_JOBS_REFRESH_INTERVAL_MS);
// exclude mlJobservice from deps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [jobIds, deleteUserAnnotations, deleteAlertingRules, closeModal, refreshJobs]);
}, [toasts, mlApi, jobIds, deleteUserAnnotations, deleteAlertingRules, closeModal, refreshJobs]);
if (modalVisible === false || jobIds.length === 0) {
return null;

View file

@ -13,8 +13,6 @@ import { EuiFieldText, EuiForm, EuiFormRow, EuiSpacer, EuiTitle } from '@elastic
import { FormattedMessage } from '@kbn/i18n-react';
import { context } from '@kbn/kibana-react-plugin/public';
import { mlJobServiceFactory } from '../../../../../services/job_service';
import { toastNotificationServiceProvider } from '../../../../../services/toast_notification_service';
import { detectorToString } from '../../../../../util/string_utils';
export class Detectors extends Component {
@ -23,13 +21,6 @@ export class Detectors extends Component {
constructor(props, constructorContext) {
super(props, constructorContext);
const mlJobService = mlJobServiceFactory(
toastNotificationServiceProvider(constructorContext.services.notifications.toasts),
constructorContext.services.mlServices.mlApi
);
this.detectors = mlJobService.getJobGroups().map((g) => ({ label: g.id }));
this.state = {
detectors: [],
detectorDescriptions: [],

View file

@ -25,7 +25,6 @@ export function actionsMenuContent(
toastNotifications,
application,
mlApi,
mlJobService,
showEditJobFlyout,
showDatafeedChartFlyout,
showDeleteJobModal,
@ -77,7 +76,7 @@ export function actionsMenuContent(
if (isManagedJob(item)) {
showStopDatafeedsConfirmModal([item]);
} else {
stopDatafeeds(toastNotifications, mlJobService, [item], refreshJobs);
stopDatafeeds(toastNotifications, mlApi, [item], refreshJobs);
}
closeMenu(true);
@ -114,7 +113,7 @@ export function actionsMenuContent(
if (isManagedJob(item)) {
showCloseJobsConfirmModal([item]);
} else {
closeJobs(toastNotifications, mlJobService, [item], refreshJobs);
closeJobs(toastNotifications, mlApi, [item], refreshJobs);
}
closeMenu(true);
@ -153,7 +152,7 @@ export function actionsMenuContent(
return isJobBlocked(item) === false && canCreateJob;
},
onClick: (item) => {
cloneJob(toastNotifications, application, mlApi, mlJobService, item.id);
cloneJob(toastNotifications, application, mlApi, item.id);
closeMenu(true);
},
'data-test-subj': 'mlActionButtonCloneJob',

View file

@ -32,8 +32,6 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { AnomalyDetectionJobIdLink } from './job_id_link';
import { isManagedJob } from '../../../jobs_utils';
import { mlJobServiceFactory } from '../../../../services/job_service';
import { toastNotificationServiceProvider } from '../../../../services/toast_notification_service';
const PAGE_SIZE_OPTIONS = [10, 25, 50];
@ -47,10 +45,6 @@ export class JobsListUI extends Component {
};
this.mlApi = props.kibana.services.mlServices.mlApi;
this.mlJobService = mlJobServiceFactory(
toastNotificationServiceProvider(props.kibana.services.notifications.toasts),
this.mlApi
);
}
static getDerivedStateFromProps(props) {
@ -341,7 +335,6 @@ export class JobsListUI extends Component {
this.props.kibana.services.notifications.toasts,
this.props.kibana.services.application,
this.mlApi,
this.mlJobService,
this.props.showEditJobFlyout,
this.props.showDatafeedChartFlyout,
this.props.showDeleteJobModal,

View file

@ -10,7 +10,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { withKibana } from '@kbn/kibana-react-plugin/public';
import { checkForAutoStartDatafeed, filterJobs, loadFullJob } from '../utils';
import { filterJobs, loadFullJob } from '../utils';
import { JobsList } from '../jobs_list';
import { JobDetails } from '../job_details';
import { JobFilterBar } from '../job_filter_bar';
@ -37,8 +37,7 @@ import { StopDatafeedsConfirmModal } from '../confirm_modals/stop_datafeeds_conf
import { CloseJobsConfirmModal } from '../confirm_modals/close_jobs_confirm_modal';
import { AnomalyDetectionEmptyState } from '../anomaly_detection_empty_state';
import { removeNodeInfo } from '../../../../../../common/util/job_utils';
import { mlJobServiceFactory } from '../../../../services/job_service';
import { toastNotificationServiceProvider } from '../../../../services/toast_notification_service';
import { jobCloningService } from '../../../../services/job_cloning_service';
let blockingJobsRefreshTimeout = null;
@ -80,11 +79,6 @@ export class JobsListViewUI extends Component {
* @private
*/
this._isFiltersSet = false;
this.mlJobService = mlJobServiceFactory(
toastNotificationServiceProvider(props.kibana.services.notifications.toasts),
props.kibana.services.mlServices.mlApi
);
}
componentDidMount() {
@ -106,7 +100,7 @@ export class JobsListViewUI extends Component {
}
openAutoStartDatafeedModal() {
const job = checkForAutoStartDatafeed(this.mlJobService);
const job = jobCloningService.checkForAutoStartDatafeed();
if (job !== undefined) {
this.showStartDatafeedModal([job]);
}

View file

@ -16,8 +16,6 @@ import { context } from '@kbn/kibana-react-plugin/public';
import { checkPermission } from '../../../../capabilities/check_capabilities';
import { mlNodesAvailable } from '../../../../ml_nodes_check/check_ml_nodes';
import { mlJobServiceFactory } from '../../../../services/job_service';
import { toastNotificationServiceProvider } from '../../../../services/toast_notification_service';
import { isManagedJob } from '../../../jobs_utils';
@ -46,12 +44,8 @@ class MultiJobActionsMenuUI extends Component {
this.canResetJob = checkPermission('canResetJob') && mlNodesAvailable();
this.canCreateMlAlerts = checkPermission('canCreateMlAlerts');
this.toastNoticiations = constructorContext.services.notifications.toasts;
const mlApi = constructorContext.services.mlServices.mlApi;
const toastNotificationService = toastNotificationServiceProvider(
constructorContext.services.notifications.toasts
);
this.mlJobService = mlJobServiceFactory(toastNotificationService, mlApi);
this.toastNotifications = constructorContext.services.notifications.toasts;
this.mlApi = constructorContext.services.mlServices.mlApi;
}
onButtonClick = () => {
@ -116,7 +110,7 @@ class MultiJobActionsMenuUI extends Component {
if (this.props.jobs.some((j) => isManagedJob(j))) {
this.props.showCloseJobsConfirmModal(this.props.jobs);
} else {
closeJobs(this.toastNotifications, this.mlJobService, this.props.jobs);
closeJobs(this.toastNotifications, this.mlApi, this.props.jobs);
}
this.closePopover();
@ -163,7 +157,12 @@ class MultiJobActionsMenuUI extends Component {
if (this.props.jobs.some((j) => isManagedJob(j))) {
this.props.showStopDatafeedsConfirmModal(this.props.jobs);
} else {
stopDatafeeds(this.props.jobs, this.props.refreshJobs);
stopDatafeeds(
this.toastNotifications,
this.mlApi,
this.props.jobs,
this.props.refreshJobs
);
}
this.closePopover();
}}

View file

@ -158,8 +158,8 @@ export class GroupSelectorUI extends Component {
}
const tempJobs = newJobs.map((j) => ({ jobId: j.id, groups: j.newGroups }));
const ml = this.props.kibana.services.mlServices.mlApi;
ml.jobs
const mlApi = this.props.kibana.services.mlServices.mlApi;
mlApi.jobs
.updateGroups(tempJobs)
.then((resp) => {
let success = true;

View file

@ -25,8 +25,7 @@ import { i18n } from '@kbn/i18n';
import { resetJobs } from '../utils';
import type { MlSummaryJob } from '../../../../../../common/types/anomaly_detection_jobs';
import { RESETTING_JOBS_REFRESH_INTERVAL_MS } from '../../../../../../common/constants/jobs_list';
import { useMlKibana } from '../../../../contexts/kibana';
import { useMlJobService } from '../../../../services/job_service';
import { useMlApi, useMlKibana } from '../../../../contexts/kibana';
import { OpenJobsWarningCallout } from './open_jobs_warning_callout';
import { isManagedJob } from '../../../jobs_utils';
import { ManagedJobsWarningCallout } from '../confirm_modals/managed_jobs_warning_callout';
@ -45,7 +44,7 @@ export const ResetJobModal: FC<Props> = ({ setShowFunction, unsetShowFunction, r
notifications: { toasts },
},
} = useMlKibana();
const mlJobService = useMlJobService();
const mlApi = useMlApi();
const [resetting, setResetting] = useState(false);
const [modalVisible, setModalVisible] = useState(false);
const [jobIds, setJobIds] = useState<string[]>([]);
@ -81,14 +80,12 @@ export const ResetJobModal: FC<Props> = ({ setShowFunction, unsetShowFunction, r
const resetJob = useCallback(async () => {
setResetting(true);
await resetJobs(toasts, mlJobService, jobIds, deleteUserAnnotations);
await resetJobs(toasts, mlApi, jobIds, deleteUserAnnotations);
closeModal();
setTimeout(() => {
refreshJobs();
}, RESETTING_JOBS_REFRESH_INTERVAL_MS);
// exclude mlJobservice from deps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [closeModal, deleteUserAnnotations, jobIds, refreshJobs]);
}, [closeModal, deleteUserAnnotations, jobIds, mlApi, refreshJobs, toasts]);
if (modalVisible === false || jobIds.length === 0) {
return null;

View file

@ -24,9 +24,6 @@ import {
import { FormattedMessage } from '@kbn/i18n-react';
import { context } from '@kbn/kibana-react-plugin/public';
import { mlJobServiceFactory } from '../../../../services/job_service';
import { toastNotificationServiceProvider } from '../../../../services/toast_notification_service';
import { isManagedJob } from '../../../jobs_utils';
import { forceStartDatafeeds } from '../utils';
@ -57,10 +54,7 @@ export class StartDatafeedModal extends Component {
this.refreshJobs = this.props.refreshJobs;
this.getShowCreateAlertFlyoutFunction = this.props.getShowCreateAlertFlyoutFunction;
this.toastNotifications = constructorContext.services.notifications.toasts;
this.mlJobService = mlJobServiceFactory(
toastNotificationServiceProvider(this.toastNotifications),
constructorContext.services.mlServices.mlApi
);
this.mlApi = constructorContext.services.mlServices.mlApi;
}
componentDidMount() {
@ -125,7 +119,7 @@ export class StartDatafeedModal extends Component {
? this.state.endTime.valueOf()
: this.state.endTime;
forceStartDatafeeds(this.toastNotifications, this.mlJobService, jobs, start, end, () => {
forceStartDatafeeds(this.toastNotifications, this.mlApi, jobs, start, end, () => {
if (this.state.createAlert && jobs.length > 0) {
this.getShowCreateAlertFlyoutFunction()(jobs.map((job) => job.id));
}

View file

@ -12,7 +12,6 @@ import type {
CombinedJobWithStats,
MlSummaryJob,
} from '../../../../../common/types/anomaly_detection_jobs';
import type { MlJobService } from '../../../services/job_service';
import type { MlApi } from '../../../services/ml_api_service';
export function loadFullJob(mlApi: MlApi, jobId: string): Promise<CombinedJobWithStats>;
@ -22,7 +21,7 @@ export function isClosable(jobs: CombinedJobWithStats[]): boolean;
export function isResettable(jobs: CombinedJobWithStats[]): boolean;
export function forceStartDatafeeds(
toastNotifications: ToastsStart,
mlJobService: MlJobService,
mlApi: MlApi,
jobs: CombinedJobWithStats[],
start: number | undefined,
end: number | undefined,
@ -30,7 +29,7 @@ export function forceStartDatafeeds(
): Promise<void>;
export function stopDatafeeds(
toastNotifications: ToastsStart,
mlJobService: MlJobService,
mlApi: MlApi,
jobs: CombinedJobWithStats[] | MlSummaryJob[],
finish?: () => void
): Promise<void>;
@ -43,18 +42,17 @@ export function cloneJob(
toastNotifications: ToastsStart,
application: ApplicationStart,
mlApi: MlApi,
mlJobService: MlJobService,
jobId: string
): Promise<void>;
export function closeJobs(
toastNotifications: ToastsStart,
mlJobService: MlJobService,
mlApi: MlApi,
jobs: CombinedJobWithStats[] | MlSummaryJob[],
finish?: () => void
): Promise<void>;
export function deleteJobs(
toastNotifications: ToastsStart,
mlJobService: MlJobService,
mlApi: MlApi,
jobs: Array<{ id: string }>,
deleteUserAnnotations?: boolean,
deleteAlertingRules?: boolean,
@ -62,7 +60,7 @@ export function deleteJobs(
): Promise<void>;
export function resetJobs(
toastNotifications: ToastsStart,
mlJobService: MlJobService,
mlApi: MlApi,
jobIds: string[],
deleteUserAnnotations?: boolean,
finish?: () => void
@ -73,8 +71,3 @@ export function filterJobs(
): CombinedJobWithStats[];
export function jobProperty(job: CombinedJobWithStats, prop: string): any;
export function jobTagFilter(jobs: CombinedJobWithStats[], value: string): CombinedJobWithStats[];
export function checkForAutoStartDatafeed(
mlJobService: MlJobService
):
| { id: string; hasDatafeed: boolean; latestTimestampSortValue: number; datafeedId: string }
| undefined;

View file

@ -14,7 +14,7 @@ import { JOB_STATE, DATAFEED_STATE } from '../../../../../common/constants/state
import { JOB_ACTION } from '../../../../../common/constants/job_actions';
import { parseInterval } from '../../../../../common/util/parse_interval';
import { mlCalendarService } from '../../../services/calendar_service';
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import { jobCloningService } from '../../../services/job_cloning_service';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { PLUGIN_ID } from '../../../../../common/constants/app';
import { CREATED_BY_LABEL } from '../../../../../common/constants/new_job';
@ -82,14 +82,14 @@ export function isResettable(jobs) {
export function forceStartDatafeeds(
toastNotifications,
mlJobService,
mlApi,
jobs,
start,
end,
finish = () => {}
) {
const datafeedIds = jobs.filter((j) => j.hasDatafeed).map((j) => j.datafeedId);
mlJobService
mlApi.jobs
.forceStartDatafeeds(datafeedIds, start, end)
.then((resp) => {
showResults(toastNotifications, resp, DATAFEED_STATE.STARTED);
@ -106,9 +106,9 @@ export function forceStartDatafeeds(
});
}
export function stopDatafeeds(toastNotifications, mlJobService, jobs, finish = () => {}) {
export function stopDatafeeds(toastNotifications, mlApi, jobs, finish = () => {}) {
const datafeedIds = jobs.filter((j) => j.hasDatafeed).map((j) => j.datafeedId);
mlJobService
mlApi.jobs
.stopDatafeeds(datafeedIds)
.then((resp) => {
showResults(toastNotifications, resp, DATAFEED_STATE.STOPPED);
@ -214,13 +214,17 @@ function showResults(toastNotifications, resp, action) {
}
}
export async function cloneJob(toastNotifications, application, mlApi, mlJobService, jobId) {
export async function cloneJob(toastNotifications, application, mlApi, jobId) {
try {
const [{ job: cloneableJob, datafeed }, originalJob] = await Promise.all([
loadJobForCloning(mlApi, jobId),
loadFullJob(mlApi, jobId),
]);
const tempJobCloningData = {
skipTimeRangeStep: false,
};
const createdBy = originalJob?.custom_settings?.created_by;
if (
cloneableJob !== undefined &&
@ -228,9 +232,9 @@ export async function cloneJob(toastNotifications, application, mlApi, mlJobServ
createdBy !== CREATED_BY_LABEL.ADVANCED
) {
// if the job is from a wizards, i.e. contains a created_by property
// use tempJobCloningObjects to temporarily store the job
mlJobService.tempJobCloningObjects.createdBy = originalJob?.custom_settings?.created_by;
mlJobService.tempJobCloningObjects.job = cloneableJob;
// use tempJobCloningData to temporarily store the job
tempJobCloningData.createdBy = originalJob?.custom_settings?.created_by;
tempJobCloningData.job = cloneableJob;
if (
originalJob.data_counts.earliest_record_timestamp !== undefined &&
@ -256,26 +260,28 @@ export async function cloneJob(toastNotifications, application, mlApi, mlJobServ
end = originalJob.data_counts.latest_bucket_timestamp + bucketSpanMs * 2 - 1;
}
mlJobService.tempJobCloningObjects.start = start;
mlJobService.tempJobCloningObjects.end = end;
tempJobCloningData.start = start;
tempJobCloningData.end = end;
}
} else {
// otherwise use the tempJobCloningObjects
mlJobService.tempJobCloningObjects.job = cloneableJob;
// otherwise tempJobCloningData
tempJobCloningData.job = cloneableJob;
// resets the createdBy field in case it still retains previous settings
mlJobService.tempJobCloningObjects.createdBy = undefined;
tempJobCloningData.createdBy = undefined;
}
if (datafeed !== undefined) {
mlJobService.tempJobCloningObjects.datafeed = datafeed;
tempJobCloningData.datafeed = datafeed;
}
if (originalJob.calendars) {
mlJobService.tempJobCloningObjects.calendars = await mlCalendarService.fetchCalendarsByIds(
tempJobCloningData.calendars = await mlCalendarService.fetchCalendarsByIds(
mlApi,
originalJob.calendars
);
}
jobCloningService.stashJobCloningData(tempJobCloningData);
application.navigateToApp(PLUGIN_ID, { path: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB });
} catch (error) {
toastNotificationServiceProvider(toastNotifications).displayErrorToast(
@ -288,9 +294,9 @@ export async function cloneJob(toastNotifications, application, mlApi, mlJobServ
}
}
export function closeJobs(toastNotifications, mlJobService, jobs, finish = () => {}) {
export function closeJobs(toastNotifications, mlApi, jobs, finish = () => {}) {
const jobIds = jobs.map((j) => j.id);
mlJobService
mlApi.jobs
.closeJobs(jobIds)
.then((resp) => {
showResults(toastNotifications, resp, JOB_STATE.CLOSED);
@ -309,12 +315,12 @@ export function closeJobs(toastNotifications, mlJobService, jobs, finish = () =>
export function resetJobs(
toastNotifications,
mlJobService,
mlApi,
jobIds,
deleteUserAnnotations,
finish = () => {}
) {
mlJobService
mlApi.jobs
.resetJobs(jobIds, deleteUserAnnotations)
.then((resp) => {
showResults(toastNotifications, resp, JOB_ACTION.RESET);
@ -333,14 +339,14 @@ export function resetJobs(
export function deleteJobs(
toastNotifications,
mlJobService,
mlApi,
jobs,
deleteUserAnnotations,
deleteAlertingRules,
finish = () => {}
) {
const jobIds = jobs.map((j) => j.id);
mlJobService
mlApi.jobs
.deleteJobs(jobIds, deleteUserAnnotations, deleteAlertingRules)
.then((resp) => {
showResults(toastNotifications, resp, JOB_STATE.DELETED);
@ -449,24 +455,3 @@ function jobTagFilter(jobs, value) {
.find((t) => value.some((t1) => t1 === t));
});
}
// check to see if a job has been stored in mlJobService.tempJobCloningObjects
// if it has, return an object with the minimum properties needed for the
// start datafeed modal.
export function checkForAutoStartDatafeed(mlJobService) {
const job = mlJobService.tempJobCloningObjects.job;
const datafeed = mlJobService.tempJobCloningObjects.datafeed;
if (job !== undefined) {
mlJobService.tempJobCloningObjects.job = undefined;
mlJobService.tempJobCloningObjects.datafeed = undefined;
mlJobService.tempJobCloningObjects.createdBy = undefined;
const hasDatafeed = isPopulatedObject(datafeed);
const datafeedId = hasDatafeed ? datafeed.datafeed_id : '';
return {
id: job.job_id,
hasDatafeed,
latestTimestampSortValue: 0,
datafeedId,
};
}
}

View file

@ -10,7 +10,6 @@ import type { DataView } from '@kbn/data-views-plugin/public';
import type { Field, Aggregation, SplitField } from '@kbn/ml-anomaly-utils';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import type { MlJobService } from '../../../../services/job_service';
import type { MlApi } from '../../../../services/ml_api_service';
import { JobCreator } from './job_creator';
import type {
@ -44,13 +43,12 @@ export class AdvancedJobCreator extends JobCreator {
constructor(
mlApi: MlApi,
mlJobService: MlJobService,
newJobCapsService: NewJobCapsService,
indexPattern: DataView,
savedSearch: SavedSearch | null,
query: object
) {
super(mlApi, mlJobService, newJobCapsService, indexPattern, savedSearch, query);
super(mlApi, newJobCapsService, indexPattern, savedSearch, query);
this.createdBy = CREATED_BY_LABEL.ADVANCED;
this._queryString = JSON.stringify(this._datafeed_config.query);

View file

@ -34,7 +34,6 @@ import {
DEFAULT_BUCKET_SPAN,
DEFAULT_RARE_BUCKET_SPAN,
} from '../../../../../../common/constants/new_job';
import type { MlJobService } from '../../../../services/job_service';
import type { MlApi } from '../../../../services/ml_api_service';
import type { NewJobCapsService } from '../../../../services/new_job_capabilities/new_job_capabilities_service';
@ -66,13 +65,12 @@ export class CategorizationJobCreator extends JobCreator {
constructor(
mlApi: MlApi,
mlJobService: MlJobService,
newJobCapsService: NewJobCapsService,
indexPattern: DataView,
savedSearch: SavedSearch | null,
query: object
) {
super(mlApi, mlJobService, newJobCapsService, indexPattern, savedSearch, query);
super(mlApi, newJobCapsService, indexPattern, savedSearch, query);
this.createdBy = CREATED_BY_LABEL.CATEGORIZATION;
this._examplesLoader = new CategorizationExamplesLoader(this, indexPattern, query);

View file

@ -8,7 +8,6 @@
import type { DataView } from '@kbn/data-views-plugin/public';
import type { Field, Aggregation, SplitField, AggFieldPair } from '@kbn/ml-anomaly-utils';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import type { MlJobService } from '../../../../services/job_service';
import type { MlApi } from '../../../../services/ml_api_service';
import type { NewJobCapsService } from '../../../../services/new_job_capabilities/new_job_capabilities_service';
import { JobCreator } from './job_creator';
@ -32,13 +31,12 @@ export class GeoJobCreator extends JobCreator {
constructor(
mlApi: MlApi,
mlJobService: MlJobService,
newJobCapsService: NewJobCapsService,
indexPattern: DataView,
savedSearch: SavedSearch | null,
query: object
) {
super(mlApi, mlJobService, newJobCapsService, indexPattern, savedSearch, query);
super(mlApi, newJobCapsService, indexPattern, savedSearch, query);
this.createdBy = CREATED_BY_LABEL.GEO;
this._wizardInitialized$.next(true);
}

View file

@ -22,6 +22,7 @@ import {
import type { RuntimeMappings } from '@kbn/ml-runtime-field-utils';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import { createDatafeedId } from '../../../../../../common/util/job_utils';
import type { MlApi } from '../../../../services/ml_api_service';
import type { IndexPatternTitle } from '../../../../../../common/types/kibana';
import { getQueryFromSavedSearchObject } from '../../../../util/index_utils';
@ -36,7 +37,6 @@ import type {
} from '../../../../../../common/types/anomaly_detection_jobs';
import { combineFieldsAndAggs } from '../../../../../../common/util/fields_utils';
import { createEmptyJob, createEmptyDatafeed } from './util/default_configs';
import type { MlJobService } from '../../../../services/job_service';
import { JobRunner, type ProgressSubscriber } from '../job_runner';
import type { CREATED_BY_LABEL } from '../../../../../../common/constants/new_job';
import { JOB_TYPE, SHARED_RESULTS_INDEX_NAME } from '../../../../../../common/constants/new_job';
@ -80,19 +80,16 @@ export class JobCreator {
protected _wizardInitialized$ = new BehaviorSubject<boolean>(false);
public wizardInitialized$ = this._wizardInitialized$.asObservable();
public mlApi: MlApi;
public mlJobService: MlJobService;
public newJobCapsService: NewJobCapsService;
constructor(
mlApi: MlApi,
mlJobService: MlJobService,
newJobCapsService: NewJobCapsService,
indexPattern: DataView,
savedSearch: SavedSearch | null,
query: object
) {
this.mlApi = mlApi;
this.mlJobService = mlJobService;
this.newJobCapsService = newJobCapsService;
this._indexPattern = indexPattern;
this._savedSearch = savedSearch;
@ -241,7 +238,7 @@ export class JobCreator {
public set jobId(jobId: JobId) {
this._job_config.job_id = jobId;
this._datafeed_config.job_id = jobId;
this._datafeed_config.datafeed_id = `datafeed-${jobId}`;
this._datafeed_config.datafeed_id = createDatafeedId(jobId);
if (this._useDedicatedIndex) {
this._job_config.results_index_name = jobId;
@ -620,16 +617,13 @@ export class JobCreator {
}
}
public async createJob(): Promise<object> {
public async createJob() {
try {
const { success, resp } = await this.mlJobService.saveNewJob(this._job_config);
await this.mlApi.addJob({
jobId: this._job_config.job_id,
job: this._job_config,
});
await this._updateCalendars();
if (success === true) {
return resp;
} else {
throw resp;
}
} catch (error) {
throw error;
}
@ -638,7 +632,14 @@ export class JobCreator {
public async createDatafeed(): Promise<object> {
try {
const tempDatafeed = this._getDatafeedWithFilteredRuntimeMappings();
return await this.mlJobService.saveNewDatafeed(tempDatafeed, this._job_config.job_id);
const jobId = this._job_config.job_id;
const datafeedId = createDatafeedId(jobId);
tempDatafeed.job_id = jobId;
return this.mlApi.addDatafeed({
datafeedId,
datafeedConfig: tempDatafeed,
});
} catch (error) {
throw error;
}

View file

@ -7,7 +7,6 @@
import type { DataView } from '@kbn/data-views-plugin/public';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import type { MlJobService } from '../../../../services/job_service';
import type { MlApi } from '../../../../services/ml_api_service';
import type { NewJobCapsService } from '../../../../services/new_job_capabilities/new_job_capabilities_service';
import { SingleMetricJobCreator } from './single_metric_job_creator';
@ -24,7 +23,6 @@ export const jobCreatorFactory =
(jobType: JOB_TYPE) =>
(
mlApi: MlApi,
mlJobService: MlJobService,
newJobCapsService: NewJobCapsService,
indexPattern: DataView,
savedSearch: SavedSearch | null,
@ -57,5 +55,5 @@ export const jobCreatorFactory =
jc = SingleMetricJobCreator;
break;
}
return new jc(mlApi, mlJobService, newJobCapsService, indexPattern, savedSearch, query);
return new jc(mlApi, newJobCapsService, indexPattern, savedSearch, query);
};

View file

@ -8,7 +8,6 @@
import type { DataView } from '@kbn/data-views-plugin/public';
import type { Field, Aggregation, SplitField, AggFieldPair } from '@kbn/ml-anomaly-utils';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import type { MlJobService } from '../../../../services/job_service';
import type { MlApi } from '../../../../services/ml_api_service';
import type { NewJobCapsService } from '../../../../services/new_job_capabilities/new_job_capabilities_service';
import { JobCreator } from './job_creator';
@ -31,13 +30,12 @@ export class MultiMetricJobCreator extends JobCreator {
constructor(
mlApi: MlApi,
mlJobService: MlJobService,
newJobCapsService: NewJobCapsService,
indexPattern: DataView,
savedSearch: SavedSearch | null,
query: object
) {
super(mlApi, mlJobService, newJobCapsService, indexPattern, savedSearch, query);
super(mlApi, newJobCapsService, indexPattern, savedSearch, query);
this.createdBy = CREATED_BY_LABEL.MULTI_METRIC;
this._wizardInitialized$.next(true);
}

View file

@ -8,7 +8,6 @@
import type { DataView } from '@kbn/data-views-plugin/public';
import type { Field, Aggregation, SplitField, AggFieldPair } from '@kbn/ml-anomaly-utils';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import type { MlJobService } from '../../../../services/job_service';
import type { MlApi } from '../../../../services/ml_api_service';
import type { NewJobCapsService } from '../../../../services/new_job_capabilities/new_job_capabilities_service';
import { JobCreator } from './job_creator';
@ -30,13 +29,12 @@ export class PopulationJobCreator extends JobCreator {
constructor(
mlApi: MlApi,
mlJobService: MlJobService,
newJobCapsService: NewJobCapsService,
indexPattern: DataView,
savedSearch: SavedSearch | null,
query: object
) {
super(mlApi, mlJobService, newJobCapsService, indexPattern, savedSearch, query);
super(mlApi, newJobCapsService, indexPattern, savedSearch, query);
this.createdBy = CREATED_BY_LABEL.POPULATION;
this._wizardInitialized$.next(true);
}

View file

@ -13,7 +13,6 @@ import {
ML_JOB_AGGREGATION,
} from '@kbn/ml-anomaly-utils';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import type { MlJobService } from '../../../../services/job_service';
import type { MlApi } from '../../../../services/ml_api_service';
import type { NewJobCapsService } from '../../../../services/new_job_capabilities/new_job_capabilities_service';
import { JobCreator } from './job_creator';
@ -38,13 +37,12 @@ export class RareJobCreator extends JobCreator {
constructor(
mlApi: MlApi,
mlJobService: MlJobService,
newJobCapsService: NewJobCapsService,
indexPattern: DataView,
savedSearch: SavedSearch | null,
query: object
) {
super(mlApi, mlJobService, newJobCapsService, indexPattern, savedSearch, query);
super(mlApi, newJobCapsService, indexPattern, savedSearch, query);
this.createdBy = CREATED_BY_LABEL.RARE;
this._wizardInitialized$.next(true);
this._rareAgg = {} as Aggregation;

View file

@ -15,7 +15,6 @@ import {
ES_AGGREGATION,
} from '@kbn/ml-anomaly-utils';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import type { MlJobService } from '../../../../services/job_service';
import type { MlApi } from '../../../../services/ml_api_service';
import { parseInterval } from '../../../../../../common/util/parse_interval';
import { JobCreator } from './job_creator';
@ -36,13 +35,12 @@ export class SingleMetricJobCreator extends JobCreator {
constructor(
mlApi: MlApi,
mlJobService: MlJobService,
newJobCapsService: NewJobCapsService,
indexPattern: DataView,
savedSearch: SavedSearch | null,
query: object
) {
super(mlApi, mlJobService, newJobCapsService, indexPattern, savedSearch, query);
super(mlApi, newJobCapsService, indexPattern, savedSearch, query);
this.createdBy = CREATED_BY_LABEL.SINGLE_METRIC;
this._wizardInitialized$.next(true);
}

View file

@ -20,6 +20,8 @@ import {
ML_JOB_AGGREGATION,
SPARSE_DATA_AGGREGATIONS,
} from '@kbn/ml-anomaly-utils';
import { cloneDeep } from 'lodash';
import { jobCloningService } from '../../../../../services/job_cloning_service';
import type {
Job,
Datafeed,
@ -28,7 +30,6 @@ import type {
import type { NewJobCapsService } from '../../../../../services/new_job_capabilities/new_job_capabilities_service';
import type { NavigateToPath } from '../../../../../contexts/kibana';
import { ML_PAGES } from '../../../../../../../common/constants/locator';
import type { MlJobService } from '../../../../../services/job_service';
import type { JobCreatorType } from '..';
import { CREATED_BY_LABEL, JOB_TYPE } from '../../../../../../../common/constants/new_job';
@ -237,53 +238,39 @@ export function isSparseDataJob(job: Job, datafeed: Datafeed): boolean {
}
export function convertToMultiMetricJob(
mlJobService: MlJobService,
jobCreator: JobCreatorType,
navigateToPath: NavigateToPath
) {
jobCreator.createdBy = CREATED_BY_LABEL.MULTI_METRIC;
jobCreator.modelPlot = false;
mlJobService.stashJobForCloning(jobCreator, true, true);
jobCloningService.stashJobForCloning(jobCreator, true, true);
navigateToPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_CONVERT_TO_MULTI_METRIC, true);
}
export function convertToAdvancedJob(
mlJobService: MlJobService,
jobCreator: JobCreatorType,
navigateToPath: NavigateToPath
) {
export function convertToAdvancedJob(jobCreator: JobCreatorType, navigateToPath: NavigateToPath) {
jobCreator.createdBy = null;
mlJobService.stashJobForCloning(jobCreator, true, true);
jobCloningService.stashJobForCloning(jobCreator, true, true);
navigateToPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_CONVERT_TO_ADVANCED, true);
}
export function resetAdvancedJob(
mlJobService: MlJobService,
jobCreator: JobCreatorType,
navigateToPath: NavigateToPath
) {
export function resetAdvancedJob(jobCreator: JobCreatorType, navigateToPath: NavigateToPath) {
jobCreator.createdBy = null;
mlJobService.stashJobForCloning(jobCreator, true, false);
jobCloningService.stashJobForCloning(jobCreator, true, false);
navigateToPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB);
}
export function resetJob(
mlJobService: MlJobService,
jobCreator: JobCreatorType,
navigateToPath: NavigateToPath
) {
export function resetJob(jobCreator: JobCreatorType, navigateToPath: NavigateToPath) {
jobCreator.jobId = '';
mlJobService.stashJobForCloning(jobCreator, true, true);
jobCloningService.stashJobForCloning(jobCreator, true, true);
navigateToPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB);
}
export function advancedStartDatafeed(
mlJobService: MlJobService,
jobCreator: JobCreatorType | null,
navigateToPath: NavigateToPath
) {
if (jobCreator !== null) {
mlJobService.stashJobForCloning(jobCreator, false, false);
jobCloningService.stashJobForCloning(jobCreator, false, false);
}
navigateToPath('/jobs');
}
@ -350,3 +337,13 @@ export function collectAggs(o: any, aggFields: Field[]) {
}
}
}
export function cloneDatafeed(datafeed: Datafeed) {
const tempDatafeed = cloneDeep(datafeed);
// remove parts of the datafeed config which should not be copied
tempDatafeed.datafeed_id = '';
tempDatafeed.job_id = '';
return tempDatafeed;
}

View file

@ -7,7 +7,6 @@
import { BehaviorSubject } from 'rxjs';
import type { MlApi } from '../../../../services/ml_api_service';
import type { MlJobService } from '../../../../services/job_service';
import type { JobCreator } from '../job_creator';
import type { DatafeedId, JobId } from '../../../../../../common/types/anomaly_detection_jobs';
import { DATAFEED_STATE } from '../../../../../../common/constants/states';
@ -23,7 +22,6 @@ export type JobAssignmentSubscriber = (assigned: boolean) => void;
export class JobRunner {
private _mlApi: MlApi;
private _mlJobService: MlJobService;
private _jobId: JobId;
private _datafeedId: DatafeedId;
private _start: number = 0;
@ -45,7 +43,6 @@ export class JobRunner {
constructor(jobCreator: JobCreator) {
this._mlApi = jobCreator.mlApi;
this._mlJobService = jobCreator.mlJobService;
this._jobId = jobCreator.jobId;
this._datafeedId = jobCreator.datafeedId;
this._start = jobCreator.start;
@ -72,7 +69,7 @@ export class JobRunner {
private async openJob(): Promise<void> {
try {
const { node }: { node?: string } = await this._mlJobService.openJob(this._jobId);
const { node }: { node?: string } = await this._mlApi.openJob({ jobId: this._jobId });
this._jobAssignedToNode = node !== undefined && node.length > 0;
this._jobAssignedToNode$.next(this._jobAssignedToNode);
} catch (error) {
@ -96,12 +93,11 @@ export class JobRunner {
pollProgress === true ? this._subscribers.map((s) => this._progress$.subscribe(s)) : [];
await this.openJob();
const { started } = await this._mlJobService.startDatafeed(
this._datafeedId,
this._jobId,
const { started } = await this._mlApi.startDatafeed({
datafeedId: this._datafeedId,
start,
end
);
end,
});
this._datafeedState = DATAFEED_STATE.STARTED;
this._percentageComplete = 0;

View file

@ -22,7 +22,6 @@ import { FilterStateStore } from '@kbn/es-query';
import type { ErrorType } from '@kbn/ml-error-utils';
import type { DataViewsContract } from '@kbn/data-views-plugin/public';
import type { MlApi } from '../../../services/ml_api_service';
import type { MlJobService } from '../../../services/job_service';
import type { Job, Datafeed } from '../../../../../common/types/anomaly_detection_jobs';
import { getFiltersForDSLQuery } from '../../../../../common/util/job_utils';
import type { CREATED_BY_LABEL } from '../../../../../common/constants/new_job';
@ -57,8 +56,7 @@ export class QuickJobCreatorBase {
protected readonly kibanaConfig: IUiSettingsClient,
protected readonly timeFilter: TimefilterContract,
protected readonly dashboardService: DashboardStart,
protected readonly mlApi: MlApi,
protected readonly mlJobService: MlJobService
protected readonly mlApi: MlApi
) {}
protected async putJobAndDataFeed({

View file

@ -20,7 +20,6 @@ import type { LensApi } from '@kbn/lens-plugin/public';
import type { JobCreatorType } from '../common/job_creator';
import { createEmptyJob, createEmptyDatafeed } from '../common/job_creator/util/default_configs';
import type { MlApi } from '../../../services/ml_api_service';
import type { MlJobService } from '../../../services/job_service';
import {
CREATED_BY_LABEL,
DEFAULT_BUCKET_SPAN,
@ -34,6 +33,7 @@ import {
} from './utils';
import { VisualizationExtractor } from './visualization_extractor';
import { QuickJobCreatorBase, type CreateState } from '../job_from_dashboard';
import { jobCloningService } from '../../../services/job_cloning_service';
export class QuickLensJobCreator extends QuickJobCreatorBase {
constructor(
@ -42,10 +42,9 @@ export class QuickLensJobCreator extends QuickJobCreatorBase {
kibanaConfig: IUiSettingsClient,
timeFilter: TimefilterContract,
dashboardService: DashboardStart,
mlApi: MlApi,
mlJobService: MlJobService
mlApi: MlApi
) {
super(dataViews, kibanaConfig, timeFilter, dashboardService, mlApi, mlJobService);
super(dataViews, kibanaConfig, timeFilter, dashboardService, mlApi);
}
public async createAndSaveJob(
@ -116,7 +115,7 @@ export class QuickLensJobCreator extends QuickJobCreatorBase {
// add job config and start and end dates to the
// job cloning stash, so they can be used
// by the new job wizards
this.mlJobService.stashJobForCloning(
jobCloningService.stashJobForCloning(
{
jobConfig,
datafeedConfig,

View file

@ -15,7 +15,6 @@ import type { DashboardStart } from '@kbn/dashboard-plugin/public';
import type { DataViewsContract } from '@kbn/data-views-plugin/public';
import { QuickLensJobCreator } from './quick_create_job';
import type { MlApi } from '../../../services/ml_api_service';
import type { MlJobService } from '../../../services/job_service';
import { getDefaultQuery, getRisonValue } from '../utils/new_job_utils';
@ -26,7 +25,6 @@ interface Dependencies {
timeFilter: TimefilterContract;
dashboardService: DashboardStart;
mlApi: MlApi;
mlJobService: MlJobService;
}
export async function resolver(
deps: Dependencies,
@ -37,7 +35,7 @@ export async function resolver(
filtersRisonString: string,
layerIndexRisonString: string
) {
const { dataViews, lens, mlApi, mlJobService, timeFilter, kibanaConfig, dashboardService } = deps;
const { dataViews, lens, mlApi, timeFilter, kibanaConfig, dashboardService } = deps;
if (lensSavedObjectRisonString === undefined) {
throw new Error('Cannot create visualization');
}
@ -59,8 +57,7 @@ export async function resolver(
kibanaConfig,
timeFilter,
dashboardService,
mlApi,
mlJobService
mlApi
);
await jobCreator.createAndStashADJob(vis, from, to, query, filters, layerIndex);
}

View file

@ -13,7 +13,6 @@ import type { DataView, DataViewsContract } from '@kbn/data-views-plugin/public'
import type { DashboardStart } from '@kbn/dashboard-plugin/public';
import type { MapApi } from '@kbn/maps-plugin/public';
import type { MlApi } from '../../../services/ml_api_service';
import type { MlJobService } from '../../../services/job_service';
import {
CREATED_BY_LABEL,
JOB_TYPE,
@ -25,6 +24,7 @@ import { getJobsItemsFromEmbeddable } from './utils';
import type { CreateState } from '../job_from_dashboard';
import { QuickJobCreatorBase } from '../job_from_dashboard';
import { getDefaultQuery } from '../utils/new_job_utils';
import { jobCloningService } from '../../../services/job_cloning_service';
interface VisDescriptor {
dashboard: { query: Query; filters: Filter[] };
@ -43,10 +43,9 @@ export class QuickGeoJobCreator extends QuickJobCreatorBase {
kibanaConfig: IUiSettingsClient,
timeFilter: TimefilterContract,
dashboardService: DashboardStart,
mlApi: MlApi,
mlJobService: MlJobService
mlApi: MlApi
) {
super(dataViews, kibanaConfig, timeFilter, dashboardService, mlApi, mlJobService);
super(dataViews, kibanaConfig, timeFilter, dashboardService, mlApi);
}
public async createAndSaveGeoJob({
@ -145,7 +144,7 @@ export class QuickGeoJobCreator extends QuickJobCreatorBase {
// add job config and start and end dates to the
// job cloning stash, so they can be used
// by the new job wizards
this.mlJobService.stashJobForCloning(
jobCloningService.stashJobForCloning(
{
jobConfig,
datafeedConfig,

View file

@ -10,7 +10,6 @@ import type { TimefilterContract } from '@kbn/data-plugin/public';
import type { DashboardStart } from '@kbn/dashboard-plugin/public';
import type { DataViewsContract } from '@kbn/data-views-plugin/public';
import type { MlApi } from '../../../services/ml_api_service';
import type { MlJobService } from '../../../services/job_service';
import { QuickGeoJobCreator } from './quick_create_job';
import { getDefaultQuery, getRisonValue } from '../utils/new_job_utils';
@ -21,7 +20,6 @@ interface Dependencies {
timeFilter: TimefilterContract;
dashboardService: DashboardStart;
mlApi: MlApi;
mlJobService: MlJobService;
}
export async function resolver(
deps: Dependencies,
@ -34,7 +32,7 @@ export async function resolver(
toRisonString: string,
layerRisonString?: string
) {
const { dataViews, kibanaConfig, timeFilter, dashboardService, mlApi, mlJobService } = deps;
const { dataViews, kibanaConfig, timeFilter, dashboardService, mlApi } = deps;
const defaultLayer = { query: getDefaultQuery(), filters: [] };
const dashboard = getRisonValue<typeof defaultLayer>(dashboardRisonString, defaultLayer);
@ -57,8 +55,7 @@ export async function resolver(
kibanaConfig,
timeFilter,
dashboardService,
mlApi,
mlJobService
mlApi
);
await jobCreator.createAndStashGeoJob(

View file

@ -17,9 +17,9 @@ import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/type
import { CREATED_BY_LABEL, DEFAULT_BUCKET_SPAN } from '../../../../../common/constants/new_job';
import { type CreateState, QuickJobCreatorBase } from '../job_from_dashboard/quick_create_job_base';
import type { MlApi } from '../../../services/ml_api_service';
import type { MlJobService } from '../../../services/job_service';
import { createEmptyDatafeed, createEmptyJob } from '../common/job_creator/util/default_configs';
import type { JobCreatorType } from '../common/job_creator';
import { jobCloningService } from '../../../services/job_cloning_service';
export const CATEGORIZATION_TYPE = {
COUNT: ML_JOB_AGGREGATION.COUNT,
@ -36,10 +36,9 @@ export class QuickCategorizationJobCreator extends QuickJobCreatorBase {
timeFilter: TimefilterContract,
dashboardService: DashboardStart,
private data: DataPublicPluginStart,
mlApi: MlApi,
mlJobService: MlJobService
mlApi: MlApi
) {
super(dataViews, kibanaConfig, timeFilter, dashboardService, mlApi, mlJobService);
super(dataViews, kibanaConfig, timeFilter, dashboardService, mlApi);
}
public async createAndSaveJob(
@ -119,7 +118,7 @@ export class QuickCategorizationJobCreator extends QuickJobCreatorBase {
// add job config and start and end dates to the
// job cloning stash, so they can be used
// by the new job wizards
this.mlJobService.stashJobForCloning(
jobCloningService.stashJobForCloning(
{
jobConfig,
datafeedConfig,

View file

@ -15,7 +15,6 @@ import {
CATEGORIZATION_TYPE,
} from './quick_create_job';
import type { MlApi } from '../../../services/ml_api_service';
import type { MlJobService } from '../../../services/job_service';
import { getDefaultDatafeedQuery, getRisonValue } from '../utils/new_job_utils';
@ -25,7 +24,6 @@ interface Dependencies {
dashboardService: DashboardStart;
data: DataPublicPluginStart;
mlApi: MlApi;
mlJobService: MlJobService;
}
export async function resolver(
deps: Dependencies,
@ -38,7 +36,7 @@ export async function resolver(
toRisonString: string,
queryRisonString: string
) {
const { mlApi, mlJobService, timeFilter, kibanaConfig, dashboardService, data } = deps;
const { mlApi, timeFilter, kibanaConfig, dashboardService, data } = deps;
const query = getRisonValue<QueryDslQueryContainer>(queryRisonString, getDefaultDatafeedQuery());
const from = getRisonValue<string>(fromRisonString, '');
@ -59,8 +57,7 @@ export async function resolver(
timeFilter,
dashboardService,
data,
mlApi,
mlJobService
mlApi
);
await jobCreator.createAndStashADJob(
categorizationType,

View file

@ -27,7 +27,6 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { SavedObjectFinder } from '@kbn/saved-objects-finder-plugin/public';
import { extractErrorMessage } from '@kbn/ml-error-utils';
import { useMlJobService } from '../../../../../../../services/job_service';
import { JobCreatorContext } from '../../../job_creator_context';
import type { AdvancedJobCreator } from '../../../../../common/job_creator';
import { resetAdvancedJob } from '../../../../../common/job_creator/util/general';
@ -63,7 +62,6 @@ export const ChangeDataViewModal: FC<Props> = ({ onClose }) => {
const { jobCreator: jc } = useContext(JobCreatorContext);
const jobCreator = jc as AdvancedJobCreator;
const mlJobService = useMlJobService();
const [validating, setValidating] = useState(false);
const [step, setStep] = useState(STEP.PICK_DATA_VIEW);
@ -121,9 +119,8 @@ export const ChangeDataViewModal: FC<Props> = ({ onClose }) => {
const applyDataView = useCallback(() => {
const newIndices = newDataViewTitle.split(',');
jobCreator.indices = newIndices;
resetAdvancedJob(mlJobService, jobCreator, navigateToPath);
resetAdvancedJob(jobCreator, navigateToPath);
// exclude mlJobService from deps
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [jobCreator, newDataViewTitle, navigateToPath]);
return (

View file

@ -10,7 +10,6 @@ import React, { Fragment, useContext } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui';
import { useMlJobService } from '../../../../../../../services/job_service';
import { useNavigateToPath } from '../../../../../../../contexts/kibana';
import { convertToMultiMetricJob } from '../../../../../common/job_creator/util/general';
@ -26,11 +25,10 @@ interface Props {
export const SingleMetricSettings: FC<Props> = ({ setIsValid }) => {
const { jobCreator } = useContext(JobCreatorContext);
const mlJobService = useMlJobService();
const navigateToPath = useNavigateToPath();
const convertToMultiMetric = () => {
convertToMultiMetricJob(mlJobService, jobCreator, navigateToPath);
convertToMultiMetricJob(jobCreator, navigateToPath);
};
return (

View file

@ -18,13 +18,13 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { createResultsUrl } from '../../../../../util/results_url';
import { useMlKibana, useNavigateToPath } from '../../../../../contexts/kibana';
import { PreviousButton } from '../wizard_nav';
import type { StepProps } from '../step_types';
import { WIZARD_STEPS } from '../step_types';
import { JobCreatorContext } from '../job_creator_context';
import type { JobRunner } from '../../../common/job_runner';
import { useMlJobService } from '../../../../../services/job_service';
import { JsonEditorFlyout, EDITOR_MODE } from '../common/json_editor_flyout';
import { isSingleMetricJobCreator, isAdvancedJobCreator } from '../../../common/job_creator';
import { JobDetails } from './components/job_details';
@ -49,7 +49,6 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
http: { basePath },
},
} = useMlKibana();
const mlJobService = useMlJobService();
const navigateToPath = useNavigateToPath();
@ -108,7 +107,7 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
try {
await jobCreator.createJob();
await jobCreator.createDatafeed();
advancedStartDatafeed(mlJobService, showStartModal ? jobCreator : null, navigateToPath);
advancedStartDatafeed(showStartModal ? jobCreator : null, navigateToPath);
} catch (error) {
handleJobCreationError(error);
}
@ -126,7 +125,7 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
}
function viewResults() {
const url = mlJobService.createResultsUrl(
const url = createResultsUrl(
[jobCreator.jobId],
jobCreator.start,
jobCreator.end,
@ -136,11 +135,11 @@ export const SummaryStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) =>
}
function clickResetJob() {
resetJob(mlJobService, jobCreator, navigateToPath);
resetJob(jobCreator, navigateToPath);
}
const convertToAdvanced = () => {
convertToAdvancedJob(mlJobService, jobCreator, navigateToPath);
convertToAdvancedJob(jobCreator, navigateToPath);
};
useEffect(() => {

View file

@ -7,17 +7,16 @@
import type { ApplicationStart } from '@kbn/core/public';
import type { DataViewsContract } from '@kbn/data-views-plugin/public';
import type { MlJobService } from '../../../../services/job_service';
import { jobCloningService } from '../../../../services/job_cloning_service';
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(
mlJobService: MlJobService,
dataViewsService: DataViewsContract,
basePath: string,
navigateToUrl: ApplicationStart['navigateToUrl']
) {
const { createdBy, job, datafeed } = mlJobService.tempJobCloningObjects;
const { createdBy, job, datafeed } = jobCloningService;
if (job && datafeed) {
const dataViewId = await getDataViewIdFromDatafeed(job, datafeed, dataViewsService);

View file

@ -13,9 +13,10 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { getTimeFilterRange, useTimefilter } from '@kbn/ml-date-picker';
import { EVENT_RATE_FIELD_ID } from '@kbn/ml-anomaly-utils';
import { useTimeBuckets } from '@kbn/ml-time-buckets';
import { jobCloningService } from '../../../../services/job_cloning_service';
import { Wizard } from './wizard';
import { WIZARD_STEPS } from '../components/step_types';
import { getJobCreatorTitle } from '../../common/job_creator/util/general';
import { cloneDatafeed, getJobCreatorTitle } from '../../common/job_creator/util/general';
import {
jobCreatorFactory,
isAdvancedJobCreator,
@ -35,7 +36,6 @@ import { JobValidator } from '../../common/job_validator';
import { useDataSource } from '../../../../contexts/ml';
import { useMlApi, useMlKibana } from '../../../../contexts/kibana';
import type { ExistingJobsAndGroups } from '../../../../services/job_service';
import { useMlJobService } from '../../../../services/job_service';
import { useNewJobCapsService } from '../../../../services/new_job_capabilities/new_job_capabilities_service';
import { getNewJobDefaults } from '../../../../services/ml_server_info';
import { useToastNotificationService } from '../../../../services/toast_notification_service';
@ -57,7 +57,6 @@ export const Page: FC<PageProps> = ({ existingJobsAndGroups, jobType }) => {
services: { maps: mapsPlugin, uiSettings },
} = useMlKibana();
const mlApi = useMlApi();
const mlJobService = useMlJobService();
const newJobCapsService = useNewJobCapsService();
const chartInterval = useTimeBuckets(uiSettings);
@ -66,7 +65,6 @@ export const Page: FC<PageProps> = ({ existingJobsAndGroups, jobType }) => {
() =>
jobCreatorFactory(jobType)(
mlApi,
mlJobService,
newJobCapsService,
dataSourceContext.selectedDataView,
dataSourceContext.selectedSavedSearch,
@ -88,53 +86,36 @@ export const Page: FC<PageProps> = ({ existingJobsAndGroups, jobType }) => {
? WIZARD_STEPS.ADVANCED_CONFIGURE_DATAFEED
: WIZARD_STEPS.TIME_RANGE;
let autoSetTimeRange = mlJobService.tempJobCloningObjects.autoSetTimeRange;
mlJobService.tempJobCloningObjects.autoSetTimeRange = false;
let autoSetTimeRange = jobCloningService.autoSetTimeRange;
if (
mlJobService.tempJobCloningObjects.job !== undefined &&
mlJobService.tempJobCloningObjects.datafeed !== undefined
) {
if (jobCloningService.job !== undefined && jobCloningService.datafeed !== undefined) {
// cloning a job
const clonedJob = mlJobService.tempJobCloningObjects.job;
const clonedDatafeed = mlJobService.cloneDatafeed(mlJobService.tempJobCloningObjects.datafeed);
const clonedJob = jobCloningService.job;
const clonedDatafeed = cloneDatafeed(jobCloningService.datafeed);
initCategorizationSettings();
jobCreator.cloneFromExistingJob(clonedJob, clonedDatafeed);
// if we're not skipping the time range, this is a standard job clone, so wipe the jobId
if (mlJobService.tempJobCloningObjects.skipTimeRangeStep === false) {
if (jobCloningService.skipTimeRangeStep === false) {
jobCreator.jobId = '';
} else if (jobType !== JOB_TYPE.ADVANCED) {
firstWizardStep = WIZARD_STEPS.PICK_FIELDS;
}
mlJobService.tempJobCloningObjects.skipTimeRangeStep = false;
mlJobService.tempJobCloningObjects.job = undefined;
mlJobService.tempJobCloningObjects.datafeed = undefined;
mlJobService.tempJobCloningObjects.createdBy = undefined;
if (
mlJobService.tempJobCloningObjects.start !== undefined &&
mlJobService.tempJobCloningObjects.end !== undefined
) {
if (jobCloningService.start !== undefined && jobCloningService.end !== undefined) {
// auto select the start and end dates for the time range picker
jobCreator.setTimeRange(
mlJobService.tempJobCloningObjects.start,
mlJobService.tempJobCloningObjects.end
);
mlJobService.tempJobCloningObjects.start = undefined;
mlJobService.tempJobCloningObjects.end = undefined;
jobCreator.setTimeRange(jobCloningService.start, jobCloningService.end);
} else {
// if not start and end times are set and this is an advanced job,
// auto set the time range based on the index
autoSetTimeRange = autoSetTimeRange || isAdvancedJobCreator(jobCreator);
}
if (mlJobService.tempJobCloningObjects.calendars) {
jobCreator.calendars = mlJobService.tempJobCloningObjects.calendars;
mlJobService.tempJobCloningObjects.calendars = undefined;
if (jobCloningService.calendars) {
jobCreator.calendars = jobCloningService.calendars;
}
jobCloningService.clearJobCloningData();
} else {
// creating a new job
jobCreator.bucketSpan = DEFAULT_BUCKET_SPAN;

View file

@ -15,7 +15,6 @@ import type { MlRoute, PageProps } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { resolver } from '../../../jobs/new_job/job_from_lens';
import { useMlJobService } from '../../../services/job_service';
export const fromLensRouteFactory = (): MlRoute => ({
path: createPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_FROM_LENS),
@ -44,7 +43,6 @@ const PageWrapper: FC<PageProps> = ({ location }) => {
lens,
},
} = useMlKibana();
const mlJobService = useMlJobService();
const { context } = useRouteResolver('full', ['canCreateJob'], {
redirect: () =>
@ -53,7 +51,6 @@ const PageWrapper: FC<PageProps> = ({ location }) => {
dataViews,
lens,
mlApi,
mlJobService,
timeFilter,
kibanaConfig,
dashboardService,

View file

@ -15,7 +15,6 @@ import type { MlRoute, PageProps } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { resolver } from '../../../jobs/new_job/job_from_map';
import { useMlJobService } from '../../../services/job_service';
export const fromMapRouteFactory = (): MlRoute => ({
path: createPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_FROM_MAP),
@ -50,12 +49,11 @@ const PageWrapper: FC<PageProps> = ({ location }) => {
mlServices: { mlApi },
},
} = useMlKibana();
const mlJobService = useMlJobService();
const { context } = useRouteResolver('full', ['canCreateJob'], {
redirect: () =>
resolver(
{ dataViews, mlApi, mlJobService, timeFilter, kibanaConfig, dashboardService },
{ dataViews, mlApi, timeFilter, kibanaConfig, dashboardService },
dashboard,
dataViewId,
embeddable,

View file

@ -15,7 +15,6 @@ import type { MlRoute, PageProps } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { resolver } from '../../../jobs/new_job/job_from_pattern_analysis';
import { useMlJobService } from '../../../services/job_service';
export const fromPatternAnalysisRouteFactory = (): MlRoute => ({
path: createPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_FROM_PATTERN_ANALYSIS),
@ -44,14 +43,12 @@ const PageWrapper: FC<PageProps> = ({ location }) => {
mlServices: { mlApi },
},
} = useMlKibana();
const mlJobService = useMlJobService();
const { context } = useRouteResolver('full', ['canCreateJob'], {
redirect: () =>
resolver(
{
mlApi,
mlJobService,
timeFilter: data.query.timefilter.timefilter,
kibanaConfig,
dashboardService,

View file

@ -21,7 +21,6 @@ import { basicResolvers } from '../../resolvers';
import { preConfiguredJobRedirect } from '../../../jobs/new_job/pages/index_or_search';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { NavigateToPageButton } from '../../components/navigate_to_page_button';
import { useMlJobService } from '../../../services/job_service';
enum MODE {
NEW_JOB,
@ -219,12 +218,11 @@ const PageWrapper: FC<IndexOrSearchPageProps> = ({ nextStepPath, mode, extraButt
data: { dataViews: dataViewsService },
},
} = useMlKibana();
const mlJobService = useMlJobService();
const newJobResolvers = {
...basicResolvers(),
preConfiguredJobRedirect: () =>
preConfiguredJobRedirect(mlJobService, dataViewsService, basePath.get(), navigateToUrl),
preConfiguredJobRedirect(dataViewsService, basePath.get(), navigateToUrl),
};
const { context } = useRouteResolver(

View file

@ -17,11 +17,9 @@ import { useMlApi, useMlKibana, useNavigateToPath } from '../../../contexts/kiba
import type { MlRoute, PageProps } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { mlJobServiceFactory } from '../../../services/job_service';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { useCreateADLinks } from '../../../components/custom_hooks/use_create_ad_links';
import { DataSourceContextProvider } from '../../../contexts/ml';
import { useToastNotificationService } from '../../../services/toast_notification_service';
const Page = dynamic(async () => ({
default: (await import('../../../jobs/new_job/recognize')).Page,
@ -56,12 +54,10 @@ export const checkViewOrCreateRouteFactory = (): MlRoute => ({
const PageWrapper: FC<PageProps> = ({ location }) => {
const { id } = parse(location.search, { sort: false });
const mlApi = useMlApi();
const toastNotificationService = useToastNotificationService();
const { context, results } = useRouteResolver('full', ['canGetJobs'], {
...basicResolvers(),
existingJobsAndGroups: () =>
mlJobServiceFactory(toastNotificationService, mlApi).getJobAndGroupIds(),
existingJobsAndGroups: () => mlApi.jobs.getAllJobAndGroupIds(),
});
return (

View file

@ -19,8 +19,6 @@ import type { MlRoute, PageProps } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { JOB_TYPE } from '../../../../../common/constants/new_job';
import { mlJobServiceFactory } from '../../../services/job_service';
import { useToastNotificationService } from '../../../services/toast_notification_service';
import {
loadNewJobCapabilities,
ANOMALY_DETECTOR,
@ -210,8 +208,6 @@ const PageWrapper: FC<WizardPageProps> = ({ location, jobType }) => {
mlServices: { mlApi },
},
} = useMlKibana();
const toastNotificationService = useToastNotificationService();
const { context, results } = useRouteResolver('full', ['canGetJobs', 'canCreateJob'], {
...basicResolvers(),
// TODO useRouteResolver should be responsible for the redirect
@ -225,8 +221,7 @@ const PageWrapper: FC<WizardPageProps> = ({ location, jobType }) => {
savedSearchService,
ANOMALY_DETECTOR
),
existingJobsAndGroups: () =>
mlJobServiceFactory(toastNotificationService, mlApi).getJobAndGroupIds(),
existingJobsAndGroups: () => mlApi.jobs.getAllJobAndGroupIds(),
});
return (

View file

@ -0,0 +1,130 @@
/*
* 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 { isPopulatedObject } from '@kbn/ml-is-populated-object';
import type { JobCreator } from '../jobs/new_job/common/job_creator/job_creator';
interface TempJobCloningData {
createdBy: any;
datafeed: any;
job: any;
skipTimeRangeStep: boolean;
start?: any;
end?: any;
calendars: any;
autoSetTimeRange?: boolean;
}
export class JobCloningService {
// tempJobCloningData -> used to pass a job object between the job management page and
// and the advanced wizard.
// if populated when loading the advanced wizard, the job is used for cloning.
// if populated when loading the job management page, the start datafeed modal
// is automatically opened.
private tempJobCloningData: TempJobCloningData = {
createdBy: undefined,
datafeed: undefined,
job: undefined,
skipTimeRangeStep: false,
start: undefined,
end: undefined,
calendars: undefined,
autoSetTimeRange: false,
};
public getJobCloningData(): Readonly<TempJobCloningData> {
return this.tempJobCloningData;
}
clearJobCloningData() {
this.tempJobCloningData = {
createdBy: undefined,
datafeed: undefined,
job: undefined,
skipTimeRangeStep: false,
start: undefined,
end: undefined,
calendars: undefined,
autoSetTimeRange: false,
};
}
stashJobForCloning(
jobCreator: JobCreator,
skipTimeRangeStep: boolean,
includeTimeRange: boolean,
autoSetTimeRange: boolean = false
) {
const tempJobCloningData: TempJobCloningData = {
job: jobCreator.jobConfig,
datafeed: jobCreator.datafeedConfig,
createdBy: jobCreator.createdBy ?? undefined,
// skip over the time picker step of the wizard
skipTimeRangeStep,
calendars: jobCreator.calendars,
...(includeTimeRange === true && autoSetTimeRange === false
? // auto select the start and end dates of the time picker
{
start: jobCreator.start,
end: jobCreator.end,
}
: { autoSetTimeRange: true }),
};
this.tempJobCloningData = tempJobCloningData;
}
public checkForAutoStartDatafeed() {
const job = this.tempJobCloningData.job;
const datafeed = this.tempJobCloningData.datafeed;
if (job !== undefined) {
this.tempJobCloningData.job = undefined;
this.tempJobCloningData.datafeed = undefined;
this.tempJobCloningData.createdBy = undefined;
const hasDatafeed = isPopulatedObject(datafeed);
const datafeedId = hasDatafeed ? datafeed.datafeed_id : '';
return {
id: job.job_id,
hasDatafeed,
latestTimestampSortValue: 0,
datafeedId,
};
}
}
public stashJobCloningData(config: TempJobCloningData) {
this.tempJobCloningData = config;
}
public get createdBy() {
return this.tempJobCloningData.createdBy;
}
public get datafeed() {
return this.tempJobCloningData.datafeed;
}
public get job() {
return this.tempJobCloningData.job;
}
public get skipTimeRangeStep() {
return this.tempJobCloningData.skipTimeRangeStep;
}
public get start() {
return this.tempJobCloningData.start;
}
public get end() {
return this.tempJobCloningData.end;
}
public get calendars() {
return this.tempJobCloningData.calendars;
}
public get autoSetTimeRange() {
return this.tempJobCloningData.autoSetTimeRange;
}
}
export const jobCloningService = new JobCloningService();

View file

@ -5,12 +5,8 @@
* 2.0.
*/
import type { TimeRange } from '@kbn/data-plugin/common/query/timefilter/types';
import type { CombinedJob, Datafeed, Job } from '../../../common/types/anomaly_detection_jobs';
import type { Calendar } from '../../../common/types/calendars';
import type { ToastNotificationService } from './toast_notification_service';
import type { CombinedJob } from '../../../common/types/anomaly_detection_jobs';
import type { MlApi } from './ml_api_service';
import type { JobCreatorType } from '../jobs/new_job/common/job_creator';
export interface ExistingJobsAndGroups {
jobIds: string[];
@ -19,50 +15,13 @@ export interface ExistingJobsAndGroups {
export declare interface MlJobService {
jobs: CombinedJob[];
createResultsUrlForJobs: (jobs: any[], target: string, timeRange?: TimeRange) => string;
tempJobCloningObjects: {
createdBy?: string;
datafeed?: Datafeed;
job?: Job;
skipTimeRangeStep: boolean;
start?: number;
end?: number;
calendars: Calendar[] | undefined;
autoSetTimeRange?: boolean;
};
skipTimeRangeStep: boolean;
saveNewJob(job: Job): Promise<any>;
cloneDatafeed(Datafeed: Datafeed): Datafeed;
openJob(jobId: string): Promise<any>;
saveNewDatafeed(datafeedConfig: any, jobId: string): Promise<any>;
startDatafeed(
datafeedId: string,
jobId: string,
start: number | undefined,
end: number | undefined
): Promise<any>;
forceStartDatafeeds(
dIds: string[],
start: number | undefined,
end: number | undefined
): Promise<any>;
createResultsUrl(jobId: string[], start: number, end: number, location: string): string;
getJobAndGroupIds(): Promise<ExistingJobsAndGroups>;
getJob(jobId: string): CombinedJob;
loadJobsWrapper(): Promise<CombinedJob[]>;
customUrlsByJob: Record<string, any[]>;
detectorsByJob: Record<string, any>;
stashJobForCloning(
jobCreator: JobCreatorType,
skipTimeRangeStep: boolean = false,
includeTimeRange: boolean = false,
autoSetTimeRange: boolean = false
): void;
}
export const mlJobServiceFactory: (
toastNotificationService: ToastNotificationService,
mlApi: MlApi
) => MlJobService;
export const mlJobServiceFactory: (mlApi: MlApi) => MlJobService;
export const useMlJobService: () => MlJobService;

View file

@ -5,42 +5,21 @@
* 2.0.
*/
import { cloneDeep, each, find, get, isNumber } from 'lodash';
import moment from 'moment';
import { validateTimeRange, TIME_FORMAT } from '@kbn/ml-date-utils';
import { cloneDeep, each, find, get } from 'lodash';
import { parseInterval } from '../../../common/util/parse_interval';
import { createDatafeedId } from '../../../common/util/job_utils';
import { isWebUrl } from '../util/url_utils';
import { useMlApi } from '../contexts/kibana';
import { useToastNotificationService } from './toast_notification_service';
let jobs = [];
let datafeedIds = {};
class JobService {
constructor(toastNotificationService, ml) {
this.toastNotificationService = toastNotificationService;
constructor(ml) {
this.ml = ml;
// tempJobCloningObjects -> used to pass a job object between the job management page and
// and the advanced wizard.
// if populated when loading the advanced wizard, the job is used for cloning.
// if populated when loading the job management page, the start datafeed modal
// is automatically opened.
this.tempJobCloningObjects = {
createdBy: undefined,
datafeed: undefined,
job: undefined,
skipTimeRangeStep: false,
start: undefined,
end: undefined,
calendars: undefined,
autoSetTimeRange: false,
};
this.jobs = [];
// Provide ready access to widely used basic job properties.
@ -112,7 +91,6 @@ class JobService {
function error(err) {
console.log('jobService error getting list of jobs:', err);
this.toastNotificationService.displayErrorToast(err);
reject({ jobs, err });
}
});
@ -168,7 +146,7 @@ class JobService {
}
}
const datafeedId = this.getDatafeedId(jobId);
const datafeedId = createDatafeedId(jobId);
this.loadDatafeeds(datafeedId).then((datafeedsResp) => {
for (let i = 0; i < jobs.length; i++) {
@ -195,7 +173,6 @@ class JobService {
function error(err) {
console.log('JobService error getting list of jobs:', err);
this.toastNotificationService.displayErrorToast(err);
reject({ jobs, err });
}
});
@ -235,56 +212,11 @@ class JobService {
function error(err) {
console.log('loadDatafeeds error getting list of datafeeds:', err);
this.toastNotificationService.displayErrorToast(err);
reject({ jobs, err });
}
});
}
updateSingleJobDatafeedState(jobId) {
return new Promise((resolve, reject) => {
const datafeedId = this.getDatafeedId(jobId);
this.ml
.getDatafeedStats({ datafeedId })
.then((resp) => {
// console.log('updateSingleJobCounts controller query response:', resp);
const datafeeds = resp.datafeeds;
let state = 'UNKNOWN';
if (datafeeds && datafeeds.length) {
state = datafeeds[0].state;
}
resolve(state);
})
.catch((resp) => {
reject(resp);
});
});
}
saveNewJob(job) {
// run then and catch through the same check
function func(resp) {
console.log('Response for job query:', resp);
const success = checkSaveResponse(resp, job);
return { success, job, resp };
}
// return the promise chain
return this.ml.addJob({ jobId: job.job_id, job }).then(func).catch(func);
}
cloneDatafeed(datafeed) {
const tempDatafeed = cloneDeep(datafeed);
// remove parts of the datafeed config which should not be copied
if (tempDatafeed) {
delete tempDatafeed.datafeed_id;
delete tempDatafeed.job_id;
}
return tempDatafeed;
}
// find a job based on the id
getJob(jobId) {
const job = find(jobs, (j) => {
@ -293,175 +225,6 @@ class JobService {
return job;
}
openJob(jobId) {
return this.ml.openJob({ jobId });
}
closeJob(jobId) {
return this.ml.closeJob({ jobId });
}
saveNewDatafeed(datafeedConfig, jobId) {
const datafeedId = `datafeed-${jobId}`;
datafeedConfig.job_id = jobId;
return this.ml.addDatafeed({
datafeedId,
datafeedConfig,
});
}
// start the datafeed for a given job
// refresh the job state on start success
startDatafeed(datafeedId, jobId, start, end) {
return new Promise((resolve, reject) => {
// if the end timestamp is a number, add one ms to it to make it
// inclusive of the end of the data
if (isNumber(end)) {
end++;
}
this.ml
.startDatafeed({
datafeedId,
start,
end,
})
.then((resp) => {
resolve(resp);
})
.catch((err) => {
console.log('jobService error starting datafeed:', err);
reject(err);
});
});
}
forceStartDatafeeds(dIds, start, end) {
return this.ml.jobs.forceStartDatafeeds(dIds, start, end);
}
stopDatafeeds(dIds) {
return this.ml.jobs.stopDatafeeds(dIds);
}
deleteJobs(jIds, deleteUserAnnotations, deleteAlertingRules) {
return this.ml.jobs.deleteJobs(jIds, deleteUserAnnotations, deleteAlertingRules);
}
closeJobs(jIds) {
return this.ml.jobs.closeJobs(jIds);
}
resetJobs(jIds, deleteUserAnnotations) {
return this.ml.jobs.resetJobs(jIds, deleteUserAnnotations);
}
validateDetector(detector) {
return new Promise((resolve, reject) => {
if (detector) {
this.ml
.validateDetector({ detector })
.then((resp) => {
resolve(resp);
})
.catch((resp) => {
reject(resp);
});
} else {
reject({});
}
});
}
getDatafeedId(jobId) {
let datafeedId = datafeedIds[jobId];
if (datafeedId === undefined) {
datafeedId = `datafeed-${jobId}`;
}
return datafeedId;
}
// get the list of job group ids as well as how many jobs are in each group
getJobGroups() {
const groups = [];
const tempGroups = {};
this.jobs.forEach((job) => {
if (Array.isArray(job.groups)) {
job.groups.forEach((group) => {
if (tempGroups[group] === undefined) {
tempGroups[group] = [job];
} else {
tempGroups[group].push(job);
}
});
}
});
each(tempGroups, (js, id) => {
groups.push({ id, jobs: js });
});
return groups;
}
createResultsUrlForJobs(jobsList, resultsPage, timeRange) {
return createResultsUrlForJobs(jobsList, resultsPage, timeRange);
}
createResultsUrl(jobIds, from, to, resultsPage) {
return createResultsUrl(jobIds, from, to, resultsPage);
}
async getJobAndGroupIds() {
try {
return await this.ml.jobs.getAllJobAndGroupIds();
} catch (error) {
return {
jobIds: [],
groupIds: [],
};
}
}
stashJobForCloning(jobCreator, skipTimeRangeStep, includeTimeRange, autoSetTimeRange) {
const tempJobCloningObjects = {
job: jobCreator.jobConfig,
datafeed: jobCreator.datafeedConfig,
createdBy: jobCreator.createdBy ?? undefined,
// skip over the time picker step of the wizard
skipTimeRangeStep,
calendars: jobCreator.calendars,
...(includeTimeRange === true && autoSetTimeRange === false
? // auto select the start and end dates of the time picker
{
start: jobCreator.start,
end: jobCreator.end,
}
: { autoSetTimeRange: true }),
};
this.tempJobCloningObjects = tempJobCloningObjects;
}
}
// private function used to check the job saving response
function checkSaveResponse(resp, origJob) {
if (resp) {
if (resp.job_id) {
if (resp.job_id === origJob.job_id) {
console.log('checkSaveResponse(): save successful');
return true;
}
} else {
if (resp.errorCode) {
console.log('checkSaveResponse(): save failed', resp);
return false;
}
}
} else {
console.log('checkSaveResponse(): response is empty');
return false;
}
}
function processBasicJobInfo(localJobService, jobsList) {
@ -521,90 +284,16 @@ function processBasicJobInfo(localJobService, jobsList) {
return processedJobsList;
}
function createResultsUrlForJobs(jobsList, resultsPage, userTimeRange) {
let from = undefined;
let to = undefined;
let mode = 'absolute';
const jobIds = jobsList.map((j) => j.id);
// if the custom default time filter is set and enabled in advanced settings
// if time is either absolute date or proper datemath format
if (validateTimeRange(userTimeRange)) {
from = userTimeRange.from;
to = userTimeRange.to;
// if both pass datemath's checks but are not technically absolute dates, use 'quick'
// e.g. "now-15m" "now+1d"
const fromFieldAValidDate = moment(userTimeRange.from).isValid();
const toFieldAValidDate = moment(userTimeRange.to).isValid();
if (!fromFieldAValidDate && !toFieldAValidDate) {
return createResultsUrl(jobIds, from, to, resultsPage, 'quick');
}
} else {
// if time range is specified but with incorrect format
// change back to the default time range but alert the user
// that the advanced setting config is invalid
if (userTimeRange) {
mode = 'invalid';
}
if (jobsList.length === 1) {
from = jobsList[0].earliestTimestampMs;
to = jobsList[0].latestResultsTimestampMs; // Will be max(latest source data, latest bucket results)
} else {
const jobsWithData = jobsList.filter((j) => j.earliestTimestampMs !== undefined);
if (jobsWithData.length > 0) {
from = Math.min(...jobsWithData.map((j) => j.earliestTimestampMs));
to = Math.max(...jobsWithData.map((j) => j.latestResultsTimestampMs));
}
}
}
const fromString = moment(from).format(TIME_FORMAT); // Defaults to 'now' if 'from' is undefined
const toString = moment(to).format(TIME_FORMAT); // Defaults to 'now' if 'to' is undefined
return createResultsUrl(jobIds, fromString, toString, resultsPage, mode);
}
function createResultsUrl(jobIds, start, end, resultsPage, mode = 'absolute') {
const idString = jobIds.map((j) => `'${j}'`).join(',');
let from;
let to;
let path = '';
if (resultsPage !== undefined) {
path += resultsPage;
}
if (mode === 'quick') {
from = start;
to = end;
} else {
from = moment(start).toISOString();
to = moment(end).toISOString();
}
path += `?_g=(ml:(jobIds:!(${idString}))`;
path += `,refreshInterval:(display:Off,pause:!t,value:0),time:(from:'${from}'`;
path += `,to:'${to}'`;
if (mode === 'invalid') {
path += `,mode:invalid`;
}
path += "))&_a=(query:(query_string:(analyze_wildcard:!t,query:'*')))";
return path;
}
// This is to retain the singleton behavior of the previous direct instantiation and export.
let mlJobService;
export const mlJobServiceFactory = (toastNotificationService, mlApi) => {
export const mlJobServiceFactory = (mlApi) => {
if (mlJobService) return mlJobService;
mlJobService = new JobService(toastNotificationService, mlApi);
mlJobService = new JobService(mlApi);
return mlJobService;
};
export const useMlJobService = () => {
const toastNotificationService = useToastNotificationService();
const mlApi = useMlApi();
return mlJobServiceFactory(toastNotificationService, mlApi);
return mlJobServiceFactory(mlApi);
};

View file

@ -11,6 +11,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { RuntimeMappings } from '@kbn/ml-runtime-field-utils';
import { isNumber } from 'lodash';
import { ML_INTERNAL_BASE_PATH } from '../../../../common/constants/app';
import type {
MlServerDefaults,
@ -299,6 +300,12 @@ export function mlApiProvider(httpService: HttpService) {
start?: number;
end?: number;
}) {
// if the end timestamp is a number, add one ms to it to make it
// inclusive of the end of the data
if (isNumber(end)) {
end++;
}
const body = JSON.stringify({
...(start !== undefined ? { start } : {}),
...(end !== undefined ? { end } : {}),

View file

@ -160,7 +160,7 @@ export class TimeSeriesExplorer extends React.Component {
this.mlApi = constructorContext.services.mlServices.mlApi;
this.mlForecastService = forecastServiceFactory(this.mlApi);
this.mlIndexUtils = indexServiceFactory(this.dataViewsService);
this.mlJobService = mlJobServiceFactory(this.toastNotificationService, this.mlApi);
this.mlJobService = mlJobServiceFactory(this.mlApi);
this.mlResultsService = mlResultsServiceProvider(this.mlApi);
this.mlTimeSeriesExplorer = timeSeriesExplorerServiceFactory(
constructorContext.services.uiSettings,

View file

@ -17,7 +17,6 @@ import { HttpService } from '../services/http_service';
import { mlApiProvider } from '../services/ml_api_service';
import { mlUsageCollectionProvider } from '../services/usage_collection';
import { mlJobServiceFactory } from '../services/job_service';
import { toastNotificationServiceProvider } from '../services/toast_notification_service';
import { indexServiceFactory } from './index_service';
/**
@ -30,8 +29,7 @@ export function getMlGlobalServices(
) {
const httpService = new HttpService(coreStart.http);
const mlApi = mlApiProvider(httpService);
const toastNotificationService = toastNotificationServiceProvider(coreStart.notifications.toasts);
const mlJobService = mlJobServiceFactory(toastNotificationService, mlApi);
const mlJobService = mlJobServiceFactory(mlApi);
// Note on the following services:
// - `mlIndexUtils` is just instantiated here to be passed on to `mlFieldFormatService`,
// but it's not being made available as part of global services. Since it's just

View file

@ -0,0 +1,95 @@
/*
* 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 moment from 'moment';
import { validateTimeRange, TIME_FORMAT } from '@kbn/ml-date-utils';
import type { TimeRange } from '@kbn/es-query';
import type { MlSummaryJob } from '../../../common';
export function createResultsUrlForJobs(
jobsList: MlSummaryJob[],
resultsPage: string,
userTimeRange: TimeRange
) {
let from;
let to;
let mode = 'absolute';
const jobIds = jobsList.map((j) => j.id);
// if the custom default time filter is set and enabled in advanced settings
// if time is either absolute date or proper datemath format
if (validateTimeRange(userTimeRange)) {
from = userTimeRange.from;
to = userTimeRange.to;
// if both pass datemath's checks but are not technically absolute dates, use 'quick'
// e.g. "now-15m" "now+1d"
const fromFieldAValidDate = moment(userTimeRange.from).isValid();
const toFieldAValidDate = moment(userTimeRange.to).isValid();
if (!fromFieldAValidDate && !toFieldAValidDate) {
return createResultsUrl(jobIds, from, to, resultsPage, 'quick');
}
} else {
// if time range is specified but with incorrect format
// change back to the default time range but alert the user
// that the advanced setting config is invalid
if (userTimeRange) {
mode = 'invalid';
}
if (jobsList.length === 1) {
from = jobsList[0].earliestTimestampMs;
to = jobsList[0].latestResultsTimestampMs; // Will be max(latest source data, latest bucket results)
} else {
const jobsWithData = jobsList.filter((j) => j.earliestTimestampMs !== undefined);
if (jobsWithData.length > 0) {
from = Math.min(...jobsWithData.map((j) => j.earliestTimestampMs!));
to = Math.max(...jobsWithData.map((j) => j.latestResultsTimestampMs!));
}
}
}
const fromString = moment(from).format(TIME_FORMAT); // Defaults to 'now' if 'from' is undefined
const toString = moment(to).format(TIME_FORMAT); // Defaults to 'now' if 'to' is undefined
return createResultsUrl(jobIds, fromString, toString, resultsPage, mode);
}
export function createResultsUrl(
jobIds: string[],
start: number | string,
end: number | string,
resultsPage: string,
mode = 'absolute'
) {
const idString = jobIds.map((j) => `'${j}'`).join(',');
let from;
let to;
let path = '';
if (resultsPage !== undefined) {
path += resultsPage;
}
if (mode === 'quick') {
from = start;
to = end;
} else {
from = moment(start).toISOString();
to = moment(end).toISOString();
}
path += `?_g=(ml:(jobIds:!(${idString}))`;
path += `,refreshInterval:(display:Off,pause:!t,value:0),time:(from:'${from}'`;
path += `,to:'${to}'`;
if (mode === 'invalid') {
path += `,mode:invalid`;
}
path += "))&_a=(query:(query_string:(analyze_wildcard:!t,query:'*')))";
return path;
}

View file

@ -22,7 +22,6 @@ export const getAnomalyChartsServiceDependencies = async (
{ mlApiProvider },
{ mlJobServiceFactory },
{ mlResultsServiceProvider },
{ toastNotificationServiceProvider },
] = await Promise.all([
await import('../../application/services/anomaly_detector_service'),
await import('../../application/services/field_format_service_factory'),
@ -30,13 +29,11 @@ export const getAnomalyChartsServiceDependencies = async (
await import('../../application/services/ml_api_service'),
await import('../../application/services/job_service'),
await import('../../application/services/results_service'),
await import('../../application/services/toast_notification_service'),
]);
const httpService = new HttpService(coreStart.http);
const anomalyDetectorService = new AnomalyDetectorService(httpService);
const mlApi = mlApiProvider(httpService);
const toastNotificationService = toastNotificationServiceProvider(coreStart.notifications.toasts);
const mlJobService = mlJobServiceFactory(toastNotificationService, mlApi);
const mlJobService = mlJobServiceFactory(mlApi);
const mlResultsService = mlResultsServiceProvider(mlApi);
const anomalyExplorerService = new AnomalyExplorerChartsService(
pluginsStart.data.query.timefilter.timefilter,

View file

@ -34,8 +34,6 @@ import {
} from '../../../../application/jobs/new_job/job_from_pattern_analysis';
import { useMlFromLensKibanaContext } from '../../common/context';
import { JobDetails, type CreateADJobParams } from '../../common/job_details';
import { mlJobServiceFactory } from '../../../../application/services/job_service';
import { toastNotificationServiceProvider } from '../../../../application/services/toast_notification_service';
interface Props {
dataView: DataView;
@ -51,13 +49,9 @@ export const CreateJob: FC<Props> = ({ dataView, field, query, timeRange }) => {
share,
uiSettings,
dashboardService,
notifications: { toasts },
mlServices: { mlApi },
},
} = useMlFromLensKibanaContext();
const toastNotificationService = toastNotificationServiceProvider(toasts);
const mlJobService = mlJobServiceFactory(toastNotificationService, mlApi);
const [categorizationType, setCategorizationType] = useState<CategorizationType>(
CATEGORIZATION_TYPE.COUNT
);
@ -96,10 +90,9 @@ export const CreateJob: FC<Props> = ({ dataView, field, query, timeRange }) => {
data.query.timefilter.timefilter,
dashboardService,
data,
mlApi,
mlJobService
mlApi
),
[dashboardService, data, mlApi, mlJobService, uiSettings]
[dashboardService, data, mlApi, uiSettings]
);
function createADJobInWizard() {

View file

@ -19,8 +19,6 @@ import { JOB_TYPE } from '../../../../../../common/constants/new_job';
import { useMlFromLensKibanaContext } from '../../../common/context';
import type { CreateADJobParams } from '../../../common/job_details';
import { JobDetails } from '../../../common/job_details';
import { mlJobServiceFactory } from '../../../../../application/services/job_service';
import { toastNotificationServiceProvider } from '../../../../../application/services/toast_notification_service';
interface Props {
layer: LayerResult;
@ -36,12 +34,9 @@ export const CompatibleLayer: FC<Props> = ({ layer, layerIndex, embeddable }) =>
uiSettings,
lens,
dashboardService,
notifications: { toasts },
mlServices: { mlApi },
},
} = useMlFromLensKibanaContext();
const toastNotificationService = toastNotificationServiceProvider(toasts);
const mlJobService = mlJobServiceFactory(toastNotificationService, mlApi);
const quickJobCreator = useMemo(
() =>
@ -51,8 +46,7 @@ export const CompatibleLayer: FC<Props> = ({ layer, layerIndex, embeddable }) =>
uiSettings,
data.query.timefilter.timefilter,
dashboardService,
mlApi,
mlJobService
mlApi
),
// eslint-disable-next-line react-hooks/exhaustive-deps
[data, uiSettings]

View file

@ -29,8 +29,6 @@ import {
import { useMlFromLensKibanaContext } from '../../../common/context';
import type { CreateADJobParams } from '../../../common/job_details';
import { JobDetails } from '../../../common/job_details';
import { mlJobServiceFactory } from '../../../../../application/services/job_service';
import { toastNotificationServiceProvider } from '../../../../../application/services/toast_notification_service';
interface DropDownLabel {
label: string;
@ -53,12 +51,9 @@ export const CompatibleLayer: FC<Props> = ({ embeddable, layer, layerIndex }) =>
share,
uiSettings,
dashboardService,
notifications: { toasts },
mlServices: { mlApi },
},
} = useMlFromLensKibanaContext();
const toastNotificationService = toastNotificationServiceProvider(toasts);
const mlJobService = mlJobServiceFactory(toastNotificationService, mlApi);
const quickJobCreator = useMemo(
() =>
@ -67,8 +62,7 @@ export const CompatibleLayer: FC<Props> = ({ embeddable, layer, layerIndex }) =>
uiSettings,
data.query.timefilter.timefilter,
dashboardService,
mlApi,
mlJobService
mlApi
),
// eslint-disable-next-line react-hooks/exhaustive-deps
[data, uiSettings]

View file

@ -50,7 +50,7 @@ export const getMlServices = async (
const anomalyDetectorService = new AnomalyDetectorService(httpService);
const mlApi = mlApiProvider(httpService);
const toastNotificationService = toastNotificationServiceProvider(coreStart.notifications.toasts);
const mlJobService = mlJobServiceFactory(toastNotificationService, mlApi);
const mlJobService = mlJobServiceFactory(mlApi);
const mlResultsService = mlResultsServiceProvider(mlApi);
const mlTimeSeriesSearchService = timeSeriesSearchServiceFactory(mlResultsService, mlApi);
const mlTimeSeriesExplorerService = timeSeriesExplorerServiceFactory(

View file

@ -14,6 +14,7 @@ import {
getSingleMetricViewerJobErrorMessage,
parseTimeIntervalForJob,
isJobWithGeoData,
createDatafeedId,
} from '../../../common/util/job_utils';
import { JOB_STATE, DATAFEED_STATE } from '../../../common/constants/states';
import type { JobAction } from '../../../common/constants/job_actions';
@ -626,7 +627,7 @@ export function jobsProvider(
}
async function getLookBackProgress(jobId: string, start: number, end: number) {
const datafeedId = `datafeed-${jobId}`;
const datafeedId = createDatafeedId(jobId);
const [body, isRunning] = await Promise.all([
mlClient.getJobStats({ job_id: jobId }),
isDatafeedRunning(datafeedId),