Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Add embedded map to geo_point fields for Data Visualizer #88880

Merged
merged 26 commits into from
Jan 27, 2021
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ea3504e
[ML] Add map
qn895 Jan 15, 2021
2c92b5a
[ML] Refactor embedded map
qn895 Jan 20, 2021
7c30c47
[ML] update map inputs
qn895 Jan 15, 2021
11eaf09
[ML] update tests to test for geo field in file based
qn895 Jan 20, 2021
3a50b3c
[ML] Update cloning for ad jobs to use exclude_generated
qn895 Jan 20, 2021
c6dac9b
[ML] Update dependency
qn895 Jan 20, 2021
7aef966
[ML] Update icon classname
qn895 Jan 20, 2021
33ac1e8
[ML] Add formatting for number preview
qn895 Jan 20, 2021
9632ad8
[ML] Add columns not aligned, % formatting
qn895 Jan 20, 2021
b925e59
[ML] Consolidating header and content formatting
qn895 Jan 20, 2021
3118928
Revert "[ML] Update cloning for ad jobs to use exclude_generated"
qn895 Jan 21, 2021
fb0f337
[ML] Make maps an optional dependency
qn895 Jan 21, 2021
f537b8c
[ML] Update types
qn895 Jan 21, 2021
8a8ae4d
[ML] Update embedded map to not throw error
qn895 Jan 21, 2021
841d76d
[ML] Add tests for index based version
qn895 Jan 21, 2021
6147930
Merge remote-tracking branch 'upstream/master' into ml-embedded-map-d…
qn895 Jan 21, 2021
a56cd58
[ML] Update translations
qn895 Jan 21, 2021
62acfc4
[ML] Update types and summary table minwidth
qn895 Jan 21, 2021
11da3e8
[ML] Update shard sizes
qn895 Jan 25, 2021
7e81cd3
Merge remote-tracking branch 'upstream/master' into ml-embedded-map-d…
qn895 Jan 25, 2021
22f42fc
Merge remote-tracking branch 'upstream/master' into ml-embedded-map-d…
qn895 Jan 26, 2021
dd5ed19
[ML] Remove empty breakpoint
qn895 Jan 26, 2021
d44517f
[ML] Add support for fields in getFieldExamples
qn895 Jan 26, 2021
647a5d2
[ML] Rename rowsPerPage
qn895 Jan 26, 2021
6db1f38
[ML] Add type to doc
qn895 Jan 27, 2021
dca4051
Merge remote-tracking branch 'upstream/master' into ml-embedded-map-d…
qn895 Jan 27, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion x-pack/plugins/maps/public/embeddable/map_embeddable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ import {
MapEmbeddableInput,
MapEmbeddableOutput,
} from './types';
export { MapEmbeddableInput };
export { MapEmbeddableInput, MapEmbeddableOutput };

