mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[ML] Add recognized modules links for Index data visualizer (#131342)
* [ML] Add dynamic registration of links for both index and file * [ML] Consolidate type imports * [ML] Revert uptime changes * [ML] Fix cards visible when canDisplay is false * [ML] Shorten create job text * [ML] Remove as assertions * [ML] Rename to GetAdditionalLinks Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
fe76adbc3a
commit
a632484214
17 changed files with 277 additions and 165 deletions
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC, ReactElement } from 'react';
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import {
|
||||
EuiIcon,
|
||||
|
@ -18,8 +18,8 @@ import {
|
|||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
|
||||
interface Props {
|
||||
icon: IconType | ReactElement;
|
||||
export interface LinkCardProps {
|
||||
icon: IconType;
|
||||
iconAreaLabel?: string;
|
||||
title: any;
|
||||
description: any;
|
||||
|
@ -31,7 +31,7 @@ interface Props {
|
|||
|
||||
// Component for rendering a card which links to the Create Job page, displaying an
|
||||
// icon, card title, description and link.
|
||||
export const LinkCard: FC<Props> = ({
|
||||
export const LinkCard: FC<LinkCardProps> = ({
|
||||
icon,
|
||||
iconAreaLabel,
|
||||
title,
|
||||
|
@ -39,7 +39,7 @@ export const LinkCard: FC<Props> = ({
|
|||
onClick,
|
||||
href,
|
||||
isDisabled,
|
||||
'data-test-subj': dateTestSubj,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}) => {
|
||||
const linkHrefAndOnClickProps = {
|
||||
...(href ? { href } : {}),
|
||||
|
@ -58,7 +58,7 @@ export const LinkCard: FC<Props> = ({
|
|||
background: 'transparent',
|
||||
outline: 'none',
|
||||
}}
|
||||
data-test-subj={dateTestSubj}
|
||||
data-test-subj={dataTestSubj}
|
||||
color="subdued"
|
||||
{...linkHrefAndOnClickProps}
|
||||
>
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export type { ResultLink } from './results_links';
|
||||
export type { ResultLink, GetAdditionalLinks, GetAdditionalLinksParams } from './results_links';
|
||||
export { ResultsLinks } from './results_links';
|
||||
|
|
|
@ -12,10 +12,23 @@ import { EuiFlexGroup, EuiFlexItem, EuiCard, EuiIcon } from '@elastic/eui';
|
|||
import { TimeRange, RefreshInterval } from '@kbn/data-plugin/public';
|
||||
import { FindFileStructureResponse } from '@kbn/file-upload-plugin/common';
|
||||
import type { FileUploadPluginStart } from '@kbn/file-upload-plugin/public';
|
||||
import { flatten } from 'lodash';
|
||||
import { LinkCardProps } from '../link_card/link_card';
|
||||
import { useDataVisualizerKibana } from '../../../kibana_context';
|
||||
import { isDefined } from '../../util/is_defined';
|
||||
|
||||
type LinkType = 'file' | 'index';
|
||||
|
||||
export interface GetAdditionalLinksParams {
|
||||
dataViewId: string;
|
||||
dataViewTitle?: string;
|
||||
globalState?: any;
|
||||
}
|
||||
|
||||
export type GetAdditionalLinks = Array<
|
||||
(params: GetAdditionalLinksParams) => Promise<ResultLink[] | undefined>
|
||||
>;
|
||||
|
||||
export interface ResultLink {
|
||||
id: string;
|
||||
type: LinkType;
|
||||
|
@ -24,7 +37,7 @@ export interface ResultLink {
|
|||
description: string;
|
||||
getUrl(params?: any): Promise<string>;
|
||||
canDisplay(params?: any): Promise<boolean>;
|
||||
dataTestSubj?: string;
|
||||
'data-test-subj'?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
|
@ -34,7 +47,7 @@ interface Props {
|
|||
timeFieldName?: string;
|
||||
createDataView: boolean;
|
||||
showFilebeatFlyout(): void;
|
||||
additionalLinks: ResultLink[];
|
||||
getAdditionalLinks?: GetAdditionalLinks;
|
||||
}
|
||||
|
||||
interface GlobalState {
|
||||
|
@ -51,7 +64,7 @@ export const ResultsLinks: FC<Props> = ({
|
|||
timeFieldName,
|
||||
createDataView,
|
||||
showFilebeatFlyout,
|
||||
additionalLinks,
|
||||
getAdditionalLinks,
|
||||
}) => {
|
||||
const {
|
||||
services: {
|
||||
|
@ -70,7 +83,7 @@ export const ResultsLinks: FC<Props> = ({
|
|||
const [discoverLink, setDiscoverLink] = useState('');
|
||||
const [indexManagementLink, setIndexManagementLink] = useState('');
|
||||
const [dataViewsManagementLink, setDataViewsManagementLink] = useState('');
|
||||
const [generatedLinks, setGeneratedLinks] = useState<Record<string, string>>({});
|
||||
const [asyncHrefCards, setAsyncHrefCards] = useState<LinkCardProps[]>();
|
||||
|
||||
useEffect(() => {
|
||||
let unmounted = false;
|
||||
|
@ -93,22 +106,30 @@ export const ResultsLinks: FC<Props> = ({
|
|||
|
||||
getDiscoverUrl();
|
||||
|
||||
Promise.all(
|
||||
additionalLinks.map(async ({ canDisplay, getUrl }) => {
|
||||
if ((await canDisplay({ indexPatternId: dataViewId })) === false) {
|
||||
return null;
|
||||
}
|
||||
return getUrl({ globalState, indexPatternId: dataViewId });
|
||||
})
|
||||
).then((urls) => {
|
||||
const linksById = urls.reduce((acc, url, i) => {
|
||||
if (url !== null) {
|
||||
acc[additionalLinks[i].id] = url;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
setGeneratedLinks(linksById);
|
||||
});
|
||||
if (Array.isArray(getAdditionalLinks)) {
|
||||
Promise.all(
|
||||
getAdditionalLinks.map(async (asyncCardGetter) => {
|
||||
const results = await asyncCardGetter({
|
||||
dataViewId,
|
||||
});
|
||||
if (Array.isArray(results)) {
|
||||
return await Promise.all(
|
||||
results.map(async (c) => ({
|
||||
...c,
|
||||
canDisplay: await c.canDisplay(),
|
||||
href: await c.getUrl(),
|
||||
}))
|
||||
);
|
||||
}
|
||||
})
|
||||
).then((cards) => {
|
||||
setAsyncHrefCards(
|
||||
flatten(cards)
|
||||
.filter(isDefined)
|
||||
.filter((d) => d.canDisplay === true)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (!unmounted) {
|
||||
setIndexManagementLink(
|
||||
|
@ -244,16 +265,15 @@ export const ResultsLinks: FC<Props> = ({
|
|||
onClick={showFilebeatFlyout}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{additionalLinks
|
||||
.filter(({ id }) => generatedLinks[id] !== undefined)
|
||||
.map((link) => (
|
||||
{Array.isArray(asyncHrefCards) &&
|
||||
asyncHrefCards.map((link) => (
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
icon={<EuiIcon size="xxl" type={link.icon} />}
|
||||
data-test-subj="fileDataVisLink"
|
||||
title={link.title}
|
||||
description={link.description}
|
||||
href={generatedLinks[link.id]}
|
||||
href={link.href}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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 function isDefined<T>(argument: T | undefined | null): argument is T {
|
||||
return argument !== undefined && argument !== null;
|
||||
}
|
|
@ -369,7 +369,7 @@ export class FileDataVisualizerView extends Component {
|
|||
hideBottomBar={this.hideBottomBar}
|
||||
savedObjectsClient={this.savedObjectsClient}
|
||||
fileUpload={this.props.fileUpload}
|
||||
resultsLinks={this.props.resultsLinks}
|
||||
getAdditionalLinks={this.props.getAdditionalLinks}
|
||||
capabilities={this.props.capabilities}
|
||||
/>
|
||||
|
||||
|
|
|
@ -585,7 +585,7 @@ export class ImportView extends Component {
|
|||
timeFieldName={timeFieldName}
|
||||
createDataView={createDataView}
|
||||
showFilebeatFlyout={this.showFilebeatFlyout}
|
||||
additionalLinks={this.props.resultsLinks ?? []}
|
||||
getAdditionalLinks={this.props.getAdditionalLinks ?? []}
|
||||
/>
|
||||
|
||||
{isFilebeatFlyoutVisible && (
|
||||
|
|
|
@ -11,14 +11,14 @@ import { getCoreStart, getPluginsStart } from '../../kibana_services';
|
|||
|
||||
// @ts-ignore
|
||||
import { FileDataVisualizerView } from './components/file_data_visualizer_view';
|
||||
import { ResultLink } from '../common/components/results_links';
|
||||
import { GetAdditionalLinks } from '../common/components/results_links';
|
||||
|
||||
interface Props {
|
||||
additionalLinks?: ResultLink[];
|
||||
getAdditionalLinks?: GetAdditionalLinks;
|
||||
}
|
||||
|
||||
export type FileDataVisualizerSpec = typeof FileDataVisualizer;
|
||||
export const FileDataVisualizer: FC<Props> = ({ additionalLinks }) => {
|
||||
export const FileDataVisualizer: FC<Props> = ({ getAdditionalLinks }) => {
|
||||
const coreStart = getCoreStart();
|
||||
const { data, maps, embeddable, discover, share, security, fileUpload, cloud } =
|
||||
getPluginsStart();
|
||||
|
@ -45,7 +45,7 @@ export const FileDataVisualizer: FC<Props> = ({ additionalLinks }) => {
|
|||
savedObjectsClient={coreStart.savedObjects.client}
|
||||
http={coreStart.http}
|
||||
fileUpload={fileUpload}
|
||||
resultsLinks={additionalLinks}
|
||||
getAdditionalLinks={getAdditionalLinks}
|
||||
capabilities={coreStart.application.capabilities}
|
||||
/>
|
||||
</CloudContext>
|
||||
|
|
|
@ -11,28 +11,31 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||
import { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { flatten } from 'lodash';
|
||||
import { LinkCardProps } from '../../../common/components/link_card/link_card';
|
||||
import { useDataVisualizerKibana } from '../../../kibana_context';
|
||||
import { useUrlState } from '../../../common/util/url_state';
|
||||
import { LinkCard } from '../../../common/components/link_card';
|
||||
import { ResultLink } from '../../../common/components/results_links';
|
||||
import { GetAdditionalLinks } from '../../../common/components/results_links';
|
||||
import { isDefined } from '../../../common/util/is_defined';
|
||||
|
||||
interface Props {
|
||||
dataView: DataView;
|
||||
searchString?: string | { [key: string]: any };
|
||||
searchQueryLanguage?: string;
|
||||
additionalLinks: ResultLink[];
|
||||
getAdditionalLinks?: GetAdditionalLinks;
|
||||
}
|
||||
|
||||
export const ActionsPanel: FC<Props> = ({
|
||||
dataView,
|
||||
searchString,
|
||||
searchQueryLanguage,
|
||||
additionalLinks,
|
||||
getAdditionalLinks,
|
||||
}) => {
|
||||
const [globalState] = useUrlState('_g');
|
||||
|
||||
const [discoverLink, setDiscoverLink] = useState('');
|
||||
const [generatedLinks, setGeneratedLinks] = useState<Record<string, string>>({});
|
||||
const [asyncHrefCards, setAsyncHrefCards] = useState<LinkCardProps[]>();
|
||||
|
||||
const {
|
||||
services: {
|
||||
|
@ -46,6 +49,7 @@ export const ActionsPanel: FC<Props> = ({
|
|||
let unmounted = false;
|
||||
|
||||
const indexPatternId = dataView.id;
|
||||
const indexPatternTitle = dataView.title;
|
||||
const getDiscoverUrl = async (): Promise<void> => {
|
||||
const isDiscoverAvailable = capabilities.discover?.show ?? false;
|
||||
if (!isDiscoverAvailable) return;
|
||||
|
@ -68,24 +72,33 @@ export const ActionsPanel: FC<Props> = ({
|
|||
setDiscoverLink(discoverUrl);
|
||||
};
|
||||
|
||||
Promise.all(
|
||||
additionalLinks.map(async ({ canDisplay, getUrl }) => {
|
||||
if ((await canDisplay({ indexPatternId })) === false) {
|
||||
return null;
|
||||
}
|
||||
return getUrl({ globalState, indexPatternId });
|
||||
})
|
||||
).then((urls) => {
|
||||
const linksById = urls.reduce((acc, url, i) => {
|
||||
if (url !== null) {
|
||||
acc[additionalLinks[i].id] = url;
|
||||
}
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
setGeneratedLinks(linksById);
|
||||
});
|
||||
|
||||
if (Array.isArray(getAdditionalLinks) && indexPatternId !== undefined) {
|
||||
Promise.all(
|
||||
getAdditionalLinks.map(async (asyncCardGetter) => {
|
||||
const results = await asyncCardGetter({
|
||||
dataViewId: indexPatternId,
|
||||
dataViewTitle: indexPatternTitle,
|
||||
});
|
||||
if (Array.isArray(results)) {
|
||||
return await Promise.all(
|
||||
results.map(async (c) => ({
|
||||
...c,
|
||||
canDisplay: await c.canDisplay(),
|
||||
href: await c.getUrl(),
|
||||
}))
|
||||
);
|
||||
}
|
||||
})
|
||||
).then((cards) => {
|
||||
setAsyncHrefCards(
|
||||
flatten(cards)
|
||||
.filter(isDefined)
|
||||
.filter((d) => d.canDisplay === true)
|
||||
);
|
||||
});
|
||||
}
|
||||
getDiscoverUrl();
|
||||
|
||||
return () => {
|
||||
unmounted = true;
|
||||
};
|
||||
|
@ -96,8 +109,8 @@ export const ActionsPanel: FC<Props> = ({
|
|||
globalState,
|
||||
capabilities,
|
||||
discover,
|
||||
additionalLinks,
|
||||
data.query,
|
||||
getAdditionalLinks,
|
||||
]);
|
||||
|
||||
// Note we use display:none for the DataRecognizer section as it needs to be
|
||||
|
@ -105,20 +118,6 @@ export const ActionsPanel: FC<Props> = ({
|
|||
// controls whether the recognizer section is ultimately displayed.
|
||||
return (
|
||||
<div data-test-subj="dataVisualizerActionsPanel">
|
||||
{additionalLinks
|
||||
.filter(({ id }) => generatedLinks[id] !== undefined)
|
||||
.map((link) => (
|
||||
<>
|
||||
<LinkCard
|
||||
href={generatedLinks[link.id]}
|
||||
icon={link.icon}
|
||||
description={link.description}
|
||||
title={link.title}
|
||||
data-test-subj={link.dataTestSubj}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
))}
|
||||
{discoverLink && (
|
||||
<>
|
||||
<EuiTitle size="s">
|
||||
|
@ -147,8 +146,23 @@ export const ActionsPanel: FC<Props> = ({
|
|||
}
|
||||
data-test-subj="dataVisualizerViewInDiscoverCard"
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
)}
|
||||
|
||||
{Array.isArray(asyncHrefCards) &&
|
||||
asyncHrefCards.map((link) => (
|
||||
<>
|
||||
<LinkCard
|
||||
href={link.href}
|
||||
icon={link.icon}
|
||||
description={link.description}
|
||||
title={link.title}
|
||||
data-test-subj={link['data-test-subj']}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -48,7 +48,7 @@ import { DatePickerWrapper } from '../../../common/components/date_picker_wrappe
|
|||
import { HelpMenu } from '../../../common/components/help_menu';
|
||||
import { createMergedEsQuery } from '../../utils/saved_search_utils';
|
||||
import { DataVisualizerDataViewManagement } from '../data_view_management';
|
||||
import { ResultLink } from '../../../common/components/results_links';
|
||||
import { GetAdditionalLinks } from '../../../common/components/results_links';
|
||||
import { useDataVisualizerGridData } from '../../hooks/use_data_visualizer_grid_data';
|
||||
import { DataVisualizerGridInput } from '../../embeddables/grid_embeddable/grid_embeddable';
|
||||
import './_index.scss';
|
||||
|
@ -110,7 +110,7 @@ export interface IndexDataVisualizerViewProps {
|
|||
currentDataView: DataView;
|
||||
currentSavedSearch: SavedSearchSavedObject | null;
|
||||
currentSessionId?: string;
|
||||
additionalLinks?: ResultLink[];
|
||||
getAdditionalLinks?: GetAdditionalLinks;
|
||||
}
|
||||
const restorableDefaults = getDefaultDataVisualizerListState();
|
||||
|
||||
|
@ -129,7 +129,7 @@ export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVi
|
|||
dataVisualizerProps.currentSavedSearch
|
||||
);
|
||||
|
||||
const { currentDataView, additionalLinks, currentSessionId } = dataVisualizerProps;
|
||||
const { currentDataView, currentSessionId, getAdditionalLinks } = dataVisualizerProps;
|
||||
|
||||
useEffect(() => {
|
||||
if (dataVisualizerProps?.currentSavedSearch !== undefined) {
|
||||
|
@ -487,7 +487,7 @@ export const IndexDataVisualizerView: FC<IndexDataVisualizerViewProps> = (dataVi
|
|||
dataView={currentDataView}
|
||||
searchQueryLanguage={searchQueryLanguage}
|
||||
searchString={searchString}
|
||||
additionalLinks={additionalLinks ?? []}
|
||||
getAdditionalLinks={getAdditionalLinks}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -29,17 +29,16 @@ import {
|
|||
isRisonSerializationRequired,
|
||||
} from '../common/util/url_state';
|
||||
import { useDataVisualizerKibana } from '../kibana_context';
|
||||
import { ResultLink } from '../common/components/results_links';
|
||||
import { GetAdditionalLinks } from '../common/components/results_links';
|
||||
import { DATA_VISUALIZER_APP_LOCATOR, IndexDataVisualizerLocatorParams } from './locator';
|
||||
import { DATA_VISUALIZER_INDEX_VIEWER } from './constants/index_data_visualizer_viewer';
|
||||
import { INDEX_DATA_VISUALIZER_NAME } from '../common/constants';
|
||||
|
||||
export type IndexDataVisualizerSpec = typeof IndexDataVisualizer;
|
||||
|
||||
export interface DataVisualizerUrlStateContextProviderProps {
|
||||
IndexDataVisualizerComponent: FC<IndexDataVisualizerViewProps>;
|
||||
additionalLinks: ResultLink[];
|
||||
getAdditionalLinks?: GetAdditionalLinks;
|
||||
}
|
||||
export type IndexDataVisualizerSpec = typeof IndexDataVisualizer;
|
||||
|
||||
export const getLocatorParams = (params: {
|
||||
dataViewId?: string;
|
||||
|
@ -73,7 +72,7 @@ export const getLocatorParams = (params: {
|
|||
|
||||
export const DataVisualizerUrlStateContextProvider: FC<
|
||||
DataVisualizerUrlStateContextProviderProps
|
||||
> = ({ IndexDataVisualizerComponent, additionalLinks }) => {
|
||||
> = ({ IndexDataVisualizerComponent, getAdditionalLinks }) => {
|
||||
const { services } = useDataVisualizerKibana();
|
||||
const {
|
||||
data: { dataViews, search },
|
||||
|
@ -247,8 +246,8 @@ export const DataVisualizerUrlStateContextProvider: FC<
|
|||
<IndexDataVisualizerComponent
|
||||
currentDataView={currentDataView}
|
||||
currentSavedSearch={currentSavedSearch}
|
||||
additionalLinks={additionalLinks}
|
||||
currentSessionId={currentSessionId}
|
||||
getAdditionalLinks={getAdditionalLinks}
|
||||
/>
|
||||
) : (
|
||||
<div />
|
||||
|
@ -257,7 +256,9 @@ export const DataVisualizerUrlStateContextProvider: FC<
|
|||
);
|
||||
};
|
||||
|
||||
export const IndexDataVisualizer: FC<{ additionalLinks: ResultLink[] }> = ({ additionalLinks }) => {
|
||||
export const IndexDataVisualizer: FC<{
|
||||
getAdditionalLinks?: GetAdditionalLinks;
|
||||
}> = ({ getAdditionalLinks }) => {
|
||||
const coreStart = getCoreStart();
|
||||
const {
|
||||
data,
|
||||
|
@ -294,7 +295,7 @@ export const IndexDataVisualizer: FC<{ additionalLinks: ResultLink[] }> = ({ add
|
|||
<KibanaContextProvider services={{ ...services }}>
|
||||
<DataVisualizerUrlStateContextProvider
|
||||
IndexDataVisualizerComponent={IndexDataVisualizerView}
|
||||
additionalLinks={additionalLinks}
|
||||
getAdditionalLinks={getAdditionalLinks}
|
||||
/>
|
||||
</KibanaContextProvider>
|
||||
</KibanaThemeProvider>
|
||||
|
|
|
@ -18,4 +18,8 @@ export type {
|
|||
IndexDataVisualizerSpec,
|
||||
IndexDataVisualizerViewProps,
|
||||
} from './application';
|
||||
export type { ResultLink } from './application/common/components/results_links';
|
||||
export type {
|
||||
GetAdditionalLinksParams,
|
||||
ResultLink,
|
||||
GetAdditionalLinks,
|
||||
} from './application/common/components/results_links';
|
||||
|
|
|
@ -38,6 +38,7 @@ export const ML_PAGES = {
|
|||
*/
|
||||
DATA_VISUALIZER_INDEX_VIEWER: 'jobs/new_job/datavisualizer',
|
||||
ANOMALY_DETECTION_CREATE_JOB: `jobs/new_job`,
|
||||
ANOMALY_DETECTION_CREATE_JOB_RECOGNIZER: `jobs/new_job/recognize`,
|
||||
ANOMALY_DETECTION_CREATE_JOB_ADVANCED: `jobs/new_job/advanced`,
|
||||
ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE: `jobs/new_job/step/job_type`,
|
||||
ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX: `jobs/new_job/step/index_or_search`,
|
||||
|
|
|
@ -46,6 +46,7 @@ export interface MlGenericUrlPageState extends MlIndexBasedSearchState {
|
|||
export type MlGenericUrlState = MLPageState<
|
||||
| typeof ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER
|
||||
| typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB
|
||||
| typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_RECOGNIZER
|
||||
| typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_ADVANCED
|
||||
| typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE
|
||||
| typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX
|
||||
|
|
|
@ -39,7 +39,7 @@ export const LinkCard: FC<Props> = ({
|
|||
onClick,
|
||||
href,
|
||||
isDisabled,
|
||||
'data-test-subj': dateTestSubj,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}) => {
|
||||
const linkHrefAndOnClickProps = {
|
||||
...(href ? { href } : {}),
|
||||
|
@ -58,7 +58,7 @@ export const LinkCard: FC<Props> = ({
|
|||
background: 'transparent',
|
||||
outline: 'none',
|
||||
}}
|
||||
data-test-subj={dateTestSubj}
|
||||
data-test-subj={dataTestSubj}
|
||||
color="subdued"
|
||||
{...linkHrefAndOnClickProps}
|
||||
>
|
||||
|
|
|
@ -8,7 +8,11 @@
|
|||
import React, { FC, Fragment, useState, useEffect, useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { ResultLink, FileDataVisualizerSpec } from '@kbn/data-visualizer-plugin/public';
|
||||
import type {
|
||||
FileDataVisualizerSpec,
|
||||
GetAdditionalLinksParams,
|
||||
GetAdditionalLinks,
|
||||
} from '@kbn/data-visualizer-plugin/public';
|
||||
import { useTimefilter } from '../../contexts/kibana';
|
||||
import { HelpMenu } from '../../components/help_menu';
|
||||
import { useMlKibana, useMlLocator } from '../../contexts/kibana';
|
||||
|
@ -19,11 +23,6 @@ import { mlNodesAvailable, getMlNodeCount } from '../../ml_nodes_check/check_ml_
|
|||
import { checkPermission } from '../../capabilities/check_capabilities';
|
||||
import { MlPageHeader } from '../../components/page_header';
|
||||
|
||||
interface GetUrlParams {
|
||||
indexPatternId: string;
|
||||
globalState: any;
|
||||
}
|
||||
|
||||
export const FileDataVisualizerPage: FC = () => {
|
||||
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false });
|
||||
const {
|
||||
|
@ -40,60 +39,62 @@ export const FileDataVisualizerPage: FC = () => {
|
|||
|
||||
const [FileDataVisualizer, setFileDataVisualizer] = useState<FileDataVisualizerSpec | null>(null);
|
||||
|
||||
const links: ResultLink[] = useMemo(
|
||||
const getAdditionalLinks: GetAdditionalLinks = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'create_ml_job',
|
||||
title: i18n.translate('xpack.ml.fileDatavisualizer.actionsPanel.anomalyDetectionTitle', {
|
||||
defaultMessage: 'Create new ML job',
|
||||
}),
|
||||
description: '',
|
||||
icon: 'machineLearningApp',
|
||||
type: 'file',
|
||||
getUrl: async ({ indexPatternId, globalState }: GetUrlParams) => {
|
||||
return await mlLocator.getUrl({
|
||||
page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE,
|
||||
pageState: {
|
||||
index: indexPatternId,
|
||||
globalState,
|
||||
},
|
||||
});
|
||||
async ({ dataViewId, globalState }: GetAdditionalLinksParams) => [
|
||||
{
|
||||
id: 'create_ml_job',
|
||||
title: i18n.translate('xpack.ml.fileDatavisualizer.actionsPanel.anomalyDetectionTitle', {
|
||||
defaultMessage: 'Create ML job',
|
||||
}),
|
||||
description: '',
|
||||
icon: 'machineLearningApp',
|
||||
type: 'file',
|
||||
getUrl: async () => {
|
||||
return await mlLocator.getUrl({
|
||||
page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE,
|
||||
pageState: {
|
||||
index: dataViewId,
|
||||
globalState,
|
||||
},
|
||||
});
|
||||
},
|
||||
canDisplay: async () => {
|
||||
try {
|
||||
const { timeFieldName } = await getDataView(dataViewId);
|
||||
return (
|
||||
isFullLicense() &&
|
||||
timeFieldName !== undefined &&
|
||||
checkPermission('canCreateJob') &&
|
||||
mlNodesAvailable()
|
||||
);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
canDisplay: async ({ indexPatternId }) => {
|
||||
try {
|
||||
const { timeFieldName } = await getDataView(indexPatternId);
|
||||
return (
|
||||
isFullLicense() &&
|
||||
timeFieldName !== undefined &&
|
||||
checkPermission('canCreateJob') &&
|
||||
mlNodesAvailable()
|
||||
);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
{
|
||||
id: 'open_in_data_viz',
|
||||
title: i18n.translate('xpack.ml.fileDatavisualizer.actionsPanel.dataframeTitle', {
|
||||
defaultMessage: 'Open in Data Visualizer',
|
||||
}),
|
||||
description: '',
|
||||
icon: 'dataVisualizer',
|
||||
type: 'file',
|
||||
getUrl: async () => {
|
||||
return await mlLocator.getUrl({
|
||||
page: ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER,
|
||||
pageState: {
|
||||
index: dataViewId,
|
||||
globalState,
|
||||
},
|
||||
});
|
||||
},
|
||||
canDisplay: async () => dataViewId !== '',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'open_in_data_viz',
|
||||
title: i18n.translate('xpack.ml.fileDatavisualizer.actionsPanel.dataframeTitle', {
|
||||
defaultMessage: 'Open in Data Visualizer',
|
||||
}),
|
||||
description: '',
|
||||
icon: 'dataVisualizer',
|
||||
type: 'file',
|
||||
getUrl: async ({ indexPatternId, globalState }: GetUrlParams) => {
|
||||
return await mlLocator.getUrl({
|
||||
page: ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER,
|
||||
pageState: {
|
||||
index: indexPatternId,
|
||||
globalState,
|
||||
},
|
||||
});
|
||||
},
|
||||
canDisplay: async ({ indexPatternId }) => indexPatternId !== '',
|
||||
},
|
||||
],
|
||||
],
|
||||
[]
|
||||
[mlLocator]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -114,7 +115,7 @@ export const FileDataVisualizerPage: FC = () => {
|
|||
defaultMessage="Data Visualizer"
|
||||
/>
|
||||
</MlPageHeader>
|
||||
<FileDataVisualizer additionalLinks={links} />
|
||||
<FileDataVisualizer getAdditionalLinks={getAdditionalLinks} />
|
||||
</>
|
||||
) : null}
|
||||
<HelpMenu docLink={docLinks.links.ml.guide} />
|
||||
|
|
|
@ -5,28 +5,38 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC, Fragment, useEffect, useState, useMemo } from 'react';
|
||||
import React, { FC, Fragment, useEffect, useMemo, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { ResultLink, IndexDataVisualizerSpec } from '@kbn/data-visualizer-plugin/public';
|
||||
import type {
|
||||
IndexDataVisualizerSpec,
|
||||
ResultLink,
|
||||
GetAdditionalLinks,
|
||||
GetAdditionalLinksParams,
|
||||
} from '@kbn/data-visualizer-plugin/public';
|
||||
import { useMlKibana, useTimefilter, useMlLocator } from '../../contexts/kibana';
|
||||
import { HelpMenu } from '../../components/help_menu';
|
||||
import { ML_PAGES } from '../../../../common/constants/locator';
|
||||
import { isFullLicense } from '../../license';
|
||||
import { mlNodesAvailable, getMlNodeCount } from '../../ml_nodes_check/check_ml_nodes';
|
||||
import { checkPermission } from '../../capabilities/check_capabilities';
|
||||
|
||||
import { MlPageHeader } from '../../components/page_header';
|
||||
|
||||
interface GetUrlParams {
|
||||
indexPatternId: string;
|
||||
globalState: any;
|
||||
interface RecognizerModule {
|
||||
id: string;
|
||||
title: string;
|
||||
query: Record<string, object>;
|
||||
description: string;
|
||||
logo: {
|
||||
icon: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const IndexDataVisualizerPage: FC = () => {
|
||||
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false });
|
||||
const {
|
||||
services: {
|
||||
http,
|
||||
docLinks,
|
||||
dataVisualizer,
|
||||
data: {
|
||||
|
@ -48,8 +58,12 @@ export const IndexDataVisualizerPage: FC = () => {
|
|||
}
|
||||
}, []);
|
||||
|
||||
const links: ResultLink[] = useMemo(
|
||||
() => [
|
||||
const getAsyncMLCards = async ({
|
||||
dataViewId,
|
||||
dataViewTitle,
|
||||
globalState,
|
||||
}: GetAdditionalLinksParams): Promise<ResultLink[]> => {
|
||||
return [
|
||||
{
|
||||
id: 'create_ml_ad_job',
|
||||
title: i18n.translate('xpack.ml.indexDatavisualizer.actionsPanel.anomalyDetectionTitle', {
|
||||
|
@ -64,18 +78,18 @@ export const IndexDataVisualizerPage: FC = () => {
|
|||
),
|
||||
icon: 'createAdvancedJob',
|
||||
type: 'file',
|
||||
getUrl: async ({ indexPatternId, globalState }: GetUrlParams) => {
|
||||
getUrl: async () => {
|
||||
return await mlLocator.getUrl({
|
||||
page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_ADVANCED,
|
||||
pageState: {
|
||||
index: indexPatternId,
|
||||
index: dataViewId,
|
||||
globalState,
|
||||
},
|
||||
});
|
||||
},
|
||||
canDisplay: async ({ indexPatternId }) => {
|
||||
canDisplay: async () => {
|
||||
try {
|
||||
const { timeFieldName } = await getDataView(indexPatternId);
|
||||
const { timeFieldName } = await getDataView(dataViewId);
|
||||
return (
|
||||
isFullLicense() &&
|
||||
timeFieldName !== undefined &&
|
||||
|
@ -86,7 +100,7 @@ export const IndexDataVisualizerPage: FC = () => {
|
|||
return false;
|
||||
}
|
||||
},
|
||||
dataTestSubj: 'dataVisualizerCreateAdvancedJobCard',
|
||||
'data-test-subj': 'dataVisualizerCreateAdvancedJobCard',
|
||||
},
|
||||
{
|
||||
id: 'create_ml_dfa_job',
|
||||
|
@ -101,11 +115,11 @@ export const IndexDataVisualizerPage: FC = () => {
|
|||
),
|
||||
icon: 'classificationJob',
|
||||
type: 'file',
|
||||
getUrl: async ({ indexPatternId, globalState }: GetUrlParams) => {
|
||||
getUrl: async () => {
|
||||
return await mlLocator.getUrl({
|
||||
page: ML_PAGES.DATA_FRAME_ANALYTICS_CREATE_JOB,
|
||||
pageState: {
|
||||
index: indexPatternId,
|
||||
index: dataViewId,
|
||||
globalState,
|
||||
},
|
||||
});
|
||||
|
@ -115,12 +129,57 @@ export const IndexDataVisualizerPage: FC = () => {
|
|||
isFullLicense() && checkPermission('canCreateDataFrameAnalytics') && mlNodesAvailable()
|
||||
);
|
||||
},
|
||||
dataTestSubj: 'dataVisualizerCreateDataFrameAnalyticsCard',
|
||||
'data-test-subj': 'dataVisualizerCreateDataFrameAnalyticsCard',
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
];
|
||||
};
|
||||
|
||||
const getAsyncRecognizedModuleCards = async (params: GetAdditionalLinksParams) => {
|
||||
const { dataViewId, dataViewTitle } = params;
|
||||
const modules = await http.fetch<RecognizerModule[]>(
|
||||
`/api/ml/modules/recognize/${dataViewTitle}`,
|
||||
{
|
||||
method: 'GET',
|
||||
}
|
||||
);
|
||||
return modules?.map(
|
||||
(m): ResultLink => ({
|
||||
id: m.id,
|
||||
title: m.title,
|
||||
description: m.description,
|
||||
icon: m.logo.icon,
|
||||
type: 'index',
|
||||
getUrl: async () => {
|
||||
return await mlLocator.getUrl({
|
||||
page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_RECOGNIZER,
|
||||
pageState: {
|
||||
id: m.id,
|
||||
index: dataViewId,
|
||||
},
|
||||
});
|
||||
},
|
||||
canDisplay: async () => {
|
||||
try {
|
||||
const { timeFieldName } = await getDataView(dataViewId);
|
||||
return (
|
||||
isFullLicense() &&
|
||||
timeFieldName !== undefined &&
|
||||
checkPermission('canCreateJob') &&
|
||||
mlNodesAvailable()
|
||||
);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
'data-test-subj': m.id,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const getAdditionalLinks: GetAdditionalLinks = useMemo(
|
||||
() => [getAsyncRecognizedModuleCards, getAsyncMLCards],
|
||||
[mlLocator]
|
||||
);
|
||||
return IndexDataVisualizer ? (
|
||||
<Fragment>
|
||||
{IndexDataVisualizer !== null ? (
|
||||
|
@ -131,7 +190,7 @@ export const IndexDataVisualizerPage: FC = () => {
|
|||
defaultMessage="Data Visualizer"
|
||||
/>
|
||||
</MlPageHeader>
|
||||
<IndexDataVisualizer additionalLinks={links} />
|
||||
<IndexDataVisualizer getAdditionalLinks={getAdditionalLinks} />
|
||||
</>
|
||||
) : null}
|
||||
<HelpMenu docLink={docLinks.links.ml.guide} />
|
||||
|
|
|
@ -77,6 +77,7 @@ export class MlLocatorDefinition implements LocatorDefinition<MlLocatorParams> {
|
|||
path = formatTrainedModelsNodesManagementUrl('', params.pageState);
|
||||
break;
|
||||
case ML_PAGES.ANOMALY_DETECTION_CREATE_JOB:
|
||||
case ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_RECOGNIZER:
|
||||
case ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_ADVANCED:
|
||||
case ML_PAGES.DATA_VISUALIZER:
|
||||
case ML_PAGES.DATA_VISUALIZER_FILE:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue