"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.LinksMenuUI = exports.LinksMenu = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _lodash = require("lodash"); var _moment = _interopRequireDefault(require("moment")); var _rison = _interopRequireDefault(require("@kbn/rison")); var _react = _interopRequireWildcard(require("react")); var _common = require("@kbn/maps-plugin/common"); var _eui = require("@elastic/eui"); var _i18nReact = require("@kbn/i18n-react"); var _i18n = require("@kbn/i18n"); var _fieldTypes = require("@kbn/field-types"); var _public = require("@kbn/maps-plugin/public"); var _mlAnomalyUtils = require("@kbn/ml-anomaly-utils"); var _mlDateUtils = require("@kbn/ml-date-utils"); var _mlQueryUtils = require("@kbn/ml-query-utils"); var _job_service = require("../../services/job_service"); var _index_utils = require("../../util/index_utils"); var _util = require("../../../maps/util"); var _parse_interval = require("../../../../common/util/parse_interval"); var _ml_api_service = require("../../services/ml_api_service"); var _string_utils = require("../../util/string_utils"); var _custom_url_utils = require("../../util/custom_url_utils"); var _locator = require("../../../../common/constants/locator"); var _explorer_utils = require("../../explorer/explorer_utils"); var _check_capabilities = require("../../capabilities/check_capabilities"); var _kibana = require("../../contexts/kibana"); var _mapping_service = require("../../services/mapping_service"); var _get_query_string_for_influencers = require("./get_query_string_for_influencers"); var _job_utils = require("../../../../common/util/job_utils"); 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. */ // @ts-ignore // @ts-ignore const LinksMenuUI = props => { const [openInDiscoverUrl, setOpenInDiscoverUrl] = (0, _react.useState)(); const [discoverUrlError, setDiscoverUrlError] = (0, _react.useState)(); const isCategorizationAnomalyRecord = (0, _mlAnomalyUtils.isCategorizationAnomaly)(props.anomaly); const closePopover = props.onItemClick; const kibana = (0, _kibana.useMlKibana)(); const { services: { data, share, application } } = kibana; const job = (0, _react.useMemo)(() => { return _job_service.mlJobService.getJob(props.anomaly.jobId); }, [props.anomaly.jobId]); const getAnomaliesMapsLink = async anomaly => { const index = job.datafeed_config.indices[0]; const dataViewId = await (0, _index_utils.getDataViewIdFromName)(index); const initialLayers = (0, _util.getInitialAnomaliesLayers)(anomaly.jobId); const anomalyBucketStartMoment = (0, _moment.default)(anomaly.source.timestamp).tz((0, _explorer_utils.getDateFormatTz)()); const anomalyBucketStart = anomalyBucketStartMoment.toISOString(); const anomalyBucketEnd = anomalyBucketStartMoment.add(anomaly.source.bucket_span, 'seconds').subtract(1, 'ms').toISOString(); const timeRange = data.query.timefilter.timefilter.getTime(); // Set 'from' in timeRange to start bucket time for the specific anomaly timeRange.from = anomalyBucketStart; timeRange.to = anomalyBucketEnd; const locator = share.url.locators.get(_public.MAPS_APP_LOCATOR); const location = await (locator === null || locator === void 0 ? void 0 : locator.getLocation({ initialLayers, timeRange, ...(anomaly.entityName && anomaly.entityValue ? { query: { language: _mlQueryUtils.SEARCH_QUERY_LANGUAGE.KUERY, query: (0, _string_utils.escapeKueryForFieldValuePair)(anomaly.entityName, anomaly.entityValue) } } : {}), filters: dataViewId === null ? [] : (0, _job_utils.getFiltersForDSLQuery)(job.datafeed_config.query, dataViewId, job.job_id) })); return location; }; const getAnomalySourceMapsLink = async (anomaly, sourceIndicesWithGeoFields) => { const index = job.datafeed_config.indices[0]; const dataViewId = await (0, _index_utils.getDataViewIdFromName)(index); // Create a layer for each of the geoFields const initialLayers = (0, _util.getInitialSourceIndexFieldLayers)(sourceIndicesWithGeoFields[anomaly.jobId]); // Widen the timerange by one bucket span on start/end to increase chances of always having data on the map const anomalyBucketStartMoment = (0, _moment.default)(anomaly.source.timestamp).tz((0, _explorer_utils.getDateFormatTz)()); const anomalyBucketStart = anomalyBucketStartMoment.subtract(anomaly.source.bucket_span, 'seconds').toISOString(); const anomalyBucketEnd = anomalyBucketStartMoment.add(anomaly.source.bucket_span * 3, 'seconds').subtract(1, 'ms').toISOString(); const timeRange = data.query.timefilter.timefilter.getTime(); // Set 'from' in timeRange to start bucket time for the specific anomaly timeRange.from = anomalyBucketStart; timeRange.to = anomalyBucketEnd; // Create query string for influencers const influencersQueryString = (0, _get_query_string_for_influencers.getQueryStringForInfluencers)(anomaly.influencers, anomaly.entityName); const locator = share.url.locators.get(_public.MAPS_APP_LOCATOR); const filtersFromDatafeedQuery = dataViewId === null ? [] : (0, _job_utils.getFiltersForDSLQuery)(job.datafeed_config.query, dataViewId, job.job_id); const location = await (locator === null || locator === void 0 ? void 0 : locator.getLocation({ initialLayers, timeRange, filters: filtersFromDatafeedQuery.length > 0 ? filtersFromDatafeedQuery : data.query.filterManager.getFilters(), ...(anomaly.entityName && anomaly.entityValue ? { query: { language: _mlQueryUtils.SEARCH_QUERY_LANGUAGE.KUERY, query: `${(0, _string_utils.escapeKueryForFieldValuePair)(anomaly.entityName, anomaly.entityValue)}${influencersQueryString !== '' ? ` and (${influencersQueryString})` : ''}` } } : {}) })); return location; }; (0, _react.useEffect)(() => { let unmounted = false; const discoverLocator = share.url.locators.get('DISCOVER_APP_LOCATOR'); if (!discoverLocator) { const discoverLocatorMissing = _i18n.i18n.translate('xpack.ml.anomaliesTable.linksMenu.discoverLocatorMissingErrorMessage', { defaultMessage: 'No locator for Discover detected' }); if (!unmounted) { setDiscoverUrlError(discoverLocatorMissing); } return; } const getDataViewId = async () => { const index = job.datafeed_config.indices[0]; const dataViewId = await (0, _index_utils.getDataViewIdFromName)(index); // If data view doesn't exist for some reasons if (!dataViewId && !unmounted) { const autoGeneratedDiscoverLinkError = _i18n.i18n.translate('xpack.ml.anomaliesTable.linksMenu.autoGeneratedDiscoverLinkErrorMessage', { defaultMessage: `Unable to link to Discover; no data view exists for index '{index}'`, values: { index } }); setDiscoverUrlError(autoGeneratedDiscoverLinkError); } return dataViewId; }; const generateDiscoverUrl = async () => { const interval = props.interval; const dataViewId = await getDataViewId(); const record = props.anomaly.source; const earliestMoment = (0, _moment.default)(record.timestamp).startOf(interval); if (interval === 'hour') { // Start from the previous hour. earliestMoment.subtract(1, 'h'); } const latestMoment = (0, _moment.default)(record.timestamp).add(record.bucket_span, 's'); if (props.isAggregatedData === true) { if (interval === 'hour') { // Show to the end of the next hour. latestMoment.add(1, 'h'); } latestMoment.subtract(1, 'ms').endOf(interval); // e.g. 2016-02-08T18:59:59.999Z } const from = (0, _mlDateUtils.timeFormatter)(earliestMoment.unix() * 1000); // e.g. 2016-02-08T16:00:00.000Z const to = (0, _mlDateUtils.timeFormatter)(latestMoment.unix() * 1000); // e.g. 2016-02-08T18:59:59.000Z let kqlQuery = ''; if (record.influencers) { kqlQuery = record.influencers.map(influencer => { var _influencer$influence; return `"${influencer.influencer_field_name}":"${(_influencer$influence = influencer.influencer_field_values[0]) !== null && _influencer$influence !== void 0 ? _influencer$influence : ''}"`; }).join(' AND '); } const url = await discoverLocator.getRedirectUrl({ indexPatternId: dataViewId, timeRange: { from, to, mode: 'absolute' }, query: { language: 'kuery', query: kqlQuery }, filters: dataViewId === null ? [] : (0, _job_utils.getFiltersForDSLQuery)(job.datafeed_config.query, dataViewId, job.job_id) }); if (!unmounted) { setOpenInDiscoverUrl(url); } }; if (!isCategorizationAnomalyRecord) { generateDiscoverUrl(); } else { getDataViewId(); } return () => { unmounted = true; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [JSON.stringify(props.anomaly)]); const openCustomUrl = customUrl => { const { anomaly, interval, isAggregatedData } = props; // eslint-disable-next-line no-console console.log('Anomalies Table - open customUrl for record:', anomaly); // If url_value contains $earliest$ and $latest$ tokens, add in times to the source record. // Create a copy of the record as we are adding properties into it. const record = (0, _lodash.cloneDeep)(anomaly.source); const timestamp = record.timestamp; const configuredUrlValue = customUrl.url_value; const timeRangeInterval = customUrl.time_range !== undefined ? (0, _parse_interval.parseInterval)(customUrl.time_range) : null; const basePath = kibana.services.http.basePath.get(); if (configuredUrlValue.includes('$earliest$')) { let earliestMoment = (0, _moment.default)(timestamp); if (timeRangeInterval !== null) { earliestMoment.subtract(timeRangeInterval); } else { earliestMoment = (0, _moment.default)(timestamp).startOf(interval); if (interval === 'hour') { // Start from the previous hour. earliestMoment.subtract(1, 'h'); } } record.earliest = earliestMoment.toISOString(); // e.g. 2016-02-08T16:00:00.000Z } if (configuredUrlValue.includes('$latest$')) { const latestMoment = (0, _moment.default)(timestamp).add(record.bucket_span, 's'); if (timeRangeInterval !== null) { latestMoment.add(timeRangeInterval); } else { if (isAggregatedData === true) { if (interval === 'hour') { // Show to the end of the next hour. latestMoment.add(1, 'h'); // e.g. 2016-02-08T18:59:59.999Z } latestMoment.subtract(1, 'ms').endOf(interval); // e.g. 2016-02-08T18:59:59.999Z } } record.latest = latestMoment.toISOString(); } // If url_value contains $mlcategoryterms$ or $mlcategoryregex$, add in the // terms and regex for the selected categoryId to the source record. if ((configuredUrlValue.includes('$mlcategoryterms$') || configuredUrlValue.includes('$mlcategoryregex$')) && record.mlcategory !== undefined) { const jobId = record.job_id; // mlcategory in the source record will be an array // - use first value (will only ever be more than one if influenced by category other than by/partition/over). const categoryId = record.mlcategory[0]; _ml_api_service.ml.results.getCategoryDefinition(jobId, categoryId).then(resp => { // Prefix each of the terms with '+' so that the Elasticsearch Query String query // run in a drilldown Kibana dashboard has to match on all terms. const termsArray = resp.terms.split(' ').map(term => `+${term}`); record.mlcategoryterms = termsArray.join(' '); record.mlcategoryregex = resp.regex; // Replace any tokens in the configured url_value with values from the source record, // and then open link in a new tab/window. const urlPath = (0, _string_utils.replaceStringTokens)(customUrl.url_value, record, true); (0, _custom_url_utils.openCustomUrlWindow)(urlPath, customUrl, basePath); }).catch(resp => { // eslint-disable-next-line no-console console.log('openCustomUrl(): error loading categoryDefinition:', resp); const { toasts } = kibana.services.notifications; toasts.addDanger(_i18n.i18n.translate('xpack.ml.anomaliesTable.linksMenu.unableToOpenLinkErrorMessage', { defaultMessage: 'Unable to open link as an error occurred loading details on category ID {categoryId}', values: { categoryId } })); }); } else { // Replace any tokens in the configured url_value with values from the source record, // and then open link in a new tab/window. const urlPath = (0, _custom_url_utils.getUrlForRecord)(customUrl, record); (0, _custom_url_utils.openCustomUrlWindow)(urlPath, customUrl, basePath); } }; const viewSeries = async () => { const mlLocator = share.url.locators.get(_locator.ML_APP_LOCATOR); const record = props.anomaly.source; const bounds = props.bounds; if (!mlLocator) { // eslint-disable-next-line no-console console.error('Unable to detect locator for ML or bounds'); return; } if (!bounds || !bounds.min || !bounds.max) { // eslint-disable-next-line no-console console.error('Invalid bounds'); return; } const from = bounds.min.toISOString(); // e.g. 2016-02-08T16:00:00.000Z const to = bounds.max.toISOString(); // Zoom to show 50 buckets either side of the record. const recordTime = (0, _moment.default)(record.timestamp); const zoomFrom = recordTime.subtract(50 * record.bucket_span, 's').toISOString(); const zoomTo = recordTime.add(100 * record.bucket_span, 's').toISOString(); // Extract the by, over and partition fields for the record. const entityCondition = {}; if (record.partition_field_name !== undefined && record.partition_field_value !== undefined) { entityCondition[record.partition_field_name] = record.partition_field_value; } if (record.over_field_name !== undefined && record.over_field_value !== undefined) { entityCondition[record.over_field_name] = record.over_field_value; } if (record.by_field_name !== undefined && record.by_field_value !== undefined) { // Note that analyses with by and over fields, will have a top-level by_field_name, // but the by_field_value(s) will be in the nested causes array. // TODO - drilldown from cause in expanded row only? entityCondition[record.by_field_name] = record.by_field_value; } const singleMetricViewerLink = await mlLocator.getUrl({ page: _locator.ML_PAGES.SINGLE_METRIC_VIEWER, pageState: { jobIds: [record.job_id], refreshInterval: { display: 'Off', pause: true, value: 0 }, timeRange: { from, to, mode: 'absolute' }, zoom: { from: zoomFrom, to: zoomTo }, detectorIndex: record.detector_index, entities: entityCondition, query_string: { analyze_wildcard: true, query: '*' } } }, { absolute: true }); window.open(singleMetricViewerLink, '_blank'); }; const viewExamples = () => { const categoryId = props.anomaly.entityValue; const record = props.anomaly.source; if (job === undefined) { // eslint-disable-next-line no-console console.log(`viewExamples(): no job found with ID: ${props.anomaly.jobId}`); const { toasts } = kibana.services.notifications; toasts.addDanger(_i18n.i18n.translate('xpack.ml.anomaliesTable.linksMenu.unableToViewExamplesErrorMessage', { defaultMessage: 'Unable to view examples as no details could be found for job ID {jobId}', values: { jobId: props.anomaly.jobId } })); return; } const categorizationFieldName = job.analysis_config.categorization_field_name; const datafeedIndices = job.datafeed_config.indices; // Find the type of the categorization field i.e. text (preferred) or keyword. // Uses the first matching field found in the list of indices in the datafeed_config. // attempt to load the field type using each index. we have to do it this way as _field_caps // doesn't specify which index a field came from unless there is a clash. let i = 0; findFieldType(datafeedIndices[i]); const error = () => { // eslint-disable-next-line no-console console.log(`viewExamples(): error finding type of field ${categorizationFieldName} in indices:`, datafeedIndices); const { toasts } = kibana.services.notifications; toasts.addDanger(_i18n.i18n.translate('xpack.ml.anomaliesTable.linksMenu.noMappingCouldBeFoundErrorMessage', { defaultMessage: 'Unable to view examples of documents with mlcategory {categoryId} ' + 'as no mapping could be found for the categorization field {categorizationFieldName}', values: { categoryId, categorizationFieldName } })); }; const createAndOpenUrl = (index, categorizationFieldType) => { // Get the definition of the category and use the terms or regex to view the // matching events in the Kibana Discover tab depending on whether the // categorization field is of mapping type text (preferred) or keyword. _ml_api_service.ml.results.getCategoryDefinition(record.job_id, categoryId).then(async resp => { // Find the ID of the data view with a title attribute which matches the // index configured in the datafeed. If a Kibana data view has not been created // for this index, then the user will see a warning message on the Discover tab advising // them that no matching data view has been configured. const dataViewId = await (0, _index_utils.getDataViewIdFromName)(index); // We should not redirect to Discover if data view doesn't exist if (!dataViewId) return; let query = null; // Build query using categorization regex (if keyword type) or terms (if text type). // Check for terms or regex in case categoryId represents an anomaly from the absence of the // categorization field in documents (usually indicated by a categoryId of -1). if (categorizationFieldType === _fieldTypes.ES_FIELD_TYPES.KEYWORD) { if (resp.regex) { query = { language: _mlQueryUtils.SEARCH_QUERY_LANGUAGE.LUCENE, query: `${categorizationFieldName}:/${resp.regex}/` }; } } else { if (resp.terms) { const escapedTerms = (0, _explorer_utils.escapeDoubleQuotes)(resp.terms); query = { language: _mlQueryUtils.SEARCH_QUERY_LANGUAGE.KUERY, query: `${categorizationFieldName}:"` + escapedTerms.split(' ').join(`" and ${categorizationFieldName}:"`) + '"' }; } } const recordTime = (0, _moment.default)(record.timestamp); const from = recordTime.toISOString(); const to = recordTime.add(record.bucket_span, 's').toISOString(); // Use rison to build the URL . const _g = _rison.default.encode({ refreshInterval: { display: 'Off', pause: true, value: 0 }, time: { from, to, mode: 'absolute' } }); const appStateProps = { index: dataViewId, filters: (0, _job_utils.getFiltersForDSLQuery)(job.datafeed_config.query, dataViewId, job.job_id), ...(query !== null ? { query } : {}) }; const _a = _rison.default.encode(appStateProps); // Need to encode the _a parameter as it will contain characters such as '+' if using the regex. const { basePath } = kibana.services.http; let path = basePath.get(); path += '/app/discover#/'; path += '?_g=' + _g; path += '&_a=' + encodeURIComponent(_a); window.open(path, '_blank'); }).catch(resp => { // eslint-disable-next-line no-console console.log('viewExamples(): error loading categoryDefinition:', resp); const { toasts } = kibana.services.notifications; toasts.addDanger(_i18n.i18n.translate('xpack.ml.anomaliesTable.linksMenu.loadingDetailsErrorMessage', { defaultMessage: 'Unable to view examples as an error occurred loading details on category ID {categoryId}', values: { categoryId } })); }); }; function findFieldType(index) { (0, _mapping_service.getFieldTypeFromMapping)(index, categorizationFieldName).then(resp => { if (resp !== '') { createAndOpenUrl(datafeedIndices.join(), resp); } else { i++; if (i < datafeedIndices.length) { findFieldType(datafeedIndices[i]); } else { error(); } } }).catch(() => { error(); }); } }; const { anomaly, showViewSeriesLink } = props; const canUpdateJob = (0, _check_capabilities.usePermissionCheck)('canUpdateJob'); const canConfigureRules = (0, _mlAnomalyUtils.isRuleSupported)(anomaly.source) && canUpdateJob; const contextMenuItems = (0, _react.useMemo)(() => { var _application$capabili, _application$capabili2, _application$capabili3; const items = []; if (anomaly.customUrls !== undefined) { anomaly.customUrls.forEach((customUrl, index) => { items.push( /*#__PURE__*/_react.default.createElement(_eui.EuiContextMenuItem, { key: `custom_url_${index}`, icon: "popout", onClick: () => { closePopover(); openCustomUrl(customUrl); }, "data-test-subj": `mlAnomaliesListRowActionCustomUrlButton_${index}` }, customUrl.url_name)); }); } if ((_application$capabili = application.capabilities.discover) !== null && _application$capabili !== void 0 && _application$capabili.show && !isCategorizationAnomalyRecord) { // Add item from the start, but disable it during the URL generation. const isLoading = discoverUrlError === undefined && openInDiscoverUrl === undefined; items.push( /*#__PURE__*/_react.default.createElement(_eui.EuiContextMenuItem, { key: `auto_raw_data_url`, icon: "discoverApp", disabled: discoverUrlError !== undefined || isLoading, href: openInDiscoverUrl, "data-test-subj": `mlAnomaliesListRowAction_viewInDiscoverButton` }, discoverUrlError ? /*#__PURE__*/_react.default.createElement(_eui.EuiToolTip, { content: discoverUrlError }, /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "xpack.ml.anomaliesTable.linksMenu.viewInDiscover", defaultMessage: "View in Discover" })) : /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "xpack.ml.anomaliesTable.linksMenu.viewInDiscover", defaultMessage: "View in Discover" }), isLoading ? /*#__PURE__*/_react.default.createElement(_eui.EuiProgress, { size: 'xs', color: 'accent' }) : null)); } if (showViewSeriesLink === true) { if (anomaly.isTimeSeriesViewRecord) { items.push( /*#__PURE__*/_react.default.createElement(_eui.EuiContextMenuItem, { key: "view_series", icon: "visLine", onClick: () => { closePopover(); viewSeries(); }, "data-test-subj": "mlAnomaliesListRowActionViewSeriesButton" }, /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "xpack.ml.anomaliesTable.linksMenu.viewSeriesLabel", defaultMessage: "View series" }))); } } if ((_application$capabili2 = application.capabilities.maps) !== null && _application$capabili2 !== void 0 && _application$capabili2.show) { if (anomaly.isGeoRecord === true) { items.push( /*#__PURE__*/_react.default.createElement(_eui.EuiContextMenuItem, { key: "view_in_maps", icon: "gisApp", onClick: async () => { const mapsLink = await getAnomaliesMapsLink(anomaly); await application.navigateToApp(_common.APP_ID, { path: mapsLink === null || mapsLink === void 0 ? void 0 : mapsLink.path }); }, "data-test-subj": "mlAnomaliesListRowActionViewInMapsButton" }, /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "xpack.ml.anomaliesTable.linksMenu.viewInMapsLabel", defaultMessage: "View in Maps" }))); } else if (props.sourceIndicesWithGeoFields && props.sourceIndicesWithGeoFields[anomaly.jobId]) { items.push( /*#__PURE__*/_react.default.createElement(_eui.EuiContextMenuItem, { key: "view_in_maps", icon: "gisApp", onClick: async () => { const mapsLink = await getAnomalySourceMapsLink(anomaly, props.sourceIndicesWithGeoFields); await application.navigateToApp(_common.APP_ID, { path: mapsLink === null || mapsLink === void 0 ? void 0 : mapsLink.path }); }, "data-test-subj": "mlAnomaliesListRowActionViewSourceIndexInMapsButton" }, /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "xpack.ml.anomaliesTable.linksMenu.viewSourceIndexInMapsLabel", defaultMessage: "View source index in Maps" }))); } } if ((_application$capabili3 = application.capabilities.discover) !== null && _application$capabili3 !== void 0 && _application$capabili3.show && isCategorizationAnomalyRecord) { items.push( /*#__PURE__*/_react.default.createElement(_eui.EuiContextMenuItem, { key: "view_examples", icon: "popout", onClick: () => { closePopover(); viewExamples(); }, "data-test-subj": "mlAnomaliesListRowActionViewExamplesButton", disabled: discoverUrlError !== undefined }, discoverUrlError !== undefined ? /*#__PURE__*/_react.default.createElement(_eui.EuiToolTip, { content: discoverUrlError }, /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "xpack.ml.anomaliesTable.linksMenu.viewExamplesLabel", defaultMessage: "View examples" })) : /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "xpack.ml.anomaliesTable.linksMenu.viewExamplesLabel", defaultMessage: "View examples" }))); } if (canConfigureRules) { items.push( /*#__PURE__*/_react.default.createElement(_eui.EuiContextMenuItem, { key: "create_rule", icon: "controlsHorizontal", onClick: () => { closePopover(); props.showRuleEditorFlyout(anomaly); }, "data-test-subj": "mlAnomaliesListRowActionConfigureRulesButton" }, /*#__PURE__*/_react.default.createElement(_i18nReact.FormattedMessage, { id: "xpack.ml.anomaliesTable.linksMenu.configureRulesLabel", defaultMessage: "Configure job rules" }))); } return items; // eslint-disable-next-line react-hooks/exhaustive-deps }, [openInDiscoverUrl, discoverUrlError, viewExamples, viewSeries, canConfigureRules, isCategorizationAnomalyRecord]); return /*#__PURE__*/_react.default.createElement(_eui.EuiContextMenuPanel, { items: contextMenuItems, "data-test-subj": "mlAnomaliesListRowActionsMenu" }); }; exports.LinksMenuUI = LinksMenuUI; const LinksMenu = props => { const [isPopoverOpen, setPopoverOpen] = (0, _react.useState)(false); const onButtonClick = setPopoverOpen.bind(null, !isPopoverOpen); const closePopover = setPopoverOpen.bind(null, false); const button = /*#__PURE__*/_react.default.createElement(_eui.EuiButtonIcon, { size: "s", color: "text", onClick: onButtonClick, iconType: "gear", "aria-label": _i18n.i18n.translate('xpack.ml.anomaliesTable.linksMenu.selectActionAriaLabel', { defaultMessage: 'Select action for anomaly at {time}', values: { time: (0, _mlDateUtils.formatHumanReadableDateTimeSeconds)(props.anomaly.time) } }), "data-test-subj": "mlAnomaliesListRowActionsButton" }); return /*#__PURE__*/_react.default.createElement("div", null, /*#__PURE__*/_react.default.createElement(_eui.EuiPopover, { button: button, isOpen: isPopoverOpen, closePopover: closePopover, panelPaddingSize: "none", anchorPosition: "downLeft" }, /*#__PURE__*/_react.default.createElement(LinksMenuUI, (0, _extends2.default)({}, props, { onItemClick: closePopover })))); }; exports.LinksMenu = LinksMenu;