"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.timeSeriesExplorerRouteFactory = exports.TimeSeriesExplorerUrlStateManager = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _lodash = require("lodash"); var _react = _interopRequireWildcard(require("react")); var _usePrevious = _interopRequireDefault(require("react-use/lib/usePrevious")); var _moment = _interopRequireDefault(require("moment")); var _i18n = require("@kbn/i18n"); var _mlUrlState = require("@kbn/ml-url-state"); var _mlDatePicker = require("@kbn/ml-date-picker"); var _locator = require("../../../locator"); var _get_viewable_detectors = require("../../timeseriesexplorer/timeseriesexplorer_utils/get_viewable_detectors"); var _kibana = require("../../contexts/kibana"); var _timeseriesexplorer = require("../../timeseriesexplorer"); var _explorer_utils = require("../../explorer/explorer_utils"); var _job_service = require("../../services/job_service"); var _forecast_service = require("../../services/forecast_service"); var _timeseriesexplorer_constants = require("../../timeseriesexplorer/timeseriesexplorer_constants"); var _timeseriesexplorer_utils = require("../../timeseriesexplorer/timeseriesexplorer_utils"); var _timeseriesexplorer_page = require("../../timeseriesexplorer/timeseriesexplorer_page"); var _timeseriesexplorer_no_jobs_found = require("../../timeseriesexplorer/components/timeseriesexplorer_no_jobs_found"); var _select_interval = require("../../components/controls/select_interval"); var _select_severity = require("../../components/controls/select_severity"); var _router = require("../router"); var _use_resolver = require("../use_resolver"); var _breadcrumbs = require("../breadcrumbs"); var _toast_notification_service = require("../../services/toast_notification_service"); var _annotations_service = require("../../services/annotations_service"); var _ml_annotation_updates_context = require("../../contexts/ml/ml_annotation_updates_context"); var _use_timeseriesexplorer_url_state = require("../../timeseriesexplorer/hooks/use_timeseriesexplorer_url_state"); var _use_job_selection_flyout = require("../../contexts/ml/use_job_selection_flyout"); var _use_refresh = require("../use_refresh"); var _timeseriesexplorer_no_chart_data = require("../../timeseriesexplorer/components/timeseriesexplorer_no_chart_data"); var _resolvers = require("../resolvers"); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /* * 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. */ const timeSeriesExplorerRouteFactory = (navigateToPath, basePath) => ({ id: 'timeseriesexplorer', path: (0, _router.createPath)(_locator.ML_PAGES.SINGLE_METRIC_VIEWER), title: _i18n.i18n.translate('xpack.ml.anomalyDetection.singleMetricViewerLabel', { defaultMessage: 'Single Metric Viewer' }), render: (props, deps) => /*#__PURE__*/_react.default.createElement(PageWrapper, (0, _extends2.default)({}, props, { deps: deps })), breadcrumbs: [(0, _breadcrumbs.getBreadcrumbWithUrlForApp)('ML_BREADCRUMB', navigateToPath, basePath), (0, _breadcrumbs.getBreadcrumbWithUrlForApp)('ANOMALY_DETECTION_BREADCRUMB', navigateToPath, basePath), { text: _i18n.i18n.translate('xpack.ml.anomalyDetection.singleMetricViewerLabel', { defaultMessage: 'Single Metric Viewer' }) }], enableDatePicker: true }); exports.timeSeriesExplorerRouteFactory = timeSeriesExplorerRouteFactory; const PageWrapper = ({ deps }) => { const mlApi = (0, _kibana.useMlApiContext)(); const uiSettings = (0, _kibana.useUiSettings)(); const { context, results } = (0, _use_resolver.useRouteResolver)('full', ['canGetJobs'], { ...(0, _resolvers.basicResolvers)(), jobs: _job_service.mlJobService.loadJobsWrapper, jobsWithTimeRange: () => mlApi.jobs.jobsWithTimerange((0, _explorer_utils.getDateFormatTz)()) }); const annotationUpdatesService = (0, _react.useMemo)(() => new _annotations_service.AnnotationUpdatesService(), []); return /*#__PURE__*/_react.default.createElement(_router.PageLoader, { context: context }, /*#__PURE__*/_react.default.createElement(_ml_annotation_updates_context.MlAnnotationUpdatesContext.Provider, { value: annotationUpdatesService }, results ? /*#__PURE__*/_react.default.createElement(TimeSeriesExplorerUrlStateManager, { config: uiSettings, jobsWithTimeRange: results.jobsWithTimeRange.jobs }) : null)); }; const TimeSeriesExplorerUrlStateManager = ({ config, jobsWithTimeRange }) => { var _refresh$lastRefresh, _globalState$time, _globalState$time2, _globalState$time3, _globalState$ml, _timeSeriesExplorerUr, _timeSeriesExplorerUr2, _timeSeriesExplorerUr3, _timeSeriesExplorerUr4, _getViewableDetectors, _getViewableDetectors2, _timeSeriesExplorerUr5, _timeSeriesExplorerUr6, _bounds$min, _bounds$max, _timeSeriesExplorerUr8, _refresh$lastRefresh2; const { services: { data: { dataViews: dataViewsService } } } = (0, _kibana.useMlKibana)(); const { toasts } = (0, _kibana.useNotifications)(); const toastNotificationService = (0, _toast_notification_service.useToastNotificationService)(); const [timeSeriesExplorerUrlState, setTimeSeriesExplorerUrlState] = (0, _use_timeseriesexplorer_url_state.useTimeSeriesExplorerUrlState)(); const [globalState, setGlobalState] = (0, _mlUrlState.useUrlState)('_g'); const [selectedJobId, setSelectedJobId] = (0, _react.useState)(); const timefilter = (0, _mlDatePicker.useTimefilter)({ timeRangeSelector: true, autoRefreshSelector: true }); const [invalidTimeRangeError, setInValidTimeRangeError] = (0, _react.useState)(false); const refresh = (0, _use_refresh.useRefresh)(); const previousRefresh = (0, _usePrevious.default)((_refresh$lastRefresh = refresh === null || refresh === void 0 ? void 0 : refresh.lastRefresh) !== null && _refresh$lastRefresh !== void 0 ? _refresh$lastRefresh : 0); // We cannot simply infer bounds from the globalState's `time` attribute // with `moment` since it can contain custom strings such as `now-15m`. // So when globalState's `time` changes, we update the timefilter and use // `timefilter.getBounds()` to update `bounds` in this component's state. const [bounds, setBounds] = (0, _react.useState)(undefined); (0, _react.useEffect)(() => { if ((globalState === null || globalState === void 0 ? void 0 : globalState.time) !== undefined) { if (globalState.time.mode === 'invalid') { setInValidTimeRangeError(true); } const timefilterBounds = timefilter.getBounds(); // Only if both min/max bounds are valid moment times set the bounds. // An invalid string restored from globalState might return `undefined`. if ((timefilterBounds === null || timefilterBounds === void 0 ? void 0 : timefilterBounds.min) !== undefined && (timefilterBounds === null || timefilterBounds === void 0 ? void 0 : timefilterBounds.max) !== undefined) { setBounds(timefilter.getBounds()); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [globalState === null || globalState === void 0 ? void 0 : (_globalState$time = globalState.time) === null || _globalState$time === void 0 ? void 0 : _globalState$time.from, globalState === null || globalState === void 0 ? void 0 : (_globalState$time2 = globalState.time) === null || _globalState$time2 === void 0 ? void 0 : _globalState$time2.to, globalState === null || globalState === void 0 ? void 0 : (_globalState$time3 = globalState.time) === null || _globalState$time3 === void 0 ? void 0 : _globalState$time3.ts]); const selectedJobIds = globalState === null || globalState === void 0 ? void 0 : (_globalState$ml = globalState.ml) === null || _globalState$ml === void 0 ? void 0 : _globalState$ml.jobIds; // Sort selectedJobIds so we can be sure comparison works when stringifying. if (Array.isArray(selectedJobIds)) { selectedJobIds.sort(); } // When changing jobs we'll clear appState (detectorIndex, entities, forecastId). // To restore settings from the URL on initial load we also need to check against // `previousSelectedJobIds` to avoid wiping appState. const previousSelectedJobIds = (0, _usePrevious.default)(selectedJobIds); const isJobChange = !(0, _lodash.isEqual)(previousSelectedJobIds, selectedJobIds); const selectedEntities = isJobChange ? undefined : timeSeriesExplorerUrlState === null || timeSeriesExplorerUrlState === void 0 ? void 0 : (_timeSeriesExplorerUr = timeSeriesExplorerUrlState.mlTimeSeriesExplorer) === null || _timeSeriesExplorerUr === void 0 ? void 0 : _timeSeriesExplorerUr.entities; const selectedForecastId = isJobChange ? undefined : timeSeriesExplorerUrlState === null || timeSeriesExplorerUrlState === void 0 ? void 0 : (_timeSeriesExplorerUr2 = timeSeriesExplorerUrlState.mlTimeSeriesExplorer) === null || _timeSeriesExplorerUr2 === void 0 ? void 0 : _timeSeriesExplorerUr2.forecastId; const selectedFunctionDescription = isJobChange ? undefined : timeSeriesExplorerUrlState === null || timeSeriesExplorerUrlState === void 0 ? void 0 : (_timeSeriesExplorerUr3 = timeSeriesExplorerUrlState.mlTimeSeriesExplorer) === null || _timeSeriesExplorerUr3 === void 0 ? void 0 : _timeSeriesExplorerUr3.functionDescription; const zoom = isJobChange ? undefined : timeSeriesExplorerUrlState === null || timeSeriesExplorerUrlState === void 0 ? void 0 : (_timeSeriesExplorerUr4 = timeSeriesExplorerUrlState.mlTimeSeriesExplorer) === null || _timeSeriesExplorerUr4 === void 0 ? void 0 : _timeSeriesExplorerUr4.zoom; const selectedJob = selectedJobId !== undefined ? _job_service.mlJobService.getJob(selectedJobId) : undefined; const timeSeriesJobs = (0, _timeseriesexplorer_utils.createTimeSeriesJobData)(_job_service.mlJobService.jobs); const viewableDetector = selectedJob ? (_getViewableDetectors = (_getViewableDetectors2 = (0, _get_viewable_detectors.getViewableDetectors)(selectedJob)[0]) === null || _getViewableDetectors2 === void 0 ? void 0 : _getViewableDetectors2.index) !== null && _getViewableDetectors !== void 0 ? _getViewableDetectors : 0 : 0; // Next we get globalState and appState information to pass it on as props later. // If a job change is going on, we fall back to defaults (as if appState was already cleared), // otherwise the page could break. const selectedDetectorIndex = isJobChange ? viewableDetector : (_timeSeriesExplorerUr5 = timeSeriesExplorerUrlState === null || timeSeriesExplorerUrlState === void 0 ? void 0 : (_timeSeriesExplorerUr6 = timeSeriesExplorerUrlState.mlTimeSeriesExplorer) === null || _timeSeriesExplorerUr6 === void 0 ? void 0 : _timeSeriesExplorerUr6.detectorIndex) !== null && _timeSeriesExplorerUr5 !== void 0 ? _timeSeriesExplorerUr5 : viewableDetector; let autoZoomDuration; if (selectedJobId !== undefined && selectedJob !== undefined) { autoZoomDuration = (0, _timeseriesexplorer_utils.getAutoZoomDuration)(timeSeriesJobs, selectedJob); } const appStateHandler = (0, _react.useCallback)((action, payload) => { var _timeSeriesExplorerUr7; /** * Empty zoom indicates that chart hasn't been rendered yet, * hence any updates prior that should replace the URL state. */ const isInitUpdate = (timeSeriesExplorerUrlState === null || timeSeriesExplorerUrlState === void 0 ? void 0 : (_timeSeriesExplorerUr7 = timeSeriesExplorerUrlState.mlTimeSeriesExplorer) === null || _timeSeriesExplorerUr7 === void 0 ? void 0 : _timeSeriesExplorerUr7.zoom) === undefined; const mlTimeSeriesExplorer = (timeSeriesExplorerUrlState === null || timeSeriesExplorerUrlState === void 0 ? void 0 : timeSeriesExplorerUrlState.mlTimeSeriesExplorer) !== undefined ? { ...timeSeriesExplorerUrlState.mlTimeSeriesExplorer } : {}; switch (action) { case _timeseriesexplorer_constants.APP_STATE_ACTION.CLEAR: delete mlTimeSeriesExplorer.detectorIndex; delete mlTimeSeriesExplorer.entities; delete mlTimeSeriesExplorer.forecastId; delete mlTimeSeriesExplorer.zoom; delete mlTimeSeriesExplorer.functionDescription; break; case _timeseriesexplorer_constants.APP_STATE_ACTION.SET_DETECTOR_INDEX: mlTimeSeriesExplorer.detectorIndex = payload; delete mlTimeSeriesExplorer.functionDescription; break; case _timeseriesexplorer_constants.APP_STATE_ACTION.SET_ENTITIES: mlTimeSeriesExplorer.entities = payload; delete mlTimeSeriesExplorer.functionDescription; break; case _timeseriesexplorer_constants.APP_STATE_ACTION.SET_FORECAST_ID: mlTimeSeriesExplorer.forecastId = payload; delete mlTimeSeriesExplorer.zoom; break; case _timeseriesexplorer_constants.APP_STATE_ACTION.SET_ZOOM: mlTimeSeriesExplorer.zoom = payload; break; case _timeseriesexplorer_constants.APP_STATE_ACTION.UNSET_ZOOM: delete mlTimeSeriesExplorer.zoom; break; case _timeseriesexplorer_constants.APP_STATE_ACTION.SET_FUNCTION_DESCRIPTION: mlTimeSeriesExplorer.functionDescription = payload; break; } setTimeSeriesExplorerUrlState({ mlTimeSeriesExplorer }, isInitUpdate); }, // eslint-disable-next-line react-hooks/exhaustive-deps [ // eslint-disable-next-line react-hooks/exhaustive-deps JSON.stringify(timeSeriesExplorerUrlState === null || timeSeriesExplorerUrlState === void 0 ? void 0 : timeSeriesExplorerUrlState.mlTimeSeriesExplorer), setTimeSeriesExplorerUrlState]); const getJobSelection = (0, _use_job_selection_flyout.useJobSelectionFlyout)(); // Use a side effect to clear appState when changing jobs. (0, _react.useEffect)(() => { if (selectedJobIds !== undefined && previousSelectedJobIds !== undefined) { appStateHandler(_timeseriesexplorer_constants.APP_STATE_ACTION.CLEAR); } const validatedJobId = (0, _timeseriesexplorer_utils.validateJobSelection)(jobsWithTimeRange, selectedJobIds, setGlobalState, toasts, getJobSelection); if (typeof validatedJobId === 'string') { setSelectedJobId(validatedJobId); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify(selectedJobIds)]); const boundsMinMs = bounds === null || bounds === void 0 ? void 0 : (_bounds$min = bounds.min) === null || _bounds$min === void 0 ? void 0 : _bounds$min.valueOf(); const boundsMaxMs = bounds === null || bounds === void 0 ? void 0 : (_bounds$max = bounds.max) === null || _bounds$max === void 0 ? void 0 : _bounds$max.valueOf(); const [selectedForecastIdProp, setSelectedForecastIdProp] = (0, _react.useState)(timeSeriesExplorerUrlState === null || timeSeriesExplorerUrlState === void 0 ? void 0 : (_timeSeriesExplorerUr8 = timeSeriesExplorerUrlState.mlTimeSeriesExplorer) === null || _timeSeriesExplorerUr8 === void 0 ? void 0 : _timeSeriesExplorerUr8.forecastId); (0, _react.useEffect)(() => { if (autoZoomDuration !== undefined && boundsMinMs !== undefined && boundsMaxMs !== undefined && selectedJob !== undefined && selectedForecastId !== undefined) { if (selectedForecastIdProp !== selectedForecastId) { setSelectedForecastIdProp(undefined); } _forecast_service.mlForecastService.getForecastDateRange(selectedJob, selectedForecastId).then(resp => { if (autoZoomDuration === undefined) { return; } const earliest = (0, _moment.default)(resp.earliest || boundsMinMs); const latest = (0, _moment.default)(resp.latest || boundsMaxMs); if (earliest.isBefore((0, _moment.default)(boundsMinMs)) || latest.isAfter((0, _moment.default)(boundsMaxMs))) { const earliestMs = Math.min(earliest.valueOf(), boundsMinMs); const latestMs = Math.max(latest.valueOf(), boundsMaxMs); // FIXME we should not update global state here setGlobalState('time', { from: (0, _moment.default)(earliestMs).toISOString(), to: (0, _moment.default)(latestMs).toISOString() }); } setSelectedForecastIdProp(selectedForecastId); }).catch(resp => { // eslint-disable-next-line no-console console.error('Time series explorer - error loading time range of forecast from elasticsearch:', resp); }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedForecastId]); const [tableInterval] = (0, _select_interval.useTableInterval)(); const [tableSeverity] = (0, _select_severity.useTableSeverity)(); const tzConfig = config.get('dateFormat:tz'); const dateFormatTz = tzConfig !== 'Browser' ? tzConfig : _moment.default.tz.guess(); if (timeSeriesJobs.length === 0) { return /*#__PURE__*/_react.default.createElement(_timeseriesexplorer_page.TimeSeriesExplorerPage, { dateFormatTz: dateFormatTz, noSingleMetricJobsFound: true }, /*#__PURE__*/_react.default.createElement(_timeseriesexplorer_no_jobs_found.TimeseriesexplorerNoJobsFound, null)); } if (!bounds) { return /*#__PURE__*/_react.default.createElement(_timeseriesexplorer_page.TimeSeriesExplorerPage, { dateFormatTz: dateFormatTz }, /*#__PURE__*/_react.default.createElement(_timeseriesexplorer_no_chart_data.TimeseriesexplorerNoChartData, null)); } const zoomProp = typeof selectedForecastId === 'string' && selectedForecastIdProp === undefined ? undefined : zoom; return /*#__PURE__*/_react.default.createElement(_timeseriesexplorer.TimeSeriesExplorer, { dataViewsService, toastNotificationService, appStateHandler, autoZoomDuration, bounds, dateFormatTz, lastRefresh: (_refresh$lastRefresh2 = refresh === null || refresh === void 0 ? void 0 : refresh.lastRefresh) !== null && _refresh$lastRefresh2 !== void 0 ? _refresh$lastRefresh2 : 0, previousRefresh, selectedJobId, selectedDetectorIndex, selectedEntities, selectedForecastId: selectedForecastIdProp, tableInterval: tableInterval.val, tableSeverity: tableSeverity.val, timefilter, zoom: zoomProp, invalidTimeRangeError, functionDescription: selectedFunctionDescription }); }; exports.TimeSeriesExplorerUrlStateManager = TimeSeriesExplorerUrlStateManager;