Skip to content

Commit

Permalink
[ML] Add Lens and Discover integration to index based Data Visualizer (
Browse files Browse the repository at this point in the history
  • Loading branch information
qn895 authored Feb 5, 2021
1 parent be53a06 commit 70d6143
Show file tree
Hide file tree
Showing 27 changed files with 805 additions and 102 deletions.
3 changes: 2 additions & 1 deletion x-pack/plugins/ml/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"spaces",
"management",
"licenseManagement",
"maps"
"maps",
"lens"
],
"server": true,
"ui": true,
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/ml/public/application/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ const App: FC<AppProps> = ({ coreStart, deps, appMountParams }) => {
data: deps.data,
security: deps.security,
licenseManagement: deps.licenseManagement,
lens: deps.lens,
storage: localStorage,
embeddable: deps.embeddable,
maps: deps.maps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@

import { FC } from 'react';
import { SavedSearchSavedObject } from '../../../../common/types/kibana';
import { IndexPattern } from '../../../../../../../src/plugins/data/public';
import type { IIndexPattern } from '../../../../../../../src/plugins/data/public';

declare const DataRecognizer: FC<{
indexPattern: IndexPattern;
indexPattern: IIndexPattern;
savedSearch: SavedSearchSavedObject | null;
results: {
count: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import { SharePluginStart } from '../../../../../../../src/plugins/share/public'
import { MlServicesContext } from '../../app';
import { IStorageWrapper } from '../../../../../../../src/plugins/kibana_utils/public';
import type { EmbeddableStart } from '../../../../../../../src/plugins/embeddable/public';
import { MapsStartApi } from '../../../../../maps/public';
import type { MapsStartApi } from '../../../../../maps/public';
import type { LensPublicStart } from '../../../../../lens/public';

interface StartPlugins {
data: DataPublicPluginStart;
Expand All @@ -26,6 +27,7 @@ interface StartPlugins {
share: SharePluginStart;
embeddable: EmbeddableStart;
maps?: MapsStartApi;
lens?: LensPublicStart;
}
export type StartServices = CoreStart &
StartPlugins & {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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.
*/

export interface CombinedQuery {
searchString: string | { [key: string]: any };
searchQueryLanguage: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
*/

export { FieldHistogramRequestConfig, FieldRequestConfig } from './request';
export type { CombinedQuery } from './combined_query';
Original file line number Diff line number Diff line change
Expand Up @@ -5,87 +5,198 @@
* 2.0.
*/

import React, { FC, useState } from 'react';
import React, { FC, useState, useEffect } from 'react';

import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { EuiSpacer, EuiText, EuiTitle, EuiFlexGroup } from '@elastic/eui';
import {
EuiSpacer,
EuiText,
EuiTitle,
EuiFlexGroup,
EuiFlexItem,
EuiCard,
EuiIcon,
} from '@elastic/eui';
import { Link } from 'react-router-dom';
import { IndexPattern } from '../../../../../../../../../src/plugins/data/public';
import { CreateJobLinkCard } from '../../../../components/create_job_link_card';
import { DataRecognizer } from '../../../../components/data_recognizer';
import { ML_PAGES } from '../../../../../../common/constants/ml_url_generator';
import {
DISCOVER_APP_URL_GENERATOR,
DiscoverUrlGeneratorState,
} from '../../../../../../../../../src/plugins/discover/public';
import { useMlKibana } from '../../../../contexts/kibana';
import { isFullLicense } from '../../../../license';
import { checkPermission } from '../../../../capabilities/check_capabilities';
import { mlNodesAvailable } from '../../../../ml_nodes_check';
import { useUrlState } from '../../../../util/url_state';
import type { IIndexPattern } from '../../../../../../../../../src/plugins/data/common';

interface Props {
indexPattern: IndexPattern;
indexPattern: IIndexPattern;
searchString?: string | { [key: string]: any };
searchQueryLanguage?: string;
}

export const ActionsPanel: FC<Props> = ({ indexPattern }) => {
export const ActionsPanel: FC<Props> = ({ indexPattern, searchString, searchQueryLanguage }) => {
const [recognizerResultsCount, setRecognizerResultsCount] = useState(0);
const [discoverLink, setDiscoverLink] = useState('');
const {
services: {
share: {
urlGenerators: { getUrlGenerator },
},
},
} = useMlKibana();
const [globalState] = useUrlState('_g');

const recognizerResults = {
count: 0,
onChange() {
setRecognizerResultsCount(recognizerResults.count);
},
};
const showCreateJob =
isFullLicense() &&
checkPermission('canCreateJob') &&
mlNodesAvailable() &&
indexPattern.timeFieldName !== undefined;
const createJobLink = `/${ML_PAGES.ANOMALY_DETECTION_CREATE_JOB}/advanced?index=${indexPattern.id}`;

useEffect(() => {
let unmounted = false;

const indexPatternId = indexPattern.id;
const getDiscoverUrl = async (): Promise<void> => {
const state: DiscoverUrlGeneratorState = {
indexPatternId,
};
if (searchString && searchQueryLanguage !== undefined) {
state.query = { query: searchString, language: searchQueryLanguage };
}
if (globalState?.time) {
state.timeRange = globalState.time;
}
if (globalState?.refreshInterval) {
state.refreshInterval = globalState.refreshInterval;
}

let discoverUrlGenerator;
try {
discoverUrlGenerator = getUrlGenerator(DISCOVER_APP_URL_GENERATOR);
} catch (error) {
// ignore error thrown when url generator is not available
return;
}

const discoverUrl = await discoverUrlGenerator.createUrl(state);
if (!unmounted) {
setDiscoverLink(discoverUrl);
}
};
getDiscoverUrl();
return () => {
unmounted = true;
};
}, [indexPattern, searchString, searchQueryLanguage, globalState]);

// Note we use display:none for the DataRecognizer section as it needs to be
// passed the recognizerResults object, and then run the recognizer check which
// controls whether the recognizer section is ultimately displayed.
return (
<div data-test-subj="mlDataVisualizerActionsPanel">
<EuiTitle size="s">
<h2>
<FormattedMessage
id="xpack.ml.datavisualizer.actionsPanel.createJobTitle"
defaultMessage="Create Job"
/>
</h2>
</EuiTitle>
<EuiSpacer size="s" />
<div style={recognizerResultsCount === 0 ? { display: 'none' } : {}}>
<EuiText size="s" color="subdued">
<p>
<FormattedMessage
id="xpack.ml.datavisualizer.actionsPanel.selectKnownConfigurationDescription"
defaultMessage="Select known configurations for recognized data:"
{showCreateJob && (
<>
<EuiTitle size="s">
<h2>
<FormattedMessage
id="xpack.ml.datavisualizer.actionsPanel.createJobTitle"
defaultMessage="Create Job"
/>
</h2>
</EuiTitle>
<EuiSpacer size="s" />
<div hidden={recognizerResultsCount === 0}>
<EuiText size="s" color="subdued">
<p>
<FormattedMessage
id="xpack.ml.datavisualizer.actionsPanel.selectKnownConfigurationDescription"
defaultMessage="Select known configurations for recognized data:"
/>
</p>
</EuiText>
<EuiSpacer size="m" />
<EuiFlexGroup gutterSize="l" responsive={true} wrap={true}>
<DataRecognizer
indexPattern={indexPattern}
savedSearch={null}
results={recognizerResults}
/>
</EuiFlexGroup>
<EuiSpacer size="l" />
</div>
<EuiText size="s" color="subdued">
<p>
<FormattedMessage
id="xpack.ml.datavisualizer.actionsPanel.createJobDescription"
defaultMessage="Use the Advanced job wizard to create a job to find anomalies in this data:"
/>
</p>
</EuiText>
<EuiSpacer size="m" />
<Link to={createJobLink}>
<CreateJobLinkCard
icon="createAdvancedJob"
title={i18n.translate('xpack.ml.datavisualizer.actionsPanel.advancedTitle', {
defaultMessage: 'Advanced',
})}
description={i18n.translate(
'xpack.ml.datavisualizer.actionsPanel.advancedDescription',
{
defaultMessage:
'Use the full range of options to create a job for more advanced use cases',
}
)}
data-test-subj="mlDataVisualizerCreateAdvancedJobCard"
/>
</Link>
<EuiSpacer size="m" />
</>
)}

{discoverLink && (
<>
<EuiTitle size="s">
<h2>
<FormattedMessage
id="xpack.ml.datavisualizer.actionsPanel.exploreTitle"
defaultMessage="Explore"
/>
</h2>
</EuiTitle>
<EuiSpacer size="m" />
<EuiFlexItem>
<EuiCard
data-test-subj="mlDataVisualizerViewInDiscoverCard"
icon={<EuiIcon size="xxl" type={`discoverApp`} />}
description={i18n.translate(
'xpack.ml.datavisualizer.actionsPanel.viewIndexInDiscoverDescription',
{
defaultMessage: 'Explore index in Discover',
}
)}
title={
<FormattedMessage
id="xpack.ml.datavisualizer.actionsPanel.discoverAppTitle"
defaultMessage="Discover"
/>
}
href={discoverLink}
/>
</p>
</EuiText>
<EuiSpacer size="m" />
<EuiFlexGroup gutterSize="l" responsive={true} wrap={true}>
<DataRecognizer
indexPattern={indexPattern}
savedSearch={null}
results={recognizerResults}
/>
</EuiFlexGroup>
<EuiSpacer size="l" />
</div>
<EuiText size="s" color="subdued">
<p>
<FormattedMessage
id="xpack.ml.datavisualizer.actionsPanel.createJobDescription"
defaultMessage="Use the Advanced job wizard to create a job to find anomalies in this data:"
/>
</p>
</EuiText>
<EuiSpacer size="m" />
<Link to={createJobLink}>
<CreateJobLinkCard
icon="createAdvancedJob"
title={i18n.translate('xpack.ml.datavisualizer.actionsPanel.advancedTitle', {
defaultMessage: 'Advanced',
})}
description={i18n.translate('xpack.ml.datavisualizer.actionsPanel.advancedDescription', {
defaultMessage:
'Use the full range of options to create a job for more advanced use cases',
})}
data-test-subj="mlDataVisualizerCreateAdvancedJobCard"
/>
</Link>
</EuiFlexItem>
</>
)}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import React from 'react';
import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types';
import { LoadingIndicator } from '../field_data_row/loading_indicator';
import { NotInDocsContent } from '../field_data_row/content_types';
import { FieldVisConfig } from '../../../stats_table/types';
import {
BooleanContent,
DateContent,
Expand All @@ -20,8 +19,10 @@ import {
OtherContent,
TextContent,
} from '../../../stats_table/components/field_data_expanded_row';
import { CombinedQuery, GeoPointContent } from './geo_point_content';
import { IndexPattern } from '../../../../../../../../../src/plugins/data/common/index_patterns/index_patterns';
import { GeoPointContent } from './geo_point_content';
import type { CombinedQuery } from '../../common';
import type { IndexPattern } from '../../../../../../../../../src/plugins/data/common/index_patterns/index_patterns';
import type { FieldVisConfig } from '../../../stats_table/types';

export const IndexBasedDataVisualizerExpandedRow = ({
item,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,17 @@ import React, { FC, useEffect, useState } from 'react';

import { EuiFlexItem } from '@elastic/eui';
import { ExamplesList } from '../../../index_based/components/field_data_row/examples_list';
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';
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';
import type { CombinedQuery } from '../../common';
import type { IndexPattern } from '../../../../../../../../../src/plugins/data/common/index_patterns/index_patterns';
import type { LayerDescriptor } from '../../../../../../../maps/common/descriptor_types';
import type { FieldVisConfig } from '../../../stats_table/types';

export interface CombinedQuery {
searchString: string | { [key: string]: any };
searchQueryLanguage: string;
}
export const GeoPointContent: FC<{
config: FieldVisConfig;
indexPattern: IndexPattern | undefined;
Expand Down
Loading

0 comments on commit 70d6143

Please sign in to comment.