[ML] Enhances toast notifications to improve error reporting (#173362)

## Summary

Several enhancements to the error toast notifications in the ML plugin
to improve error reporting.

The bulk of the changes are to add 'See the full error' buttons to the
toasts allowing the user to see further details on the error that has
occurred. Also makes minor edits to some of the error messages to
improve clarity.

Also closes #171839 by changes to
`x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js`
so that the the 'Jobs started successfully' toast is only shown if 1 or
more jobs have been started successfully.

Fixes #171839

### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Pete Harverson 2023-12-20 14:00:27 +00:00 committed by GitHub
parent 9023497cb5
commit 2c51f63dd1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 186 additions and 124 deletions

View file

@ -118,14 +118,15 @@ export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = (props) =>
callback(fullTimeRange);
}
} catch (e) {
toasts.addDanger(
i18n.translate(
toasts.addError(e, {
title: i18n.translate(
'xpack.ml.datePicker.fullTimeRangeSelector.errorSettingTimeRangeNotification',
{
defaultMessage: 'An error occurred setting the time range.',
defaultMessage:
'An error occurred setting the time range. Please set the desired start and end times.',
}
)
);
),
});
}
}, [
timefilter,

View file

@ -20,7 +20,11 @@ import { getTimeFieldRange } from './time_field_range';
const mockParamsFactory = () => ({
timefilter: { setTime: jest.fn() } as unknown as TimefilterContract,
dataView: { getIndexPattern: jest.fn(), getRuntimeMappings: jest.fn() } as unknown as DataView,
toasts: { addWarning: jest.fn(), addDanger: jest.fn() } as unknown as ToastsStart,
toasts: {
addWarning: jest.fn(),
addDanger: jest.fn(),
addError: jest.fn(),
} as unknown as ToastsStart,
});
describe('setFullTimeRange', () => {

View file

@ -81,14 +81,15 @@ export async function setFullTimeRange(
});
}
} catch (error) {
toasts.addDanger(
i18n.translate(
toasts.addError(error, {
title: i18n.translate(
'xpack.ml.datePicker.fullTimeRangeSelector.errorSettingTimeRangeNotification',
{
defaultMessage: 'An error occurred setting the time range.',
defaultMessage:
'An error occurred setting the time range. Please set the desired start and end times.',
}
)
);
),
});
}
}

View file

