diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js
index 2029211c98970..678a74c83df4f 100644
--- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js
+++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js
@@ -15,6 +15,7 @@ import {
EuiFlexItem,
EuiIconTip,
EuiToolTip,
+ htmlIdGenerator
} from '@elastic/eui';
import {
@@ -31,7 +32,11 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { MlTooltipComponent } from '../../components/chart_tooltip';
import { withKibana } from '../../../../../../../src/plugins/kibana_react/public';
+import { useMlKibana } from '../../contexts/kibana';
import { ML_JOB_AGGREGATION } from '../../../../common/constants/aggregation_types';
+import { AnomalySource } from '../../../maps/anomaly_source';
+import { CUSTOM_COLOR_RAMP } from '../../../maps/anomaly_layer_wizard_factory';
+import { LAYER_TYPE } from '../../../../../maps/common';
import { ExplorerChartsErrorCallOuts } from './explorer_charts_error_callouts';
import { addItemToRecentlyAccessed } from '../../util/recently_accessed';
import { EmbeddedMapComponentWrapper } from './explorer_chart_embedded_map';
@@ -53,6 +58,10 @@ const textViewButton = i18n.translate(
const mapsPluginMessage = i18n.translate('xpack.ml.explorer.charts.mapsPluginMissingMessage', {
defaultMessage: 'maps or embeddable start plugin not found',
});
+const openInMapsPluginMessage = i18n.translate('xpack.ml.explorer.charts.openInMapsPluginMessage', {
+ defaultMessage: 'Open in Maps',
+});
+
// create a somewhat unique ID
// from charts metadata for React's key attribute
@@ -79,6 +88,72 @@ function ExplorerChartContainer({
chartsService,
}) {
const [explorerSeriesLink, setExplorerSeriesLink] = useState('');
+ const [mapsLink, setMapsLink] = useState('');
+
+ const {
+ services: { data, share,
+ application: { navigateToApp }, },
+ } = useMlKibana();
+
+ // TODO: pull in layer name constant and wrap in useCallback if poss
+ const getMapsLink = async () => {
+ const initialLayers = [{
+ id: htmlIdGenerator()(),
+ type: LAYER_TYPE.GEOJSON_VECTOR,
+ sourceDescriptor: AnomalySource.createDescriptor({
+ jobId: series.jobId,
+ typicalActual: 'actual',
+ }),
+ style: {
+ type: 'VECTOR',
+ properties: {
+ fillColor: CUSTOM_COLOR_RAMP,
+ lineColor: CUSTOM_COLOR_RAMP,
+ },
+ isTimeAware: false,
+ },
+ },
+ {
+ id: htmlIdGenerator()(),
+ type: LAYER_TYPE.GEOJSON_VECTOR,
+ sourceDescriptor: AnomalySource.createDescriptor({
+ jobId: series.jobId,
+ typicalActual: 'typical',
+ }),
+ style: {
+ type: 'VECTOR',
+ properties: {
+ fillColor: CUSTOM_COLOR_RAMP,
+ lineColor: CUSTOM_COLOR_RAMP,
+ },
+ isTimeAware: false,
+ },
+ },
+ {
+ id: htmlIdGenerator()(),
+ type: LAYER_TYPE.GEOJSON_VECTOR,
+ sourceDescriptor: AnomalySource.createDescriptor({
+ jobId: series.jobId,
+ typicalActual: 'typical to actual',
+ }),
+ style: {
+ type: 'VECTOR',
+ properties: {
+ fillColor: CUSTOM_COLOR_RAMP,
+ lineColor: CUSTOM_COLOR_RAMP,
+ },
+ isTimeAware: false,
+ },
+ }];
+
+ const locator = share.url.locators.get('MAPS_APP_LOCATOR');
+ const location = await locator.getLocation({
+ initialLayers: initialLayers,
+ timeRange: data.query.timefilter.timefilter.getTime(),
+ });
+
+ return location;
+ };
useEffect(() => {
let isCancelled = false;
@@ -98,6 +173,26 @@ function ExplorerChartContainer({
};
}, [mlLocator, series]);
+ useEffect(function getMapsPluginLink() {
+ if (!series) return;
+ let isCancelled = false;
+ const generateLink = async () => {
+ if (!isCancelled) {
+ try {
+ const mapsLink = await getMapsLink();
+ setMapsLink(mapsLink?.path);
+ } catch (error) {
+ console.error(error);
+ setMapsLink('');
+ }
+ }
+ };
+ generateLink().catch(console.error);;
+ return () => {
+ isCancelled = true;
+ };
+ }, [series]);
+
const chartRef = useRef(null);
const chartTheme = chartsService.theme.useChartsTheme();
@@ -191,6 +286,20 @@ function ExplorerChartContainer({
)}
+ {chartType === CHART_TYPE.GEO_MAP && mapsLink ? (
+
+ {
+ await navigateToApp('maps', { path: mapsLink });
+ }}
+ >
+
+
+
+ ) : null}
diff --git a/x-pack/plugins/ml/public/maps/anomaly_layer_wizard_factory.tsx b/x-pack/plugins/ml/public/maps/anomaly_layer_wizard_factory.tsx
index 260d058b78e78..e4309247a272a 100644
--- a/x-pack/plugins/ml/public/maps/anomaly_layer_wizard_factory.tsx
+++ b/x-pack/plugins/ml/public/maps/anomaly_layer_wizard_factory.tsx
@@ -26,7 +26,7 @@ import type { MlPluginStart, MlStartDependencies } from '../plugin';
import type { MlApiServices } from '../application/services/ml_api_service';
export const ML_ANOMALY = 'ML_ANOMALIES';
-const CUSTOM_COLOR_RAMP = {
+export const CUSTOM_COLOR_RAMP = {
type: STYLE_TYPE.DYNAMIC,
options: {
customColorRamp: SEVERITY_COLOR_RAMP,
diff --git a/x-pack/plugins/ml/public/maps/anomaly_source.tsx b/x-pack/plugins/ml/public/maps/anomaly_source.tsx
index e2d92a730d95a..72da52ce501c5 100644
--- a/x-pack/plugins/ml/public/maps/anomaly_source.tsx
+++ b/x-pack/plugins/ml/public/maps/anomaly_source.tsx
@@ -59,7 +59,7 @@ export class AnomalySource implements IVectorSource {
constructor(sourceDescriptor: Partial, adapters?: Adapters) {
this._descriptor = AnomalySource.createDescriptor(sourceDescriptor);
}
- // TODO: implement query awareness
+
async getGeoJsonWithMeta(
layerName: string,
searchFilters: VectorSourceRequestMeta,
@@ -77,7 +77,7 @@ export class AnomalySource implements IVectorSource {
data: results,
meta: {
// Set this to true if data is incomplete (e.g. capping number of results to first 1k)
- areResultsTrimmed: false,
+ areResultsTrimmed: results.features.length === 1000,
},
};
}
@@ -147,8 +147,18 @@ export class AnomalySource implements IVectorSource {
return null;
}
- getSourceStatus() {
- return { tooltipContent: null, areResultsTrimmed: true };
+ getSourceStatus(sourceDataRequest?: DataRequest): SourceStatus {
+ const featureCollection = sourceDataRequest ? sourceDataRequest.getData() : null;
+ const meta = sourceDataRequest
+ ? (sourceDataRequest.getMeta())
+ : null;
+ if (!featureCollection || !meta) {
+ return {
+ tooltipContent: null,
+ areResultsTrimmed: false,
+ };
+ }
+ return { tooltipContent: null, areResultsTrimmed: meta?.areEntitiesTrimmed ?? false };
}
getType(): string {
@@ -222,12 +232,15 @@ export class AnomalySource implements IVectorSource {
}
getSourceTooltipContent(sourceDataRequest?: DataRequest): SourceStatus {
+ const meta = sourceDataRequest
+ ? (sourceDataRequest.getMeta())
+ : null;
return {
tooltipContent: i18n.translate('xpack.ml.maps.sourceTooltip', {
defaultMessage: 'Shows anomalies',
}),
// set to true if data is incomplete (we limit to first 1000 results)
- areResultsTrimmed: true,
+ areResultsTrimmed: meta?.areResultsTrimmed ?? false,
};
}