diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts
index 8c53e7b1f5a87..6bcba719deaee 100644
--- a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts
+++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts
@@ -7,6 +7,7 @@
import { Moment } from 'moment';
+import { MlCustomSettings } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { CombinedJob, CombinedJobWithStats } from './combined_job';
import type { MlAnomalyDetectionAlertRule } from '../alerts';
import type { MlJobBlocked } from './job';
@@ -39,6 +40,7 @@ export interface MlSummaryJob {
alertingRules?: MlAnomalyDetectionAlertRule[];
jobTags: Record;
bucketSpanSeconds: number;
+ customSettings?: MlCustomSettings;
}
export interface AuditMessage {
diff --git a/x-pack/plugins/ml/public/application/components/delete_job_check_modal/delete_job_check_modal.tsx b/x-pack/plugins/ml/public/application/components/delete_job_check_modal/delete_job_check_modal.tsx
index 5507cbfe155b4..6834f16386ce2 100644
--- a/x-pack/plugins/ml/public/application/components/delete_job_check_modal/delete_job_check_modal.tsx
+++ b/x-pack/plugins/ml/public/application/components/delete_job_check_modal/delete_job_check_modal.tsx
@@ -20,10 +20,12 @@ import {
EuiButton,
EuiLoadingSpinner,
EuiText,
+ EuiSpacer,
} from '@elastic/eui';
import { JobType, CanDeleteJobResponse } from '../../../../common/types/saved_objects';
import { useMlApiContext } from '../../contexts/kibana';
import { useToastNotificationService } from '../../services/toast_notification_service';
+import { ManagedJobsWarningCallout } from '../../jobs/jobs_list/components/confirm_modals/managed_jobs_warning_callout';
const shouldUnTagLabel = i18n.translate('xpack.ml.deleteJobCheckModal.shouldUnTagLabel', {
defaultMessage: 'Remove job from current space',
@@ -58,7 +60,8 @@ function getRespSummary(resp: CanDeleteJobResponse): JobCheckRespSummary {
function getModalContent(
jobIds: string[],
- respSummary: JobCheckRespSummary
+ respSummary: JobCheckRespSummary,
+ hasManagedJob?: boolean
): ModalContentReturnType {
const { canDelete, canRemoveFromSpace, canTakeAnyAction } = respSummary;
@@ -107,13 +110,30 @@ function getModalContent(
/>
),
modalText: (
-
-
-
+ <>
+ {hasManagedJob ? (
+ <>
+
+
+ >
+ ) : null}
+
+
+
+
+ >
),
};
} else if (canRemoveFromSpace) {
@@ -125,13 +145,27 @@ function getModalContent(
/>
),
modalText: (
-
-
-
+ <>
+ {hasManagedJob ? (
+ <>
+
+
+ >
+ ) : null}
+
+
+
+
+ >
),
};
} else {
@@ -146,6 +180,7 @@ interface Props {
jobType: JobType;
jobIds: string[];
setDidUntag?: React.Dispatch>;
+ hasManagedJob?: boolean;
}
export const DeleteJobCheckModal: FC = ({
@@ -155,6 +190,7 @@ export const DeleteJobCheckModal: FC = ({
jobType,
jobIds,
setDidUntag,
+ hasManagedJob,
}) => {
const [buttonContent, setButtonContent] = useState();
const [modalContent, setModalContent] = useState();
@@ -180,7 +216,7 @@ export const DeleteJobCheckModal: FC = ({
return;
}
setJobCheckRespSummary(respSummary);
- const { buttonText, modalText } = getModalContent(jobIds, respSummary);
+ const { buttonText, modalText } = getModalContent(jobIds, respSummary, hasManagedJob);
setButtonContent(buttonText);
setModalContent(modalText);
});
@@ -188,7 +224,7 @@ export const DeleteJobCheckModal: FC = ({
setDidUntag(false);
}
setIsLoading(false);
- }, []);
+ }, [hasManagedJob]);
const onUntagClick = async () => {
setIsUntagging(true);
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/confirm_modals/close_jobs_confirm_modal.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/confirm_modals/close_jobs_confirm_modal.tsx
new file mode 100644
index 0000000000000..5e7b7a18e3363
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/confirm_modals/close_jobs_confirm_modal.tsx
@@ -0,0 +1,136 @@
+/*
+ * 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 React, { FC, useState, useEffect, useCallback, useMemo } from 'react';
+import { FormattedMessage } from '@kbn/i18n-react';
+import {
+ EuiSpacer,
+ EuiModal,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
+ EuiModalBody,
+ EuiModalFooter,
+ EuiButtonEmpty,
+ EuiButton,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { MlSummaryJob } from '../../../../../../common/types/anomaly_detection_jobs';
+import { isManagedJob } from '../../../jobs_utils';
+import { closeJobs } from '../utils';
+import { ManagedJobsWarningCallout } from './managed_jobs_warning_callout';
+
+type ShowFunc = (jobs: MlSummaryJob[]) => void;
+
+interface Props {
+ setShowFunction(showFunc: ShowFunc): void;
+ unsetShowFunction(): void;
+ refreshJobs(): void;
+}
+
+export const CloseJobsConfirmModal: FC = ({
+ setShowFunction,
+ unsetShowFunction,
+ refreshJobs,
+}) => {
+ const [modalVisible, setModalVisible] = useState(false);
+ const [hasManagedJob, setHasManaged] = useState(true);
+ const [jobsToReset, setJobsToReset] = useState([]);
+
+ const jobIds = useMemo(() => jobsToReset.map(({ id }) => id), [jobsToReset]);
+
+ useEffect(() => {
+ if (typeof setShowFunction === 'function') {
+ setShowFunction(showModal);
+ }
+ return () => {
+ if (typeof unsetShowFunction === 'function') {
+ unsetShowFunction();
+ }
+ };
+ }, []);
+
+ const showModal = useCallback((jobs: MlSummaryJob[]) => {
+ setJobsToReset(jobs);
+
+ if (jobs.some((j) => isManagedJob(j))) {
+ setModalVisible(true);
+ setHasManaged(true);
+ }
+ }, []);
+
+ const closeModal = useCallback(() => {
+ setModalVisible(false);
+ setHasManaged(false);
+ }, []);
+
+ if (modalVisible === false) {
+ return null;
+ }
+
+ if (hasManagedJob) {
+ const title = (
+
+ );
+
+ return (
+
+
+ {title}
+
+
+
+
+ <>
+
+
+
+
+
+
+ {
+ closeJobs(jobsToReset, refreshJobs);
+ closeModal();
+ }}
+ fill
+ color="danger"
+ data-test-subj="mlCloseJobsConfirmModalButton"
+ >
+
+
+
+ >
+
+ );
+ } else {
+ return <>>;
+ }
+};
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/confirm_modals/managed_jobs_warning_callout.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/confirm_modals/managed_jobs_warning_callout.tsx
new file mode 100644
index 0000000000000..5d6a72e927ecb
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/confirm_modals/managed_jobs_warning_callout.tsx
@@ -0,0 +1,38 @@
+/*
+ * 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 { EuiCallOut, EuiSpacer } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n-react';
+import React from 'react';
+
+export const ManagedJobsWarningCallout = ({
+ jobsCount,
+ action,
+ message,
+}: {
+ jobsCount: number;
+ action?: string;
+ message?: string;
+}) => {
+ return (
+ <>
+
+
+ {message ?? (
+
+ )}
+
+ >
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/confirm_modals/stop_datafeeds_confirm_modal.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/confirm_modals/stop_datafeeds_confirm_modal.tsx
new file mode 100644
index 0000000000000..1a6d9e1e433ff
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/confirm_modals/stop_datafeeds_confirm_modal.tsx
@@ -0,0 +1,137 @@
+/*
+ * 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 React, { FC, useState, useEffect, useCallback, useMemo } from 'react';
+import { FormattedMessage } from '@kbn/i18n-react';
+import {
+ EuiSpacer,
+ EuiModal,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
+ EuiModalBody,
+ EuiModalFooter,
+ EuiButtonEmpty,
+ EuiButton,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { MlSummaryJob } from '../../../../../../common/types/anomaly_detection_jobs';
+import { isManagedJob } from '../../../jobs_utils';
+import { stopDatafeeds } from '../utils';
+import { ManagedJobsWarningCallout } from './managed_jobs_warning_callout';
+
+type ShowFunc = (jobs: MlSummaryJob[]) => void;
+
+interface Props {
+ setShowFunction(showFunc: ShowFunc): void;
+ unsetShowFunction(): void;
+ refreshJobs(): void;
+ showStopDatafeedsFlyout(job: MlSummaryJob[]): void;
+}
+
+export const StopDatafeedsConfirmModal: FC = ({
+ setShowFunction,
+ unsetShowFunction,
+ refreshJobs,
+}) => {
+ const [modalVisible, setModalVisible] = useState(false);
+ const [hasManagedJob, setHasManaged] = useState(true);
+ const [jobsToStop, setJobsToStop] = useState([]);
+
+ const jobIds = useMemo(() => jobsToStop.map(({ id }) => id), [jobsToStop]);
+
+ useEffect(() => {
+ if (typeof setShowFunction === 'function') {
+ setShowFunction(showModal);
+ }
+ return () => {
+ if (typeof unsetShowFunction === 'function') {
+ unsetShowFunction();
+ }
+ };
+ }, []);
+
+ const showModal = useCallback((jobs: MlSummaryJob[]) => {
+ setJobsToStop(jobs);
+
+ if (jobs.some((j) => isManagedJob(j))) {
+ setModalVisible(true);
+ setHasManaged(true);
+ }
+ }, []);
+
+ const closeModal = useCallback(() => {
+ setModalVisible(false);
+ setHasManaged(false);
+ }, []);
+
+ if (modalVisible === false) {
+ return null;
+ }
+
+ if (hasManagedJob) {
+ const title = (
+
+ );
+
+ return (
+
+
+ {title}
+
+
+
+
+ <>
+
+
+
+
+
+
+ {
+ stopDatafeeds(jobsToStop, refreshJobs);
+ closeModal();
+ }}
+ fill
+ color="danger"
+ data-test-subj="mlStopDatafeedsConfirmModalButton"
+ >
+
+
+
+ >
+
+ );
+ } else {
+ return <>>;
+ }
+};
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.tsx
index 2f04e8ae86e73..49c09761bc815 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/delete_job_modal/delete_job_modal.tsx
@@ -20,10 +20,13 @@ import {
EuiText,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { deleteJobs } from '../utils';
import { DELETING_JOBS_REFRESH_INTERVAL_MS } from '../../../../../../common/constants/jobs_list';
import { DeleteJobCheckModal } from '../../../../components/delete_job_check_modal';
import { MlSummaryJob } from '../../../../../../common/types/anomaly_detection_jobs';
+import { isManagedJob } from '../../../jobs_utils';
+import { ManagedJobsWarningCallout } from '../confirm_modals/managed_jobs_warning_callout';
type ShowFunc = (jobs: MlSummaryJob[]) => void;
@@ -38,6 +41,7 @@ export const DeleteJobModal: FC = ({ setShowFunction, unsetShowFunction,
const [modalVisible, setModalVisible] = useState(false);
const [jobIds, setJobIds] = useState([]);
const [canDelete, setCanDelete] = useState(false);
+ const [hasManagedJob, setHasManagedJob] = useState(false);
useEffect(() => {
if (typeof setShowFunction === 'function') {
@@ -52,6 +56,7 @@ export const DeleteJobModal: FC = ({ setShowFunction, unsetShowFunction,
const showModal = useCallback((jobs: MlSummaryJob[]) => {
setJobIds(jobs.map(({ id }) => id));
+ setHasManagedJob(jobs.some((job) => isManagedJob(job)));
setModalVisible(true);
setDeleting(false);
}, []);
@@ -104,17 +109,30 @@ export const DeleteJobModal: FC = ({ setShowFunction, unsetShowFunction,
) : (
-
-
-
+ values={{
+ jobsCount: jobIds.length,
+ }}
+ />
+
+ >
)}
@@ -155,6 +173,7 @@ export const DeleteJobModal: FC = ({ setShowFunction, unsetShowFunction,
}}
onCloseCallback={closeModal}
refreshJobsCallback={refreshJobs}
+ hasManagedJob={hasManagedJob}
/>
>
);
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js
index c278f8790984f..3ede5cf813f44 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js
@@ -22,6 +22,7 @@ import {
EuiFlexItem,
EuiTabbedContent,
EuiConfirmModal,
+ EuiSpacer,
} from '@elastic/eui';
import { JobDetails, Detectors, Datafeed, CustomUrls } from './tabs';
@@ -33,6 +34,8 @@ import { ml } from '../../../../services/ml_api_service';
import { withKibana } from '../../../../../../../../../src/plugins/kibana_react/public';
import { collapseLiteralStrings } from '../../../../../../shared_imports';
import { DATAFEED_STATE, JOB_STATE } from '../../../../../../common/constants/states';
+import { isManagedJob } from '../../../jobs_utils';
+import { ManagedJobsWarningCallout } from '../confirm_modals/managed_jobs_warning_callout';
export class EditJobFlyoutUI extends Component {
_initialJobFormState = null;
@@ -412,6 +415,21 @@ export class EditJobFlyoutUI extends Component {
/>
+
+ {isManagedJob(job) ? (
+ <>
+
+
+ >
+ ) : null}
{}} />
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js
index 64bc7f4a517c2..5b8b4b386213d 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js
@@ -19,12 +19,15 @@ import {
isResettable,
} from '../utils';
import { i18n } from '@kbn/i18n';
+import { isManagedJob } from '../../../jobs_utils';
export function actionsMenuContent(
showEditJobFlyout,
showDeleteJobModal,
showResetJobModal,
showStartDatafeedModal,
+ showCloseJobsConfirmModal,
+ showStopDatafeedsConfirmModal,
refreshJobs,
showCreateAlertFlyout
) {
@@ -65,7 +68,12 @@ export function actionsMenuContent(
enabled: (item) => isJobBlocked(item) === false && canStartStopDatafeed,
available: (item) => isStoppable([item]),
onClick: (item) => {
- stopDatafeeds([item], refreshJobs);
+ if (isManagedJob(item)) {
+ showStopDatafeedsConfirmModal([item]);
+ } else {
+ stopDatafeeds([item], refreshJobs);
+ }
+
closeMenu(true);
},
'data-test-subj': 'mlActionButtonStopDatafeed',
@@ -97,7 +105,12 @@ export function actionsMenuContent(
enabled: (item) => isJobBlocked(item) === false && canCloseJob,
available: (item) => isClosable([item]),
onClick: (item) => {
- closeJobs([item], refreshJobs);
+ if (isManagedJob(item)) {
+ showCloseJobsConfirmModal([item]);
+ } else {
+ closeJobs([item], refreshJobs);
+ }
+
closeMenu(true);
},
'data-test-subj': 'mlActionButtonCloseJob',
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js
index dea8fdd30e372..fd00058e64713 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js
@@ -42,7 +42,7 @@ export function extractJobDetails(job, basePath, refreshJobList) {
defaultMessage: 'Custom settings',
}),
position: 'right',
- items: settings ? filterObjects(settings, true, true) : [],
+ items: settings ? filterObjects(settings, true, true).map(formatValues) : [],
};
const jobTags = {
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js
index 92bbc720d8ddd..6bbbe9e59f783 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/format_values.js
@@ -80,6 +80,10 @@ export function formatValues(obj) {
typeof value === 'number' ? roundToDecimalPlace(value, 3).toLocaleString() : value,
];
+ // boolean
+ case 'managed':
+ return [key, value.toString()];
+
default:
return [key, value];
}
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js
index 5fe005c4b34ff..71a6b7d88fe1a 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js
@@ -24,10 +24,12 @@ import {
EuiIcon,
EuiScreenReaderOnly,
EuiToolTip,
+ EuiBadge,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { AnomalyDetectionJobIdLink } from './job_id_link';
+import { isManagedJob } from '../../../jobs_utils';
const PAGE_SIZE_OPTIONS = [10, 25, 50];
@@ -169,7 +171,31 @@ export class JobsList extends Component {
truncateText: false,
width: '15%',
scope: 'row',
- render: isManagementTable ? (id) => this.getJobIdLink(id) : undefined,
+ render: isManagementTable
+ ? (id) => this.getJobIdLink(id)
+ : (id, item) => {
+ if (!isManagedJob(item)) return id;
+
+ return (
+ <>
+
+ {id}
+
+
+ {i18n.translate('xpack.ml.jobsList.managedBadgeLabel', {
+ defaultMessage: 'Managed',
+ })}
+
+
+
+ >
+ );
+ },
},
{
field: 'auditMessage',
@@ -340,6 +366,8 @@ export class JobsList extends Component {
this.props.showDeleteJobModal,
this.props.showResetJobModal,
this.props.showStartDatafeedModal,
+ this.props.showCloseJobsConfirmModal,
+ this.props.showStopDatafeedsConfirmModal,
this.props.refreshJobs,
this.props.showCreateAlertFlyout
),
@@ -414,7 +442,9 @@ JobsList.propTypes = {
showEditJobFlyout: PropTypes.func,
showDeleteJobModal: PropTypes.func,
showStartDatafeedModal: PropTypes.func,
+ showCloseJobsConfirmModal: PropTypes.func,
showCreateAlertFlyout: PropTypes.func,
+ showStopDatafeedsConfirmModal: PropTypes.func,
refreshJobs: PropTypes.func,
selectedJobsCount: PropTypes.number.isRequired,
loading: PropTypes.bool,
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js
index edcb38c6305ef..539d0c475bb00 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js
@@ -29,6 +29,8 @@ import { RefreshJobsListButton } from '../refresh_jobs_list_button';
import { DELETING_JOBS_REFRESH_INTERVAL_MS } from '../../../../../../common/constants/jobs_list';
import { JobListMlAnomalyAlertFlyout } from '../../../../../alerting/ml_alerting_flyout';
+import { StopDatafeedsConfirmModal } from '../confirm_modals/stop_datafeeds_confirm_modal';
+import { CloseJobsConfirmModal } from '../confirm_modals/close_jobs_confirm_modal';
let blockingJobsRefreshTimeout = null;
@@ -58,6 +60,8 @@ export class JobsListView extends Component {
this.updateFunctions = {};
this.showEditJobFlyout = () => {};
+ this.showStopDatafeedsConfirmModal = () => {};
+ this.showCloseJobsConfirmModal = () => {};
this.showDeleteJobModal = () => {};
this.showResetJobModal = () => {};
this.showStartDatafeedModal = () => {};
@@ -193,6 +197,22 @@ export class JobsListView extends Component {
this.showEditJobFlyout = () => {};
};
+ setShowStopDatafeedsConfirmModalFunction = (func) => {
+ this.showStopDatafeedsConfirmModal = func;
+ };
+
+ unsetShowStopDatafeedsConfirmModalFunction = () => {
+ this.showStopDatafeedsConfirmModal = () => {};
+ };
+
+ setShowCloseJobsConfirmModalFunction = (func) => {
+ this.showCloseJobsConfirmModal = func;
+ };
+
+ unsetShowCloseJobsConfirmModalFunction = () => {
+ this.showCloseJobsConfirmModal = () => {};
+ };
+
setShowDeleteJobModalFunction = (func) => {
this.showDeleteJobModal = func;
};
@@ -490,10 +510,12 @@ export class JobsListView extends Component {
this.refreshJobSummaryList(true)}
/>
this.refreshJobSummaryList(true)}
jobsViewState={this.props.jobsViewState}
onJobsViewStateUpdate={this.props.onJobsViewStateUpdate}
@@ -524,6 +548,18 @@ export class JobsListView extends Component {
refreshJobs={() => this.refreshJobSummaryList(true)}
allJobIds={jobIds}
/>
+ this.refreshJobSummaryList(true)}
+ allJobIds={jobIds}
+ />
+
+ this.refreshJobSummaryList(true)}
+ />
{
- closeJobs(this.props.jobs);
+ if (this.props.jobs.some((j) => isManagedJob(j))) {
+ this.props.showCloseJobsConfirmModal(this.props.jobs);
+ } else {
+ closeJobs(this.props.jobs);
+ }
+
this.closePopover();
}}
data-test-subj="mlADJobListMultiSelectCloseJobActionButton"
@@ -139,7 +145,11 @@ class MultiJobActionsMenuUI extends Component {
icon="stop"
disabled={this.canStartStopDatafeed === false}
onClick={() => {
- stopDatafeeds(this.props.jobs, this.props.refreshJobs);
+ if (this.props.jobs.some((j) => isManagedJob(j))) {
+ this.props.showStopDatafeedsConfirmModal(this.props.jobs);
+ } else {
+ stopDatafeeds(this.props.jobs, this.props.refreshJobs);
+ }
this.closePopover();
}}
data-test-subj="mlADJobListMultiSelectStopDatafeedActionButton"
@@ -211,6 +221,7 @@ MultiJobActionsMenuUI.propTypes = {
jobs: PropTypes.array.isRequired,
showStartDatafeedModal: PropTypes.func.isRequired,
showDeleteJobModal: PropTypes.func.isRequired,
+ showStopDatafeedsConfirmModal: PropTypes.func.isRequired,
refreshJobs: PropTypes.func.isRequired,
showCreateAlertFlyout: PropTypes.func.isRequired,
};
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/multi_job_actions.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/multi_job_actions.js
index 148f1a9276c50..eede9d4573225 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/multi_job_actions.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/multi_job_actions.js
@@ -64,9 +64,11 @@ export class MultiJobActions extends Component {
@@ -81,8 +83,10 @@ MultiJobActions.propTypes = {
selectedJobs: PropTypes.array.isRequired,
allJobIds: PropTypes.array.isRequired,
showStartDatafeedModal: PropTypes.func.isRequired,
+ showCloseJobsConfirmModal: PropTypes.func.isRequired,
showDeleteJobModal: PropTypes.func.isRequired,
showResetJobModal: PropTypes.func.isRequired,
+ showStopDatafeedsConfirmModal: PropTypes.func.isRequired,
refreshJobs: PropTypes.func.isRequired,
showCreateAlertFlyout: PropTypes.func.isRequired,
};
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/reset_job_modal/reset_job_modal.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/reset_job_modal/reset_job_modal.tsx
index 45e928cdf3905..49a2b810c1000 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/reset_job_modal/reset_job_modal.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/reset_job_modal/reset_job_modal.tsx
@@ -19,10 +19,13 @@ import {
EuiText,
} from '@elastic/eui';
+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 { OpenJobsWarningCallout } from './open_jobs_warning_callout';
+import { isManagedJob } from '../../../jobs_utils';
+import { ManagedJobsWarningCallout } from '../confirm_modals/managed_jobs_warning_callout';
type ShowFunc = (jobs: MlSummaryJob[]) => void;
@@ -37,6 +40,7 @@ export const ResetJobModal: FC = ({ setShowFunction, unsetShowFunction, r
const [modalVisible, setModalVisible] = useState(false);
const [jobIds, setJobIds] = useState([]);
const [jobs, setJobs] = useState([]);
+ const [hasManagedJob, setHasManagedJob] = useState(false);
useEffect(() => {
if (typeof setShowFunction === 'function') {
@@ -52,6 +56,8 @@ export const ResetJobModal: FC = ({ setShowFunction, unsetShowFunction, r
const showModal = useCallback((tempJobs: MlSummaryJob[]) => {
setJobIds(tempJobs.map(({ id }) => id));
setJobs(tempJobs);
+ setHasManagedJob(tempJobs.some((j) => isManagedJob(j)));
+
setModalVisible(true);
setResetting(false);
}, []);
@@ -90,6 +96,22 @@ export const ResetJobModal: FC = ({ setShowFunction, unsetShowFunction, r
<>
+
+ {hasManagedJob === true ? (
+ <>
+
+
+ >
+ ) : null}
+
0;
+
this.setState({
jobs,
isModalVisible: true,
@@ -98,6 +101,7 @@ export class StartDatafeedModal extends Component {
allowCreateAlert,
createAlert: false,
now,
+ hasManagedJob: jobs.some((j) => isManagedJob(j)),
});
};
@@ -164,6 +168,8 @@ export class StartDatafeedModal extends Component {
setEndTime={this.setEndTime}
now={now}
setTimeRangeValid={this.setTimeRangeValid}
+ hasManagedJob={this.state.hasManagedJob}
+ jobsCount={startableJobs.length}
/>
{this.state.endTime === undefined && (
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js
index 20ffba0e11d4c..4300a918b948f 100644
--- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js
@@ -9,12 +9,13 @@ import './_time_range_selector.scss';
import PropTypes from 'prop-types';
import React, { Component, useState, useEffect } from 'react';
-import { EuiDatePicker, EuiFieldText } from '@elastic/eui';
+import { EuiDatePicker, EuiFieldText, EuiSpacer } from '@elastic/eui';
import moment from 'moment';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { TIME_FORMAT } from '../../../../../../../common/constants/time_format';
+import { ManagedJobsWarningCallout } from '../../confirm_modals/managed_jobs_warning_callout';
export class TimeRangeSelector extends Component {
constructor(props) {
@@ -162,6 +163,24 @@ export class TimeRangeSelector extends Component {
const { startItems, endItems } = this.getTabItems();
return (
+ {this.props.hasManagedJob === true && this.state.endTab !== 0 ? (
+ <>
+
+
+ >
+ ) : null}
, callback?: () => void): Promise;
+export function closeJobs(jobs: Array<{ id: string }>, callback?: () => void): Promise;
export function deleteJobs(jobs: Array<{ id: string }>, callback?: () => void): Promise;
export function resetJobs(jobIds: string[], callback?: () => void): Promise;
export function loadFullJob(jobId: string): Promise;
diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_utils.ts b/x-pack/plugins/ml/public/application/jobs/jobs_utils.ts
new file mode 100644
index 0000000000000..103f079b88ff3
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/jobs/jobs_utils.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { MlJob } from '@elastic/elasticsearch/lib/api/types';
+import { isPopulatedObject } from '../../../common';
+import { MlSummaryJob } from '../../../common/types/anomaly_detection_jobs';
+
+export const isManagedJob = (job: MlSummaryJob | MlJob) => {
+ return (
+ (isPopulatedObject(job, ['customSettings']) && job.customSettings.managed === true) ||
+ (isPopulatedObject(job, ['custom_settings']) && job.custom_settings.managed === true)
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts
index 9cea7aef2e117..804a368174c76 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts
@@ -767,6 +767,7 @@ export class JobCreator {
this._detectors = this._job_config.analysis_config.detectors;
this._influencers = this._job_config.analysis_config.influencers!;
+
if (this._job_config.groups === undefined) {
this._job_config.groups = [];
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/apm_tx_metrics.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/apm_tx_metrics.json
index f93b4fb009a14..9f67527ba677d 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/apm_tx_metrics.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/apm_tx_metrics.json
@@ -48,6 +48,7 @@
},
"results_index_name" : "custom-apm",
"custom_settings": {
- "created_by": "ml-module-apm-transaction"
+ "created_by": "ml-module-apm-transaction",
+ "managed": true
}
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/log_entry_rate.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/log_entry_rate.json
index 42ba15591e5c4..0cd99ae71765d 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/log_entry_rate.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/log_entry_rate.json
@@ -25,6 +25,7 @@
"enabled": true
},
"custom_settings": {
+ "managed": true,
"created_by": "ml-module-logs-ui-analysis",
"job_revision": 2
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json
index 90f88275cb6d0..74f434f36e4ed 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json
@@ -37,6 +37,7 @@
"enabled": true
},
"custom_settings": {
+ "managed": true,
"created_by": "ml-module-logs-ui-categories",
"job_revision": 2
}
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_hosts/ml/hosts_memory_usage.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_hosts/ml/hosts_memory_usage.json
index c5f62105613ba..07f2c7fe9a927 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_hosts/ml/hosts_memory_usage.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_hosts/ml/hosts_memory_usage.json
@@ -39,6 +39,7 @@
"model_memory_limit": "64mb"
},
"custom_settings": {
+ "managed": true,
"created_by": "ml-module-metrics-ui-hosts",
"custom_urls": [
{
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_hosts/ml/hosts_network_in.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_hosts/ml/hosts_network_in.json
index 258fb87f5260c..6025777e49d05 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_hosts/ml/hosts_network_in.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_hosts/ml/hosts_network_in.json
@@ -26,6 +26,7 @@
"model_memory_limit": "32mb"
},
"custom_settings": {
+ "managed": true,
"created_by": "ml-module-metrics-ui-hosts",
"custom_urls": [
{
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_hosts/ml/hosts_network_out.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_hosts/ml/hosts_network_out.json
index 381bc09bac46c..95b3950dfc35a 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_hosts/ml/hosts_network_out.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_hosts/ml/hosts_network_out.json
@@ -26,6 +26,7 @@
"model_memory_limit": "32mb"
},
"custom_settings": {
+ "managed": true,
"created_by": "ml-module-metrics-ui-hosts",
"custom_urls": [
{
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_k8s/ml/k8s_memory_usage.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_k8s/ml/k8s_memory_usage.json
index ef57612e9f90e..8b76b328a05db 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_k8s/ml/k8s_memory_usage.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_k8s/ml/k8s_memory_usage.json
@@ -42,6 +42,7 @@
"model_memory_limit": "64mb"
},
"custom_settings": {
+ "managed": true,
"created_by": "ml-module-metrics-ui-k8s",
"custom_urls": [
{
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_k8s/ml/k8s_network_in.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_k8s/ml/k8s_network_in.json
index 91f855a59add5..de591577d2d35 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_k8s/ml/k8s_network_in.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_k8s/ml/k8s_network_in.json
@@ -28,6 +28,7 @@
"model_memory_limit": "32mb"
},
"custom_settings": {
+ "managed": true,
"created_by": "ml-module-metrics-ui-k8s",
"custom_urls": [
{
diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_k8s/ml/k8s_network_out.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_k8s/ml/k8s_network_out.json
index e68866a655acf..61ce7b25eacc0 100644
--- a/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_k8s/ml/k8s_network_out.json
+++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/metrics_ui_k8s/ml/k8s_network_out.json
@@ -28,6 +28,7 @@
"model_memory_limit": "32mb"
},
"custom_settings": {
+ "managed": true,
"created_by": "ml-module-metrics-ui-k8s",
"custom_urls": [
{
diff --git a/x-pack/plugins/ml/server/models/job_service/jobs.ts b/x-pack/plugins/ml/server/models/job_service/jobs.ts
index ffc07e06db0f9..a79093caabbef 100644
--- a/x-pack/plugins/ml/server/models/job_service/jobs.ts
+++ b/x-pack/plugins/ml/server/models/job_service/jobs.ts
@@ -220,6 +220,7 @@ export function jobsProvider(
const tempJob: MlSummaryJob = {
id: job.job_id,
description: job.description || '',
+ customSettings: job.custom_settings,
groups: Array.isArray(job.groups) ? job.groups.sort() : [],
processed_record_count: job.data_counts?.processed_record_count,
earliestStartTimestampMs: getEarliestDatafeedStartTime(
@@ -319,12 +320,19 @@ export function jobsProvider(
if (jobResults && jobResults.jobs) {
const job = jobResults.jobs.find((j) => j.job_id === jobId);
if (job) {
+ removeUnClonableCustomSettings(job);
result.job = job;
}
}
return result;
}
+ function removeUnClonableCustomSettings(job: Job) {
+ if (isPopulatedObject(job.custom_settings)) {
+ delete job.custom_settings.managed;
+ }
+ }
+
async function createFullJobsList(jobIds: string[] = []) {
const jobs: CombinedJobWithStats[] = [];
const groups: { [jobId: string]: string[] } = {};
diff --git a/x-pack/plugins/transform/public/app/common/managed_transforms_utils.ts b/x-pack/plugins/transform/public/app/common/managed_transforms_utils.ts
new file mode 100644
index 0000000000000..5164245646d6e
--- /dev/null
+++ b/x-pack/plugins/transform/public/app/common/managed_transforms_utils.ts
@@ -0,0 +1,12 @@
+/*
+ * 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 type { TransformListRow } from './transform_list';
+
+export const isManagedTransform = (transform: Partial) => {
+ return transform.config?._meta?.managed;
+};
diff --git a/x-pack/plugins/transform/public/app/common/transform.ts b/x-pack/plugins/transform/public/app/common/transform.ts
index c1f8f58657d5e..09fc1c24303d8 100644
--- a/x-pack/plugins/transform/public/app/common/transform.ts
+++ b/x-pack/plugins/transform/public/app/common/transform.ts
@@ -9,8 +9,8 @@ import { useEffect } from 'react';
import { BehaviorSubject } from 'rxjs';
import { filter, distinctUntilChanged } from 'rxjs/operators';
import { Subscription } from 'rxjs';
-
-import { TransformId } from '../../../common/types/transform';
+import { cloneDeep } from 'lodash';
+import type { TransformConfigUnion, TransformId } from '../../../common/types/transform';
// Via https://github.com/elastic/elasticsearch/blob/master/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/utils/TransformStrings.java#L24
// Matches a string that contains lowercase characters, digits, hyphens, underscores or dots.
@@ -78,3 +78,11 @@ export const useRefreshTransformList = (
},
};
};
+
+export const overrideTransformForCloning = (originalConfig: TransformConfigUnion) => {
+ // 'Managed' means job is preconfigured and deployed by other solutions
+ // we should not clone this setting
+ const clonedConfig = cloneDeep(originalConfig);
+ delete clonedConfig._meta?.managed;
+ return clonedConfig;
+};
diff --git a/x-pack/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx b/x-pack/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx
index 76a3b06ef5574..c84f7cb97c959 100644
--- a/x-pack/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx
+++ b/x-pack/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx
@@ -19,7 +19,6 @@ import {
EuiPageHeader,
EuiSpacer,
} from '@elastic/eui';
-
import { APP_CREATE_TRANSFORM_CLUSTER_PRIVILEGES } from '../../../../common/constants';
import { TransformConfigUnion } from '../../../../common/types/transform';
@@ -32,8 +31,10 @@ import { breadcrumbService, docTitleService, BREADCRUMB_SECTION } from '../../se
import { PrivilegesWrapper } from '../../lib/authorization';
import { Wizard } from '../create_transform/components/wizard';
+import { overrideTransformForCloning } from '../../common/transform';
type Props = RouteComponentProps<{ transformId: string }>;
+
export const CloneTransformSection: FC = ({ match, location }) => {
const { indexPatternId }: Record = parse(location.search, {
sort: false,
@@ -82,7 +83,7 @@ export const CloneTransformSection: FC = ({ match, location }) => {
setSavedObjectId(indexPatternId);
- setTransformConfig(transformConfigs.transforms[0]);
+ setTransformConfig(overrideTransformForCloning(transformConfigs.transforms[0]));
setErrorMessage(undefined);
setIsInitialized(true);
} catch (e) {
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_modal.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_modal.tsx
index 2986fcce554e8..d5436d51c218b 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_modal.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_delete/delete_action_modal.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { FC } from 'react';
+import React, { FC, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import {
EUI_MODAL_CONFIRM_BUTTON,
@@ -15,8 +15,9 @@ import {
EuiSpacer,
EuiSwitch,
} from '@elastic/eui';
-
import { DeleteAction } from './use_delete_action';
+import { isManagedTransform } from '../../../../common/managed_transforms_utils';
+import { ManagedTransformsWarningCallout } from '../managed_transforms_callout/managed_transforms_callout';
export const DeleteActionModal: FC = ({
closeModal,
@@ -31,6 +32,7 @@ export const DeleteActionModal: FC = ({
userCanDeleteIndex,
userCanDeleteDataView,
}) => {
+ const hasManagedTransforms = useMemo(() => items.some((t) => isManagedTransform(t)), [items]);
const isBulkAction = items.length > 1;
const bulkDeleteModalTitle = i18n.translate(
@@ -47,6 +49,19 @@ export const DeleteActionModal: FC = ({
const bulkDeleteModalContent = (
<>
+ {hasManagedTransforms ? (
+ <>
+
+
+ >
+ ) : null}
+
{
= ({
const deleteModalContent = (
<>
+ {hasManagedTransforms ? (
+ <>
+
+
+ >
+ ) : null}
+
{userCanDeleteIndex && (
{
};
const openModal = (newItems: TransformListRow[]) => {
- // EUI issue: Might trigger twice, one time as an array,
- // one time as a single object. See https://github.com/elastic/eui/issues/3679
if (Array.isArray(newItems)) {
setItems(newItems);
setModalVisible(true);
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx
index fb4fee986f6e0..7ad6897034e10 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/start_action_modal.tsx
@@ -8,7 +8,6 @@
import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui';
-
import { StartAction } from './use_start_action';
export const StartActionModal: FC = ({ closeModal, items, startAndCloseModal }) => {
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.tsx
index 2c45da45509e5..20910cf5fa0a5 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_start/use_start_action.tsx
@@ -32,8 +32,6 @@ export const useStartAction = (forceDisable: boolean, transformNodes: number) =>
};
const openModal = (newItems: TransformListRow[]) => {
- // EUI issue: Might trigger twice, one time as an array,
- // one time as a single object. See https://github.com/elastic/eui/issues/3679
if (Array.isArray(newItems)) {
setItems(newItems);
setModalVisible(true);
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_modal.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_modal.tsx
new file mode 100644
index 0000000000000..ed53bb3b8d936
--- /dev/null
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/stop_action_modal.tsx
@@ -0,0 +1,54 @@
+/*
+ * 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 React, { FC, useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui';
+import { StopAction } from './use_stop_action';
+import { isManagedTransform } from '../../../../common/managed_transforms_utils';
+import { ManagedTransformsWarningCallout } from '../managed_transforms_callout/managed_transforms_callout';
+
+export const StopActionModal: FC = ({ closeModal, items, stopAndCloseModal }) => {
+ const hasManagedTransforms = useMemo(() => items.some((t) => isManagedTransform(t)), [items]);
+
+ const isBulkAction = items.length > 1;
+
+ const bulkStopModalTitle = i18n.translate('xpack.transform.transformList.bulkStopModalTitle', {
+ defaultMessage: 'Stop {count} {count, plural, one {transform} other {transforms}}?',
+ values: { count: items && items.length },
+ });
+ const stopModalTitle = i18n.translate('xpack.transform.transformList.stopModalTitle', {
+ defaultMessage: 'Stop {transformId}?',
+ values: { transformId: items[0] && items[0].config.id },
+ });
+
+ return (
+ stopAndCloseModal(items)}
+ cancelButtonText={i18n.translate('xpack.transform.transformList.startModalCancelButton', {
+ defaultMessage: 'Cancel',
+ })}
+ confirmButtonText={i18n.translate('xpack.transform.transformList.startModalStopButton', {
+ defaultMessage: 'Stop',
+ })}
+ defaultFocusedButton={EUI_MODAL_CONFIRM_BUTTON}
+ buttonColor="primary"
+ >
+ {hasManagedTransforms ? (
+
+ ) : null}
+
+ );
+};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/use_stop_action.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/use_stop_action.tsx
index f498008bfd559..ac53ee83f6f65 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/use_stop_action.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/action_stop/use_stop_action.tsx
@@ -5,21 +5,39 @@
* 2.0.
*/
-import React, { useCallback, useContext, useMemo } from 'react';
-
+import React, { useCallback, useContext, useMemo, useState } from 'react';
import { TRANSFORM_STATE } from '../../../../../../common/constants';
-
import { AuthorizationContext } from '../../../../lib/authorization';
import { TransformListAction, TransformListRow } from '../../../../common';
import { useStopTransforms } from '../../../../hooks';
-
import { isStopActionDisabled, stopActionNameText, StopActionName } from './stop_action_name';
+import { isManagedTransform } from '../../../../common/managed_transforms_utils';
export type StopAction = ReturnType;
+
export const useStopAction = (forceDisable: boolean) => {
const { canStartStopTransform } = useContext(AuthorizationContext).capabilities;
const stopTransforms = useStopTransforms();
+ const [isModalVisible, setModalVisible] = useState(false);
+ const [items, setItems] = useState([]);
+
+ const closeModal = () => setModalVisible(false);
+
+ const openModal = (newItems: TransformListRow[]) => {
+ if (Array.isArray(newItems)) {
+ setItems(newItems);
+ setModalVisible(true);
+ }
+ };
+
+ const stopAndCloseModal = useCallback(
+ (transformSelection: TransformListRow[]) => {
+ setModalVisible(false);
+ stopTransforms(transformSelection.map((t) => ({ id: t.id, state: t.stats.state })));
+ },
+ [stopTransforms]
+ );
const clickHandler = useCallback(
(i: TransformListRow) => stopTransforms([{ id: i.id, state: i.stats.state }]),
@@ -37,11 +55,17 @@ export const useStopAction = (forceDisable: boolean) => {
description: stopActionNameText,
icon: 'stop',
type: 'icon',
- onClick: clickHandler,
+ onClick: (item: TransformListRow) => {
+ if (isManagedTransform(item)) {
+ openModal([item]);
+ } else {
+ clickHandler(item);
+ }
+ },
'data-test-subj': 'transformActionStop',
}),
- [canStartStopTransform, forceDisable, clickHandler]
+ [canStartStopTransform, clickHandler, forceDisable]
);
- return { action };
+ return { action, closeModal, openModal, isModalVisible, items, stopAndCloseModal };
};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx
index 55225e0ff45c0..fd8360d02eca0 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout.tsx
@@ -38,6 +38,8 @@ import {
applyFormFieldsToTransformConfig,
useEditTransformFlyout,
} from './use_edit_transform_flyout';
+import { ManagedTransformsWarningCallout } from '../managed_transforms_callout/managed_transforms_callout';
+import { isManagedTransform } from '../../../../common/managed_transforms_utils';
interface EditTransformFlyoutProps {
closeFlyout: () => void;
@@ -99,6 +101,14 @@ export const EditTransformFlyout: FC = ({
+ {isManagedTransform({ config }) ? (
+
+ ) : null}
}>
{
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx
index 8b7aaf1cf8fd2..cdf0c14409fdd 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.tsx
@@ -31,7 +31,6 @@ import {
TransformListRow,
TRANSFORM_LIST_COLUMN,
} from '../../../../common';
-import { useStopTransforms } from '../../../../hooks';
import { AuthorizationContext } from '../../../../lib/authorization';
import { CreateTransformButton } from '../create_transform_button';
@@ -43,7 +42,7 @@ import {
DeleteActionModal,
} from '../action_delete';
import { useStartAction, StartActionName, StartActionModal } from '../action_start';
-import { StopActionName } from '../action_stop';
+import { StopActionName, useStopAction } from '../action_stop';
import { ItemIdToExpandedRowMap } from './common';
import { useColumns } from './use_columns';
@@ -52,6 +51,7 @@ import { transformFilters, filterTransforms } from './transform_search_bar_filte
import { useTableSettings } from './use_table_settings';
import { useAlertRuleFlyout } from '../../../../../alerting/transform_alerting_flyout';
import { TransformHealthAlertRule } from '../../../../../../common/types/alerting';
+import { StopActionModal } from '../action_stop/stop_action_modal';
function getItemIdToExpandedRowMap(
itemIds: TransformId[],
@@ -92,9 +92,9 @@ export const TransformList: FC = ({
const [isActionsMenuOpen, setIsActionsMenuOpen] = useState(false);
const bulkStartAction = useStartAction(false, transformNodes);
const bulkDeleteAction = useDeleteAction(false);
+ const bulkStopAction = useStopAction(false);
const [searchError, setSearchError] = useState(undefined);
- const stopTransforms = useStopTransforms();
const { capabilities } = useContext(AuthorizationContext);
const disabled =
@@ -189,9 +189,9 @@ export const TransformList: FC = ({
,
- stopTransforms(transformSelection.map((t) => ({ id: t.id, state: t.stats.state })))
- }
+ onClick={() => {
+ bulkStopAction.openModal(transformSelection);
+ }}
>
@@ -283,6 +283,7 @@ export const TransformList: FC = ({
{/* Bulk Action Modals */}
{bulkStartAction.isModalVisible && }
{bulkDeleteAction.isModalVisible && }
+ {bulkStopAction.isModalVisible && }
{/* Single Action Modals */}
{singleActionModals}
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx
index 40b40cfa8c7ba..853c839a096fa 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/use_actions.tsx
@@ -19,6 +19,7 @@ import { useEditAction } from '../action_edit';
import { useStartAction, StartActionModal } from '../action_start';
import { useStopAction } from '../action_stop';
import { useCreateAlertRuleAction } from '../action_create_alert';
+import { StopActionModal } from '../action_stop/stop_action_modal';
export const useActions = ({
forceDisable,
@@ -42,6 +43,8 @@ export const useActions = ({
modals: (
<>
{startAction.isModalVisible && }
+ {stopAction.isModalVisible && }
+
{editAction.config && editAction.isFlyoutVisible && (
{
- if (item.config?._meta?.managed !== true) return transformId;
+ if (!isManagedTransform(item)) return transformId;
return (
<>
{transformId}
-
- {i18n.translate('xpack.transform.transformList.managedBadgeLabel', {
- defaultMessage: 'Managed',
+
+ >
+
+ {i18n.translate('xpack.transform.transformList.managedBadgeLabel', {
+ defaultMessage: 'Managed',
+ })}
+
+
>
);
},