export class MapEmbeddable
extends Embeddable<MapEmbeddableInput, MapEmbeddableOutput>
Expand Down
9 changes: 5 additions & 4 deletions x-pack/plugins/ml/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
"uiActions",
"kibanaLegacy",
"indexPatternManagement",
"discover",
"maps"
"discover"
],
"optionalPlugins": [
"home",
"security",
"spaces",
"management",
"licenseManagement"
"licenseManagement",
"maps"
],
"server": true,
"ui": true,
Expand All @@ -36,7 +36,8 @@
"dashboard",
"savedObjects",
"home",
"spaces"
"spaces",
"maps"
],
"extraPublicDirs": [
"common"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@
*/

import React, { useEffect, useRef, useState } from 'react';
import uuid from 'uuid';

import { htmlIdGenerator } from '@elastic/eui';
import { LayerDescriptor } from '../../../../../maps/common/descriptor_types';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { MapEmbeddable, MapEmbeddableInput } from '../../../../../maps/public/embeddable';
import {
MapEmbeddable,
MapEmbeddableInput,
MapEmbeddableOutput,
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
} from '../../../../../maps/public/embeddable';
import { MAP_SAVED_OBJECT_TYPE, RenderTooltipContentParams } from '../../../../../maps/public';
import {
EmbeddableFactory,
ErrorEmbeddable,
isErrorEmbeddable,
ViewMode,
Expand All @@ -26,7 +32,7 @@ export function MlEmbeddedMapComponent({
mapEmbeddableInput?: MapEmbeddableInput;
renderTooltipContent?: (params: RenderTooltipContentParams) => JSX.Element;
}) {
const [embeddable, setEmbeddable] = useState<MapEmbeddable | ErrorEmbeddable | undefined>();
const [embeddable, setEmbeddable] = useState<ErrorEmbeddable | MapEmbeddable | undefined>();

const embeddableRoot: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
const baseLayers = useRef<LayerDescriptor[]>();
Expand All @@ -35,14 +41,11 @@ export function MlEmbeddedMapComponent({
services: { embeddable: embeddablePlugin, maps: mapsPlugin },
} = useMlKibana();

if (!embeddablePlugin) {
throw new Error('Embeddable start plugin not found');
}
if (!mapsPlugin) {
throw new Error('Maps start plugin not found');
}

const factory: any = embeddablePlugin.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE);
const factory:
| EmbeddableFactory<MapEmbeddableInput, MapEmbeddableOutput, MapEmbeddable>
| undefined = embeddablePlugin
? embeddablePlugin.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE)
: undefined;

// Update the layer list with updated geo points upon refresh
useEffect(() => {
Expand All @@ -62,10 +65,12 @@ export function MlEmbeddedMapComponent({
useEffect(() => {
async function setupEmbeddable() {
if (!factory) {
throw new Error('Map embeddable not found.');
// eslint-disable-next-line no-console
console.error('Map embeddable not found.');
return;
}
const input: MapEmbeddableInput = {
id: uuid.v4(),
id: htmlIdGenerator()(),
attributes: { title: '' },
filters: [],
hidePanelTitles: true,
Expand Down Expand Up @@ -95,7 +100,7 @@ export function MlEmbeddedMapComponent({
},
};

const embeddableObject: any = await factory.create(input);
const embeddableObject = await factory.create(input);

if (embeddableObject && !isErrorEmbeddable(embeddableObject)) {
const basemapLayerDescriptor = mapsPlugin
Expand Down Expand Up @@ -134,6 +139,17 @@ export function MlEmbeddedMapComponent({
}
}, [embeddable, embeddableRoot]);

if (!embeddablePlugin) {
// eslint-disable-next-line no-console
console.error('Embeddable start plugin not found');
return null;
}
if (!mapsPlugin) {
// eslint-disable-next-line no-console
console.error('Maps start plugin not found');
return null;
}

return (
<div
data-test-subj="mlEmbeddedMapContent"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import { EuiFlexItem } from '@elastic/eui';
import { Feature, Point } from 'geojson';
import type { FieldDataRowProps } from '../../../../stats_table/types/field_data_row';
import { DocumentStatsTable } from '../../../../stats_table/components/field_data_expanded_row/document_stats';
import { TopValues } from '../../../../index_based/components/field_data_row/top_values';
import { MlEmbeddedMapComponent } from '../../../../../components/ml_embedded_map';
import { convertWKTGeoToLonLat, getGeoPointsLayer } from './format_utils';
import { ExpandedRowContent } from '../../../../stats_table/components/field_data_expanded_row/expanded_row_content';
import { ExamplesList } from '../../../../index_based/components/field_data_row/examples_list';

export const DEFAULT_GEO_REGEX = RegExp('(?<lat>.+) (?<lon>.+)');

Expand All @@ -26,14 +26,14 @@ export const GeoPointContent: FC<FieldDataRowProps> = ({ config }) => {
const geoPointsFeatures: Array<Feature<Point>> = [];

// reformatting the top values from POINT (-2.359207 51.37837) to (-2.359207, 51.37837)
const formattedTopValues = [];
const formattedExamples = [];

for (let i = 0; i < stats.topValues.length; i++) {
const value = stats.topValues[i];
const coordinates = convertWKTGeoToLonLat(value.key);
if (coordinates) {
const formattedGeoPoint = `(${coordinates.lat}, ${coordinates.lon})`;
formattedTopValues.push({ key: formattedGeoPoint, doc_count: value.doc_count });
formattedExamples.push(coordinates);

geoPointsFeatures.push({
type: 'Feature',
Expand All @@ -52,7 +52,7 @@ export const GeoPointContent: FC<FieldDataRowProps> = ({ config }) => {

if (geoPointsFeatures.length > 0) {
return {
stats: { ...stats, topValues: formattedTopValues },
examples: formattedExamples,
layerList: [getGeoPointsLayer(geoPointsFeatures)],
};
}
Expand All @@ -61,9 +61,9 @@ export const GeoPointContent: FC<FieldDataRowProps> = ({ config }) => {
return (
<ExpandedRowContent dataTestSubj={'mlDVGeoPointContent'}>
<DocumentStatsTable config={config} />
{formattedResults && Array.isArray(formattedResults.stats!.topValues) && (
{formattedResults && Array.isArray(formattedResults.examples) && (
<EuiFlexItem>
<TopValues stats={formattedResults.stats} barColor="secondary" />
<ExamplesList examples={formattedResults.examples} />
</EuiFlexItem>
)}
{formattedResults && Array.isArray(formattedResults.layerList) && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
OtherContent,
TextContent,
} from '../../../stats_table/components/field_data_expanded_row';
import { GeoPointContent } from './geo_point_content';
import { CombinedQuery, GeoPointContent } from './geo_point_content';
import { IndexPattern } from '../../../../../../../../../src/plugins/data/common/index_patterns/index_patterns';

export const IndexBasedDataVisualizerExpandedRow = ({
Expand All @@ -29,7 +29,7 @@ export const IndexBasedDataVisualizerExpandedRow = ({
}: {
item: FieldVisConfig;
indexPattern: IndexPattern | undefined;
combinedQuery: any;
combinedQuery: CombinedQuery;
}) => {
const config = item;
const { loading, type, existsInDocs, fieldName } = config;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,20 @@ import { FieldVisConfig } from '../../../stats_table/types';
import { IndexPattern } from '../../../../../../../../../src/plugins/data/common/index_patterns/index_patterns';
import { MlEmbeddedMapComponent } from '../../../../components/ml_embedded_map';
import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { CreateLayerDescriptorParams } from '../../../../../../../maps/public/classes/sources/es_search_source';
import { ES_GEO_FIELD_TYPE } from '../../../../../../../maps/common/constants';
import { LayerDescriptor } from '../../../../../../../maps/common/descriptor_types';
import { useMlKibana } from '../../../../contexts/kibana';
import { DocumentStatsTable } from '../../../stats_table/components/field_data_expanded_row/document_stats';
import { ExpandedRowContent } from '../../../stats_table/components/field_data_expanded_row/expanded_row_content';

export interface CombinedQuery {
searchString: string | { [key: string]: any };
searchQueryLanguage: string;
}
export const GeoPointContent: FC<{
config: FieldVisConfig;
indexPattern: IndexPattern | undefined;
combinedQuery: { searchString: string; searchQueryLanguage: string };
combinedQuery: CombinedQuery;
}> = ({ config, indexPattern, combinedQuery }) => {
const { stats } = config;
const [layerList, setLayerList] = useState<LayerDescriptor[]>([]);
Expand All @@ -40,7 +42,7 @@ export const GeoPointContent: FC<{
config.fieldName !== undefined &&
config.type === ML_JOB_FIELD_TYPES.GEO_POINT
) {
const params: CreateLayerDescriptorParams = {
const params = {
indexPatternId: indexPattern.id,
geoFieldName: config.fieldName,
geoFieldType: config.type as ES_GEO_FIELD_TYPE.GEO_POINT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,29 @@ interface Props {
}

export const ExamplesList: FC<Props> = ({ examples }) => {
if (
examples === undefined ||
examples === null ||
!Array.isArray(examples) ||
examples.length === 0
) {
if (examples === undefined || examples === null || !Array.isArray(examples)) {
return null;
}

const examplesContent = examples.map((example, i) => {
return (
<EuiListGroupItem
className="mlFieldDataCard__codeContent"
size="s"
key={`example_${i}`}
label={typeof example === 'string' ? example : JSON.stringify(example)}
let examplesContent;
if (examples.length === 0) {
examplesContent = (
<FormattedMessage
id="xpack.ml.fieldDataCard.examplesList.noExamplesMessage"
defaultMessage="No examples were obtained for this field"
/>
);
});
} else {
examplesContent = examples.map((example, i) => {
return (
<EuiListGroupItem
className="mlFieldDataCard__codeContent"
size="s"
key={`example_${i}`}
label={typeof example === 'string' ? example : JSON.stringify(example)}
/>
);
});
}

return (
<div data-test-subj="mlFieldDataExamplesList">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ import classNames from 'classnames';
import { kibanaFieldFormat } from '../../../../../formatters/kibana_field_format';
import { roundToDecimalPlace } from '../../../../../formatters/round_to_decimal_place';
import { ExpandedRowFieldHeader } from '../../../../stats_table/components/expanded_row_field_header';
import { FieldVisStats } from '../../../../stats_table/types';

interface Props {
stats: any;
stats: FieldVisStats | undefined;
fieldFormat?: any;
barColor?: 'primary' | 'secondary' | 'danger' | 'subdued' | 'accent';
compressed?: boolean;
Expand All @@ -38,6 +39,7 @@ function getPercentLabel(docCount: number, topValuesSampleSize: number): string
}

export const TopValues: FC<Props> = ({ stats, fieldFormat, barColor, compressed }) => {
if (stats === undefined) return null;
const {
topValues,
topValuesSampleSize,
Expand All @@ -54,7 +56,7 @@ export const TopValues: FC<Props> = ({ stats, fieldFormat, barColor, compressed

<div data-test-subj="mlFieldDataTopValues" className={'mlFieldDataTopValuesContainer'}>
{Array.isArray(topValues) &&
topValues.map((value: any) => (
topValues.map((value) => (
<EuiFlexGroup gutterSize="xs" alignItems="center" key={value.key}>
<EuiFlexItem
grow={false}
Expand All @@ -78,14 +80,16 @@ export const TopValues: FC<Props> = ({ stats, fieldFormat, barColor, compressed
size="m"
/>
</EuiFlexItem>
<EuiFlexItem
grow={false}
className={classNames('eui-textTruncate', 'mlTopValuesPercentLabelContainer')}
>
<EuiText size="xs" textAlign="left" color="subdued">
{getPercentLabel(value.doc_count, progressBarMax)}
</EuiText>
</EuiFlexItem>
{progressBarMax !== undefined && (
<EuiFlexItem
grow={false}
className={classNames('eui-textTruncate', 'mlTopValuesPercentLabelContainer')}
>
<EuiText size="xs" textAlign="left" color="subdued">
{getPercentLabel(value.doc_count, progressBarMax)}
</EuiText>
</EuiFlexItem>
)}
</EuiFlexGroup>
))}
{isTopValuesSampled === true && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,7 @@ export const Page: FC = () => {
<IndexBasedDataVisualizerExpandedRow
item={item}
indexPattern={currentIndexPattern}
combinedQuery={{ searchQueryLanguage, searchString, searchQuery }}
combinedQuery={{ searchQueryLanguage, searchString }}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
}
.mlDataVisualizerSummaryTable {
max-width: 350px;
min-width: 250px;
.euiTableRow > .euiTableRowCell {
border-bottom: 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,6 @@ export function loadFullJob(jobId) {
});
}

export function loadClonableJob(jobId) {
return new Promise((resolve, reject) => {
ml.jobs
.jobs([jobId], true)
.then((jobs) => {
if (jobs.length) {
resolve(jobs[0]);
} else {
throw new Error(`Could not find job ${jobId}`);
}
})
.catch((error) => {
reject(error);
});
});
}

export function isStartable(jobs) {
return jobs.some(
(j) => j.datafeedState === DATAFEED_STATE.STOPPED && j.jobState !== JOB_STATE.CLOSING
Expand Down Expand Up @@ -197,12 +180,10 @@ function showResults(resp, action) {

export async function cloneJob(jobId) {
try {
const [job, originalJob] = await Promise.all([loadClonableJob(jobId), loadFullJob(jobId)]);
if (job.custom_settings && originalJob?.custom_settings?.created_by) {
const job = await loadFullJob(jobId);
if (job.custom_settings && job.custom_settings.created_by) {
// if the job is from a wizards, i.e. contains a created_by property
// use tempJobCloningObjects to temporarily store the job
job.custom_settings.created_by = originalJob?.custom_settings?.created_by;

mlJobService.tempJobCloningObjects.job = job;

if (
Expand Down Expand Up @@ -233,7 +214,6 @@ export async function cloneJob(jobId) {
// otherwise use the tempJobCloningObjects
mlJobService.tempJobCloningObjects.job = job;
}
mlJobService.tempJobCloningObjects.job = job;

if (job.calendars) {
mlJobService.tempJobCloningObjects.calendars = await mlCalendarService.fetchCalendarsByIds(
Expand Down
Loading