diff --git a/src/plugins/vis_types/timeseries/public/application/components/lib/external_url_error_modal.tsx b/src/plugins/vis_types/timeseries/public/application/components/lib/external_url_error_modal.tsx
new file mode 100644
index 0000000000000..43016e05dfd40
--- /dev/null
+++ b/src/plugins/vis_types/timeseries/public/application/components/lib/external_url_error_modal.tsx
@@ -0,0 +1,61 @@
+/*
+ * 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 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React from 'react';
+import { FormattedMessage } from '@kbn/i18n/react';
+import {
+ EuiButton,
+ EuiModal,
+ EuiModalBody,
+ EuiModalFooter,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
+ EuiTextColor,
+} from '@elastic/eui';
+
+interface ExternalUrlErrorModalProps {
+ url: string;
+ handleClose: () => void;
+}
+
+export const ExternalUrlErrorModal = ({ url, handleClose }: ExternalUrlErrorModalProps) => (
+
+
+
+
+
+
+
+
+ {url}
+
+ ),
+ externalUrlPolicy: 'externalUrl.policy',
+ kibanaConfigFileName: 'kibana.yml',
+ }}
+ />
+
+
+
+
+
+
+
+);
diff --git a/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/vis.js b/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/vis.js
index 7b1db4b362647..4a4db04998389 100644
--- a/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/vis.js
+++ b/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/vis.js
@@ -17,6 +17,7 @@ import { createFieldFormatter } from '../../lib/create_field_formatter';
import { isSortable } from './is_sortable';
import { EuiToolTip, EuiIcon } from '@elastic/eui';
import { replaceVars } from '../../lib/replace_vars';
+import { ExternalUrlErrorModal } from '../../lib/external_url_error_modal';
import { FIELD_FORMAT_IDS } from '../../../../../../../../plugins/field_formats/common';
import { FormattedMessage } from '@kbn/i18n/react';
import { getFieldFormats, getCoreStart } from '../../../../services';
@@ -53,12 +54,24 @@ class TableVis extends Component {
const DateFormat = fieldFormatsService.getType(FIELD_FORMAT_IDS.DATE);
this.dateFormatter = new DateFormat({}, this.props.getConfig);
+
+ this.state = {
+ showExternalUrlErrorModal: false,
+ };
}
get visibleSeries() {
return get(this.props, 'model.series', []).filter((series) => !series.hidden);
}
+ handleDrilldownUrlClick = (event, url) => {
+ const validatedUrl = getCoreStart().http.externalUrl.validateUrl(url);
+ if (!validatedUrl) {
+ event.preventDefault();
+ this.setState({ accessDeniedDrilldownUrl: url, showExternalUrlErrorModal: true });
+ }
+ };
+
renderRow = (row) => {
const { model, fieldFormatMap, getConfig } = this.props;
@@ -74,7 +87,11 @@ class TableVis extends Component {
if (model.drilldown_url) {
const url = replaceVars(model.drilldown_url, {}, { key: row.key });
- rowDisplay = {rowDisplay};
+ rowDisplay = (
+ this.handleDrilldownUrlClick(event, url)}>
+ {rowDisplay}
+
+ );
}
const columns = row.series
@@ -213,6 +230,8 @@ class TableVis extends Component {
);
}
+ closeExternalUrlErrorModal = () => this.setState({ showExternalUrlErrorModal: false });
+
render() {
const { visData, model } = this.props;
const header = this.renderHeader();
@@ -239,16 +258,24 @@ class TableVis extends Component {
);
}
return (
-
-
-
+ <>
+
+
+
+ {this.state.showExternalUrlErrorModal && (
+
+ )}
+ >
);
}
}
diff --git a/src/plugins/vis_types/timeseries/public/application/components/vis_types/top_n/vis.js b/src/plugins/vis_types/timeseries/public/application/components/vis_types/top_n/vis.js
index 8176f6ece2805..fe199ecd4302f 100644
--- a/src/plugins/vis_types/timeseries/public/application/components/vis_types/top_n/vis.js
+++ b/src/plugins/vis_types/timeseries/public/application/components/vis_types/top_n/vis.js
@@ -15,10 +15,11 @@ import { getLastValue } from '../../../../../common/last_value_utils';
import { isBackgroundInverted } from '../../../lib/set_is_reversed';
import { replaceVars } from '../../lib/replace_vars';
import PropTypes from 'prop-types';
-import React from 'react';
+import React, { useState, useCallback } from 'react';
import { sortBy, first, get } from 'lodash';
import { DATA_FORMATTERS } from '../../../../../common/enums';
import { getOperator, shouldOperate } from '../../../../../common/operators_utils';
+import { ExternalUrlErrorModal } from '../../lib/external_url_error_modal';
function sortByDirection(data, direction, fn) {
if (direction === 'desc') {
@@ -41,6 +42,9 @@ function sortSeries(visData, model) {
}
function TopNVisualization(props) {
+ const [accessDeniedDrilldownUrl, setAccessDeniedDrilldownUrl] = useState(null);
+ const [showExternalUrlErrorModal, setShowExternalUrlErrorModal] = useState(false);
+ const coreStart = getCoreStart();
const { backgroundColor, model, visData, fieldFormatMap, getConfig } = props;
const series = sortSeries(visData, model).map((item) => {
@@ -83,13 +87,27 @@ function TopNVisualization(props) {
if (model.drilldown_url) {
params.onClick = (item) => {
const url = replaceVars(model.drilldown_url, {}, { key: item.label });
- getCoreStart().application.navigateToUrl(url);
+ const validatedUrl = coreStart.http.externalUrl.validateUrl(url);
+ if (validatedUrl) {
+ coreStart.application.navigateToUrl(url);
+ } else {
+ setAccessDeniedDrilldownUrl(url);
+ setShowExternalUrlErrorModal(true);
+ }
};
}
+ const closeExternalUrlErrorModal = useCallback(() => setShowExternalUrlErrorModal(false), []);
+
return (
+ {showExternalUrlErrorModal && (
+
+ )}
);
}