@ -16,6 +16,7 @@ import { JobSelectorControl } from './job_selector';
import { useMlKibana } from '../application/contexts/kibana';
import { jobsApiProvider } from '../application/services/ml_api_service/jobs';
import { HttpService } from '../application/services/http_service';
import { useToastNotificationService } from '../application/services/toast_notification_service';
import { SeverityControl } from '../application/components/severity_control';
import { ResultTypeSelector } from './result_type_selector';
import { alertingApiProvider } from '../application/services/ml_api_service/alerting';
@ -44,11 +45,11 @@ const MlAnomalyAlertTrigger: FC<MlAnomalyAlertTriggerProps> = ({
}) => {
const {
services: { http },
notifications: { toasts },
} = useMlKibana();
const mlHttpService = useMemo(() => new HttpService(http), [http]);
const adJobsApiService = useMemo(() => jobsApiProvider(mlHttpService), [mlHttpService]);
const alertingApiService = useMemo(() => alertingApiProvider(mlHttpService), [mlHttpService]);
const { displayErrorToast } = useToastNotificationService();
const [jobConfigs, setJobConfigs] = useState<CombinedJobWithStats[]>([]);
@ -74,13 +75,13 @@ const MlAnomalyAlertTrigger: FC<MlAnomalyAlertTriggerProps> = ({
const jobs = await adJobsApiService.jobs(jobsAndGroupIds);
setJobConfigs(jobs);
} catch (e) {
toasts.danger({
title: i18n.translate('xpack.ml.anomalyDetectionAlert.errorFetchingJobs', {
displayErrorToast(
e,
i18n.translate('xpack.ml.anomalyDetectionAlert.errorFetchingJobs', {
defaultMessage: 'Unable to fetch jobs configuration',
}),
body: e.message,
toastLifeTimeMs: 5000,
});
5000
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [jobsAndGroupIds]);

View file

@ -13,6 +13,14 @@ import { CustomUrlList, CustomUrlListProps } from './list';
jest.mock('../../../contexts/kibana');
jest.mock('../../../services/toast_notification_service', () => ({
useToastNotificationService: () => {
return {
displayErrorToast: jest.fn(),
};
},
}));
function prepareTest(setCustomUrlsFn: jest.Mock) {
const customUrls = [
{

View file

@ -29,6 +29,7 @@ import {
import { parseUrlState } from '@kbn/ml-url-state';
import { useMlKibana } from '../../../contexts/kibana';
import { useToastNotificationService } from '../../../services/toast_notification_service';
import { isValidLabel, openCustomUrlWindow } from '../../../util/custom_url_utils';
import { getTestUrl } from './utils';
@ -68,10 +69,10 @@ export const CustomUrlList: FC<CustomUrlListProps> = ({
const {
services: {
http,
notifications,
data: { dataViews },
},
} = useMlKibana();
const { displayErrorToast } = useToastNotificationService();
const [expandedUrlIndex, setExpandedUrlIndex] = useState<number | null>(null);
const onLabelChange = (e: ChangeEvent<HTMLInputElement>, index: number) => {
@ -161,11 +162,8 @@ export const CustomUrlList: FC<CustomUrlListProps> = ({
const testUrl = await getTestUrl(job, customUrl, timefieldName, undefined, isPartialDFAJob);
openCustomUrlWindow(testUrl, customUrl, http.basePath.get());
} catch (error) {
// eslint-disable-next-line no-console
console.error('Error obtaining URL for test:', error);
const { toasts } = notifications;
toasts.addDanger(
displayErrorToast(
error,
i18n.translate(
'xpack.ml.customUrlEditorList.obtainingUrlToTestConfigurationErrorMessage',
{

View file

@ -29,6 +29,10 @@ import type { DataViewListItem } from '@kbn/data-views-plugin/common';
import type { MlUrlConfig } from '@kbn/ml-anomaly-utils';
import { isDataFrameAnalyticsConfigs } from '@kbn/ml-data-frame-analytics-utils';
import type { DashboardService, DashboardItems } from '../../services/dashboard_service';
import {
ToastNotificationService,
toastNotificationServiceProvider,
} from '../../services/toast_notification_service';
import type { MlKibanaReactContextValue } from '../../contexts/kibana';
import { CustomUrlEditor, CustomUrlList } from './custom_url_editor';
import {
@ -58,6 +62,8 @@ interface CustomUrlsProps extends CustomUrlsWrapperProps {
}
class CustomUrlsUI extends Component<CustomUrlsProps, CustomUrlsState> {
private toastNotificationService: ToastNotificationService | undefined;
constructor(props: CustomUrlsProps) {
super(props);
@ -79,16 +85,17 @@ class CustomUrlsUI extends Component<CustomUrlsProps, CustomUrlsState> {
componentDidMount() {
const { toasts } = this.props.kibana.services.notifications;
this.toastNotificationService = toastNotificationServiceProvider(toasts);
const { dashboardService } = this.props;
dashboardService
.fetchDashboards()
.then((dashboards) => {
this.setState({ dashboards });
})
.catch((resp) => {
// eslint-disable-next-line no-console
console.error('Error loading list of dashboards:', resp);
toasts.addDanger(
.catch((error) => {
this.toastNotificationService!.displayErrorToast(
error,
i18n.translate(
'xpack.ml.jobsList.editJobFlyout.customUrls.loadSavedDashboardsErrorNotificationMessage',
{
@ -102,10 +109,9 @@ class CustomUrlsUI extends Component<CustomUrlsProps, CustomUrlsState> {
.then((dataViewListItems) => {
this.setState({ dataViewListItems });
})
.catch((resp) => {
// eslint-disable-next-line no-console
console.error('Error loading list of dashboards:', resp);
toasts.addDanger(
.catch((error) => {
this.toastNotificationService!.displayErrorToast(
error,
i18n.translate(
'xpack.ml.jobsList.editJobFlyout.customUrls.loadDataViewsErrorNotificationMessage',
{
@ -148,11 +154,9 @@ class CustomUrlsUI extends Component<CustomUrlsProps, CustomUrlsState> {
this.props.setCustomUrls(customUrls);
this.setState({ editorOpen: false });
})
.catch((error: Error) => {
// eslint-disable-next-line no-console
console.error('Error building custom URL from settings:', error);
const { toasts } = this.props.kibana.services.notifications;
toasts.addDanger(
.catch((error) => {
this.toastNotificationService!.displayErrorToast(
error,
i18n.translate(
'xpack.ml.jobsList.editJobFlyout.customUrls.addNewUrlErrorNotificationMessage',
{
@ -167,7 +171,6 @@ class CustomUrlsUI extends Component<CustomUrlsProps, CustomUrlsState> {
onTestButtonClick = () => {
const {
http: { basePath },
notifications: { toasts },
data: { dataViews },
dashboard,
} = this.props.kibana.services;
@ -194,10 +197,9 @@ class CustomUrlsUI extends Component<CustomUrlsProps, CustomUrlsState> {
.then((testUrl) => {
openCustomUrlWindow(testUrl, customUrl, basePath.get());
})
.catch((resp) => {
// eslint-disable-next-line no-console
console.error('Error obtaining URL for test:', resp);
toasts.addWarning(
.catch((error) => {
this.toastNotificationService!.displayErrorToast(
error,
i18n.translate(
'xpack.ml.jobsList.editJobFlyout.customUrls.getTestUrlErrorNotificationMessage',
{
@ -210,10 +212,9 @@ class CustomUrlsUI extends Component<CustomUrlsProps, CustomUrlsState> {
}
);
})
.catch((resp) => {
// eslint-disable-next-line no-console
console.error('Error building custom URL from settings:', resp);
toasts.addWarning(
.catch((error) => {
this.toastNotificationService!.displayErrorToast(
error,
i18n.translate(
'xpack.ml.jobsList.editJobFlyout.customUrls.buildUrlErrorNotificationMessage',
{

View file

@ -32,7 +32,6 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { withKibana } from '@kbn/kibana-react-plugin/public';
import { extractErrorMessage } from '@kbn/ml-error-utils';
import {
ML_DETECTOR_RULE_ACTION,
ML_DETECTOR_RULE_CONDITIONS_NOT_SUPPORTED_FUNCTIONS,
@ -55,6 +54,7 @@ import {
import { getPartitioningFieldNames } from '../../../../common/util/job_utils';
import { mlJobService } from '../../services/job_service';
import { toastNotificationServiceProvider } from '../../services/toast_notification_service';
import { ml } from '../../services/ml_api_service';
class RuleEditorFlyoutUI extends Component {
@ -83,6 +83,9 @@ class RuleEditorFlyoutUI extends Component {
}
componentDidMount() {
this.toastNotificationService = toastNotificationServiceProvider(
this.props.kibana.services.notifications.toasts
);
if (typeof this.props.setShowFunction === 'function') {
this.props.setShowFunction(this.showFlyout);
}
@ -105,8 +108,7 @@ class RuleEditorFlyoutUI extends Component {
i18n.translate(
'xpack.ml.ruleEditor.ruleEditorFlyout.unableToConfigureRulesNotificationMesssage',
{
defaultMessage:
'Unable to configure job rules as an error occurred obtaining details for job ID {jobId}',
defaultMessage: 'Unable to configure job rules as no job found with ID {jobId}',
values: { jobId: anomaly.jobId },
}
)
@ -153,10 +155,9 @@ class RuleEditorFlyoutUI extends Component {
filterListIds,
});
})
.catch((resp) => {
console.log('Error loading list of filters:', resp);
const { toasts } = this.props.kibana.services.notifications;
toasts.addDanger(
.catch((error) => {
this.toastNotificationService.displayErrorToast(
error,
i18n.translate(
'xpack.ml.ruleEditor.ruleEditorFlyout.errorWithLoadingFilterListsNotificationMesssage',
{
@ -374,8 +375,8 @@ class RuleEditorFlyoutUI extends Component {
}
})
.catch((error) => {
console.error(error);
toasts.addDanger(
this.toastNotificationService.displayErrorToast(
error,
i18n.translate(
'xpack.ml.ruleEditor.ruleEditorFlyout.errorWithSavingChangesToJobDetectorRulesNotificationMessage',
{
@ -426,18 +427,16 @@ class RuleEditorFlyoutUI extends Component {
}
})
.catch((error) => {
console.error(error);
let errorMessage = i18n.translate(
'xpack.ml.ruleEditor.ruleEditorFlyout.errorWithDeletingRuleFromJobDetectorNotificationMessage',
{
defaultMessage: 'Error deleting rule from {jobId} detector',
values: { jobId },
}
this.toastNotificationService.displayErrorToast(
error,
i18n.translate(
'xpack.ml.ruleEditor.ruleEditorFlyout.errorWithDeletingRuleFromJobDetectorNotificationMessage',
{
defaultMessage: 'Error deleting rule from {jobId} detector',
values: { jobId },
}
)
);
if (error.error) {
errorMessage += ` : ${extractErrorMessage(error.error)}`;
}
toasts.addDanger(errorMessage);
});
};
@ -467,8 +466,8 @@ class RuleEditorFlyoutUI extends Component {
}
})
.catch((error) => {
console.log(`Error adding ${item} to filter ${filterId}:`, error);
toasts.addDanger(
this.toastNotificationService.displayErrorToast(
error,
i18n.translate(
'xpack.ml.ruleEditor.ruleEditorFlyout.errorWithAddingItemToFilterListNotificationMessage',
{

View file

@ -93,6 +93,11 @@ function prepareTest() {
},
},
},
notifications: {
toasts: {
addDanger: () => {},
},
},
},
},
};

View file

@ -185,17 +185,19 @@ function showResults(resp, action) {
}
const toastNotifications = getToastNotifications();
toastNotifications.addSuccess(
i18n.translate('xpack.ml.jobsList.actionExecuteSuccessfullyNotificationMessage', {
defaultMessage:
'{successesJobsCount, plural, one{{successJob}} other{# jobs}} {actionTextPT} successfully',
values: {
successesJobsCount: successes.length,
successJob: successes[0],
actionTextPT,
},
})
);
if (successes.length > 0) {
toastNotifications.addSuccess(
i18n.translate('xpack.ml.jobsList.actionExecuteSuccessfullyNotificationMessage', {
defaultMessage:
'{successesJobsCount, plural, one{{successJob}} other{# jobs}} {actionTextPT} successfully',
values: {
successesJobsCount: successes.length,
successJob: successes[0],
actionTextPT,
},
})
);
}
if (failures.length > 0) {
failures.forEach((f) => {

View file

@ -123,7 +123,11 @@ export const useModelMemoryEstimator = (
title: i18n.translate('xpack.ml.newJob.wizard.estimateModelMemoryError', {
defaultMessage: 'Model memory limit could not be calculated',
}),
text: extractErrorMessage(error),
text: i18n.translate('xpack.ml.newJob.wizard.estimateModelMemoryErrorText', {
defaultMessage:
'{errorText}. You can proceed with creating the job, but check for warning messages once the job is running that the configured limit has not been exceeded.',
values: { errorText: extractErrorMessage(error) },
}),
});
})
);

View file

@ -113,7 +113,8 @@ export const TimeRangeStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep })
const { toasts } = services.notifications;
toasts.addDanger(
i18n.translate('xpack.ml.newJob.wizard.timeRangeStep.fullTimeRangeError', {
defaultMessage: 'An error occurred obtaining the time range for the index',
defaultMessage:
'An error occurred obtaining the time range for the index. Please set the desired start and end times.',
})
);
}

View file

@ -234,21 +234,22 @@ export const Page: FC<PageProps> = ({ moduleId, existingGroupIds }) => {
);
} catch (e) {
setSaveState(SAVE_STATE.FAILED);
// eslint-disable-next-line no-console
console.error('Error setting up module', e);
const { toasts } = notifications;
toasts.addDanger({
toasts.addError(e, {
title: i18n.translate('xpack.ml.newJob.recognize.moduleSetupFailedWarningTitle', {
defaultMessage: 'Error setting up module {moduleId}',
values: { moduleId },
}),
text: i18n.translate('xpack.ml.newJob.recognize.moduleSetupFailedWarningDescription', {
defaultMessage:
'An error occurred trying to create the {count, plural, one {job} other {jobs}} in the module.',
values: {
count: jobs.length,
},
}),
toastMessage: i18n.translate(
'xpack.ml.newJob.recognize.moduleSetupFailedWarningDescription',
{
defaultMessage:
'An error occurred trying to create the {count, plural, one {job} other {jobs}} in the module.',
values: {
count: jobs.length,
},
}
),
});
}
},

View file

@ -126,7 +126,7 @@ export const JobMemoryTreeMap: FC<Props> = ({ node, type, height }) => {
displayErrorToast(
error,
i18n.translate('xpack.ml.memoryUsage.treeMap.fetchFailedErrorMessage', {
defaultMessage: 'Models memory usage fetch failed',
defaultMessage: 'Error loading model memory usage data',
})
);
}

View file

@ -84,7 +84,7 @@ export const NodesList: FC<NodesListProps> = ({ compactView = false }) => {
displayErrorToast(
e,
i18n.translate('xpack.ml.trainedModels.nodesList.nodesFetchError', {
defaultMessage: 'Nodes fetch failed',
defaultMessage: 'Error loading overview on machine learning nodes',
})
);
setIsLoading(false);

View file

@ -305,7 +305,7 @@ export const ModelsList: FC<Props> = ({
displayErrorToast(
error,
i18n.translate('xpack.ml.trainedModels.modelsList.fetchFailedErrorMessage', {
defaultMessage: 'Models fetch failed',
defaultMessage: 'Error loading trained models',
})
);
}
@ -383,7 +383,7 @@ export const ModelsList: FC<Props> = ({
displayErrorToast(
error,
i18n.translate('xpack.ml.trainedModels.modelsList.fetchModelStatsErrorMessage', {
defaultMessage: 'Fetch model stats failed',
defaultMessage: 'Error loading trained models statistics',
})
);
return false;

View file

@ -136,7 +136,7 @@ export const NotificationsList: FC = () => {
displayErrorToast(
error,
i18n.translate('xpack.ml.notifications.fetchFailedError', {
defaultMessage: 'Fetch notifications failed',
defaultMessage: 'Error loading list of notifications',
})
);
}

View file

@ -105,18 +105,16 @@ const CheckViewOrCreateWrapper: FC<PageProps> = ({ location }) => {
}
})
.catch(async (err: Error) => {
// eslint-disable-next-line no-console
console.error(`Error checking whether jobs in module ${moduleId} exists`, err);
toasts.addWarning({
toasts.addError(err, {
title: i18n.translate('xpack.ml.newJob.recognize.moduleCheckJobsExistWarningTitle', {
defaultMessage: 'Error checking module {moduleId}',
values: { moduleId },
}),
text: i18n.translate(
toastMessage: i18n.translate(
'xpack.ml.newJob.recognize.moduleCheckJobsExistWarningDescription',
{
defaultMessage:
'An error occurred trying to check whether the jobs in the module have been created.',
'An error occurred checking whether the jobs in the module have been created. Search the list for matching jobs or create new jobs.',
}
),
});

View file

@ -22,8 +22,8 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { AnomalyDetectionSettingsContext } from './anomaly_detection_settings_context';
import { useNotifications } from '../contexts/kibana';
import { ml } from '../services/ml_api_service';
import { useToastNotificationService } from '../services/toast_notification_service';
import { ML_PAGES } from '../../../common/constants/locator';
import { useCreateAndNavigateToMlLink } from '../contexts/kibana/use_create_url';
@ -35,7 +35,7 @@ export const AnomalyDetectionSettings: FC = () => {
AnomalyDetectionSettingsContext
);
const { toasts } = useNotifications();
const { displayErrorToast } = useToastNotificationService();
const redirectToCalendarList = useCreateAndNavigateToMlLink(ML_PAGES.CALENDARS_MANAGE);
const redirectToNewCalendarPage = useCreateAndNavigateToMlLink(ML_PAGES.CALENDARS_NEW);
const redirectToFilterLists = useCreateAndNavigateToMlLink(ML_PAGES.FILTER_LISTS_MANAGE);
@ -53,7 +53,8 @@ export const AnomalyDetectionSettings: FC = () => {
const calendars = await ml.calendars();
setCalendarsCount(calendars.length);
} catch (e) {
toasts.addDanger(
displayErrorToast(
e,
i18n.translate('xpack.ml.settings.anomalyDetection.loadingCalendarsCountErrorMessage', {
defaultMessage: 'An error occurred obtaining the count of calendars',
})
@ -66,7 +67,8 @@ export const AnomalyDetectionSettings: FC = () => {
const filterLists = await ml.filters.filtersStats();
setFilterListsCount(filterLists.length);
} catch (e) {
toasts.addDanger(
displayErrorToast(
e,
i18n.translate('xpack.ml.settings.anomalyDetection.loadingFilterListCountErrorMessage', {
defaultMessage: 'An error occurred obtaining the count of filter lists',
})

View file

@ -20,6 +20,7 @@ import { ml } from '../../../services/ml_api_service';
import { withKibana } from '@kbn/kibana-react-plugin/public';
import { GLOBAL_CALENDAR } from '../../../../../common/constants/calendars';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { toastNotificationServiceProvider } from '../../../services/toast_notification_service';
import { getDocLinks } from '../../../util/dependency_cache';
import { HelpMenu } from '../../../components/help_menu';
@ -54,6 +55,9 @@ class NewCalendarUI extends Component {
}
componentDidMount() {
this.toastNotificationService = toastNotificationServiceProvider(
this.props.kibana.services.notifications.toasts
);
this.formSetup();
}
@ -118,10 +122,9 @@ class NewCalendarUI extends Component {
isGlobalCalendar,
});
} catch (error) {
console.log(error);
this.setState({ loading: false });
const { toasts } = this.props.kibana.services.notifications;
toasts.addDanger(
this.toastNotificationService.displayErrorToast(
error,
i18n.translate('xpack.ml.calendarsEdit.errorWithLoadingCalendarFromDataErrorMessage', {
defaultMessage: 'An error occurred loading calendar form data. Try refreshing the page.',
})
@ -160,10 +163,9 @@ class NewCalendarUI extends Component {
await ml.addCalendar(calendar);
await this.returnToCalendarsManagementPage();
} catch (error) {
console.log('Error saving calendar', error);
this.setState({ saving: false });
const { toasts } = this.props.kibana.services.notifications;
toasts.addDanger(
this.toastNotificationService.displayErrorToast(
error,
i18n.translate('xpack.ml.calendarsEdit.errorWithCreatingCalendarErrorMessage', {
defaultMessage: 'An error occurred creating calendar {calendarId}',
values: { calendarId: calendar.calendarId },
@ -181,10 +183,9 @@ class NewCalendarUI extends Component {
await ml.updateCalendar(calendar);
await this.returnToCalendarsManagementPage();
} catch (error) {
console.log('Error saving calendar', error);
this.setState({ saving: false });
const { toasts } = this.props.kibana.services.notifications;
toasts.addDanger(
this.toastNotificationService.displayErrorToast(
error,
i18n.translate('xpack.ml.calendarsEdit.errorWithUpdatingCalendarErrorMessage', {
defaultMessage:
'An error occurred saving calendar {calendarId}. Try refreshing the page.',

View file

@ -117,6 +117,11 @@ const props = {
},
},
},
notifications: {
toasts: {
addDanger: () => {},
},
},
},
},
};

View file

@ -13,6 +13,7 @@ import { EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui';
import { CalendarsListHeader } from './header';
import { CalendarsListTable } from './table';
import { ml } from '../../../services/ml_api_service';
import { toastNotificationServiceProvider } from '../../../services/toast_notification_service';
import { mlNodesAvailable } from '../../../ml_nodes_check/check_ml_nodes';
import { deleteCalendars } from './delete_calendars';
import { i18n } from '@kbn/i18n';
@ -58,6 +59,13 @@ export class CalendarsListUI extends Component {
defaultMessage: 'An error occurred loading the list of calendars.',
})
);
const toastNotificationService = toastNotificationServiceProvider(toasts);
toastNotificationService.displayErrorToast(
error,
i18n.translate('xpack.ml.calendarsList.errorWithLoadingListOfCalendarsErrorMessage', {
defaultMessage: 'An error occurred loading the list of calendars.',
})
);
}
};

View file

@ -31,6 +31,7 @@ import { EditFilterListToolbar } from './toolbar';
import { ItemsGrid } from '../../../components/items_grid';
import { isValidFilterListId, saveFilterList } from './utils';
import { ml } from '../../../services/ml_api_service';
import { toastNotificationServiceProvider } from '../../../services/toast_notification_service';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { getDocLinks } from '../../../util/dependency_cache';
import { HelpMenu } from '../../../components/help_menu';
@ -93,6 +94,9 @@ export class EditFilterListUI extends Component {
}
componentDidMount() {
this.toastNotificationService = toastNotificationServiceProvider(
this.props.kibana.services.notifications.toasts
);
const filterId = this.props.filterId;
if (filterId !== undefined) {
this.loadFilterList(filterId);
@ -117,10 +121,9 @@ export class EditFilterListUI extends Component {
.then((filter) => {
this.setLoadedFilterState(filter);
})
.catch((resp) => {
console.log(`Error loading filter ${filterId}:`, resp);
const { toasts } = this.props.kibana.services.notifications;
toasts.addDanger(
.catch((error) => {
this.toastNotificationService.displayErrorToast(
error,
i18n.translate(
'xpack.ml.settings.filterLists.editFilterList.loadingDetailsOfFilterErrorMessage',
{
@ -287,10 +290,9 @@ export class EditFilterListUI extends Component {
this.setLoadedFilterState(savedFilter);
this.returnToFiltersList();
})
.catch((resp) => {
console.log(`Error saving filter ${filterId}:`, resp);
const { toasts } = this.props.kibana.services.notifications;
toasts.addDanger(
.catch((error) => {
this.toastNotificationService.displayErrorToast(
error,
i18n.translate('xpack.ml.settings.filterLists.editFilterList.savingFilterErrorMessage', {
defaultMessage: 'An error occurred saving filter {filterId}',
values: {

View file

@ -59,6 +59,15 @@ import { EditFilterList } from './edit_filter_list';
const props = {
canCreateFilter: true,
canDeleteFilter: true,
kibana: {
services: {
notifications: {
toasts: {
addWarning: () => {},
},
},
},
},
};
function prepareEditTest() {

View file

@ -19,6 +19,7 @@ import { withKibana } from '@kbn/kibana-react-plugin/public';
import { FilterListsHeader } from './header';
import { FilterListsTable } from './table';
import { ml } from '../../../services/ml_api_service';
import { toastNotificationServiceProvider } from '../../../services/toast_notification_service';
import { getDocLinks } from '../../../util/dependency_cache';
import { HelpMenu } from '../../../components/help_menu';
@ -69,8 +70,8 @@ export class FilterListsUI extends Component {
.then((filterLists) => {
this.setFilterLists(filterLists);
})
.catch((resp) => {
console.log('Error loading list of filters:', resp);
.catch((error) => {
console.log('Error loading list of filters:', error);
const { toasts } = this.props.kibana.services.notifications;
toasts.addDanger(
i18n.translate(
@ -80,6 +81,16 @@ export class FilterListsUI extends Component {
}
)
);
const toastNotificationService = toastNotificationServiceProvider(toasts);
toastNotificationService.displayErrorToast(
error,
i18n.translate(
'xpack.ml.settings.filterLists.filterLists.loadingFilterListsErrorMessage',
{
defaultMessage: 'An error occurred loading the filter lists',
}
)
);
});
};

View file

@ -18,7 +18,7 @@ jest.mock('../components/help_menu', () => ({
jest.mock('../contexts/kibana', () => ({
useNotifications: () => {
return {
toasts: { addDanger: jest.fn() },
toasts: { addDanger: jest.fn(), addError: jest.fn() },
};
},
useMlKibana: () => {