mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ML] Create categorization job from pattern analysis (#170567)
Adds the ability to quickly create a categorisation anomaly detection job from the pattern analysis flyout. Adds a new `created_by` ID `categorization-wizard-from-pattern-analysis` which can be picked up by telemetry. Creates a new package for sharing our AIOPs ui actions IDs. I think we should move the pattern analysis ID to this package too, but that can be done in a separate PR.51349f93
-f072-4983-85f0-98741902fb5a6e618581
-8916-4e63-930f-945c96c25e6c --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
39af788067
commit
5e3b124ae0
59 changed files with 1194 additions and 145 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -536,6 +536,7 @@ x-pack/packages/ml/route_utils @elastic/ml-ui
|
|||
x-pack/packages/ml/runtime_field_utils @elastic/ml-ui
|
||||
x-pack/packages/ml/string_hash @elastic/ml-ui
|
||||
x-pack/packages/ml/trained_models_utils @elastic/ml-ui
|
||||
x-pack/packages/ml/ui_actions @elastic/ml-ui
|
||||
x-pack/packages/ml/url_state @elastic/ml-ui
|
||||
packages/kbn-monaco @elastic/appex-sharedux
|
||||
x-pack/plugins/monitoring_collection @elastic/obs-ux-infra_services-team
|
||||
|
|
|
@ -555,6 +555,7 @@
|
|||
"@kbn/ml-runtime-field-utils": "link:x-pack/packages/ml/runtime_field_utils",
|
||||
"@kbn/ml-string-hash": "link:x-pack/packages/ml/string_hash",
|
||||
"@kbn/ml-trained-models-utils": "link:x-pack/packages/ml/trained_models_utils",
|
||||
"@kbn/ml-ui-actions": "link:x-pack/packages/ml/ui_actions",
|
||||
"@kbn/ml-url-state": "link:x-pack/packages/ml/url_state",
|
||||
"@kbn/monaco": "link:packages/kbn-monaco",
|
||||
"@kbn/monitoring-collection-plugin": "link:x-pack/plugins/monitoring_collection",
|
||||
|
|
|
@ -1066,6 +1066,8 @@
|
|||
"@kbn/ml-string-hash/*": ["x-pack/packages/ml/string_hash/*"],
|
||||
"@kbn/ml-trained-models-utils": ["x-pack/packages/ml/trained_models_utils"],
|
||||
"@kbn/ml-trained-models-utils/*": ["x-pack/packages/ml/trained_models_utils/*"],
|
||||
"@kbn/ml-ui-actions": ["x-pack/packages/ml/ui_actions"],
|
||||
"@kbn/ml-ui-actions/*": ["x-pack/packages/ml/ui_actions/*"],
|
||||
"@kbn/ml-url-state": ["x-pack/packages/ml/url_state"],
|
||||
"@kbn/ml-url-state/*": ["x-pack/packages/ml/url_state/*"],
|
||||
"@kbn/monaco": ["packages/kbn-monaco"],
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React, { FC, useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import moment from 'moment';
|
||||
import type { TimefilterContract } from '@kbn/data-plugin/public';
|
||||
import dateMath from '@kbn/datemath';
|
||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { ToastsStart, HttpStart } from '@kbn/core/public';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
import type { HttpStart } from '@kbn/core/public';
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
||||
|
||||
|
|
3
x-pack/packages/ml/ui_actions/README.md
Normal file
3
x-pack/packages/ml/ui_actions/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# @kbn/ml-ui-actions
|
||||
|
||||
Empty package generated by @kbn/generate
|
12
x-pack/packages/ml/ui_actions/index.ts
Normal file
12
x-pack/packages/ml/ui_actions/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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 {
|
||||
CREATE_PATTERN_ANALYSIS_TO_ML_AD_JOB_ACTION,
|
||||
CREATE_PATTERN_ANALYSIS_TO_ML_AD_JOB_TRIGGER,
|
||||
type CreateCategorizationADJobContext,
|
||||
} from './src/ui_actions';
|
12
x-pack/packages/ml/ui_actions/jest.config.js
Normal file
12
x-pack/packages/ml/ui_actions/jest.config.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test/jest_node',
|
||||
rootDir: '../../../..',
|
||||
roots: ['<rootDir>/x-pack/packages/ml/ui_actions'],
|
||||
};
|
5
x-pack/packages/ml/ui_actions/kibana.jsonc
Normal file
5
x-pack/packages/ml/ui_actions/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/ml-ui-actions",
|
||||
"owner": "@elastic/ml-ui"
|
||||
}
|
6
x-pack/packages/ml/ui_actions/package.json
Normal file
6
x-pack/packages/ml/ui_actions/package.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/ml-ui-actions",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "Elastic License 2.0"
|
||||
}
|
22
x-pack/packages/ml/ui_actions/src/ui_actions.ts
Normal file
22
x-pack/packages/ml/ui_actions/src/ui_actions.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import type { DataView, DataViewField } from '@kbn/data-views-plugin/common';
|
||||
import type { TimeRange } from '@kbn/es-query';
|
||||
|
||||
export interface CreateCategorizationADJobContext {
|
||||
field: DataViewField;
|
||||
dataView: DataView;
|
||||
query: QueryDslQueryContainer;
|
||||
timeRange: TimeRange;
|
||||
}
|
||||
|
||||
export const CREATE_PATTERN_ANALYSIS_TO_ML_AD_JOB_ACTION = 'createMLADCategorizationJobAction';
|
||||
|
||||
export const CREATE_PATTERN_ANALYSIS_TO_ML_AD_JOB_TRIGGER =
|
||||
'CREATE_PATTERN_ANALYSIS_TO_ML_AD_JOB_TRIGGER';
|
20
x-pack/packages/ml/ui_actions/tsconfig.json
Normal file
20
x-pack/packages/ml/ui_actions/tsconfig.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/data-views-plugin",
|
||||
"@kbn/es-query",
|
||||
]
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import moment from 'moment';
|
||||
import { EuiButtonEmpty } from '@elastic/eui';
|
||||
import type { DataViewField, DataView } from '@kbn/data-views-plugin/common';
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import {
|
||||
CREATE_PATTERN_ANALYSIS_TO_ML_AD_JOB_TRIGGER,
|
||||
type CreateCategorizationADJobContext,
|
||||
} from '@kbn/ml-ui-actions';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
|
||||
|
||||
interface Props {
|
||||
dataView: DataView;
|
||||
field: DataViewField;
|
||||
query: QueryDslQueryContainer;
|
||||
earliest: number | undefined;
|
||||
latest: number | undefined;
|
||||
}
|
||||
|
||||
export const CreateCategorizationJobButton: FC<Props> = ({
|
||||
dataView,
|
||||
field,
|
||||
query,
|
||||
earliest,
|
||||
latest,
|
||||
}) => {
|
||||
const {
|
||||
uiActions,
|
||||
application: { capabilities },
|
||||
} = useAiopsAppContext();
|
||||
|
||||
const createADJob = () => {
|
||||
if (uiActions === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const triggerOptions: CreateCategorizationADJobContext = {
|
||||
dataView,
|
||||
field,
|
||||
query,
|
||||
timeRange: { from: moment(earliest).toISOString(), to: moment(latest).toISOString() },
|
||||
};
|
||||
uiActions.getTrigger(CREATE_PATTERN_ANALYSIS_TO_ML_AD_JOB_TRIGGER).exec(triggerOptions);
|
||||
};
|
||||
|
||||
if (uiActions === undefined || capabilities.ml.canCreateJob === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="aiopsLogCategorizationFlyoutAdJobButton"
|
||||
onClick={createADJob}
|
||||
flush="left"
|
||||
iconSide="left"
|
||||
iconType={'machineLearningApp'}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.aiops.categorizeFlyout.findAnomalies"
|
||||
defaultMessage="Find anomalies in patterns"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -44,6 +44,7 @@ import { TechnicalPreviewBadge } from './technical_preview_badge';
|
|||
import { LoadingCategorization } from './loading_categorization';
|
||||
import { useValidateFieldRequest } from './use_validate_category_field';
|
||||
import { FieldValidationCallout } from './category_validation_callout';
|
||||
import { CreateCategorizationJobButton } from './create_categorization_job';
|
||||
|
||||
export interface LogCategorizationPageProps {
|
||||
dataView: DataView;
|
||||
|
@ -261,17 +262,21 @@ export const LogCategorizationFlyout: FC<LogCategorizationPageProps> = ({
|
|||
</EuiFlexGroup>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody data-test-subj="mlJobSelectorFlyoutBody">
|
||||
<CreateCategorizationJobButton
|
||||
dataView={dataView}
|
||||
field={selectedField}
|
||||
query={searchQuery}
|
||||
earliest={earliest}
|
||||
latest={latest}
|
||||
/>
|
||||
<FieldValidationCallout validationResults={fieldValidationResult} />
|
||||
|
||||
{loading === true ? <LoadingCategorization onClose={onClose} /> : null}
|
||||
|
||||
<InformationText
|
||||
loading={loading}
|
||||
categoriesLength={data?.categories?.length ?? null}
|
||||
eventRateLength={eventRate.length}
|
||||
fieldSelected={selectedField !== null}
|
||||
/>
|
||||
|
||||
{loading === false && data !== null && data.categories.length > 0 ? (
|
||||
<CategoryTable
|
||||
categories={data.categories}
|
||||
|
|
|
@ -19,7 +19,7 @@ import { UI_SETTINGS } from '@kbn/data-plugin/public';
|
|||
import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker';
|
||||
import { StorageContextProvider } from '@kbn/ml-local-storage';
|
||||
import type { AiopsPluginStartDeps } from '../../types';
|
||||
import { AiopsAppContext } from '../../hooks/use_aiops_app_context';
|
||||
import { AiopsAppContext, type AiopsAppDependencies } from '../../hooks/use_aiops_app_context';
|
||||
import { LogCategorizationFlyout } from './log_categorization_for_flyout';
|
||||
import { AIOPS_STORAGE_KEYS } from '../../types/storage';
|
||||
|
||||
|
@ -41,7 +41,7 @@ export async function showCategorizeFlyout(
|
|||
resolve();
|
||||
};
|
||||
|
||||
const appDependencies = {
|
||||
const appDependencies: AiopsAppDependencies = {
|
||||
notifications,
|
||||
uiSettings,
|
||||
http,
|
||||
|
|
|
@ -33,6 +33,7 @@ import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/
|
|||
import type { EmbeddableStart } from '@kbn/embeddable-plugin/public';
|
||||
import type { CasesUiStart } from '@kbn/cases-plugin/public';
|
||||
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
|
||||
import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
|
||||
/**
|
||||
* AIOps App Dependencies to be provided via React context.
|
||||
|
@ -97,6 +98,10 @@ export interface AiopsAppDependencies {
|
|||
* Used to create lens embeddables.
|
||||
*/
|
||||
lens: LensPublicStart;
|
||||
/**
|
||||
* UI actions.
|
||||
*/
|
||||
uiActions?: UiActionsStart;
|
||||
/**
|
||||
* Internationalisation service
|
||||
*/
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
"@kbn/ml-chi2test",
|
||||
"@kbn/usage-collection-plugin",
|
||||
"@kbn/analytics",
|
||||
"@kbn/ml-ui-actions",
|
||||
"@kbn/core-http-server",
|
||||
],
|
||||
"exclude": [
|
||||
|
|
|
@ -55,6 +55,7 @@ export const ML_PAGES = {
|
|||
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',
|
||||
ANOMALY_DETECTION_CREATE_JOB_FROM_LENS: 'jobs/new_job/from_lens',
|
||||
ANOMALY_DETECTION_CREATE_JOB_FROM_PATTERN_ANALYSIS: 'jobs/new_job/from_pattern_analysis',
|
||||
ANOMALY_DETECTION_CREATE_JOB_FROM_MAP: 'jobs/new_job/from_map',
|
||||
ANOMALY_DETECTION_MODULES_VIEW_OR_CREATE: 'modules/check_view_or_create',
|
||||
SETTINGS: 'settings',
|
||||
|
|
|
@ -26,6 +26,7 @@ export enum CREATED_BY_LABEL {
|
|||
APM_TRANSACTION = 'ml-module-apm-transaction',
|
||||
SINGLE_METRIC_FROM_LENS = 'single-metric-wizard-from-lens',
|
||||
MULTI_METRIC_FROM_LENS = 'multi-metric-wizard-from-lens',
|
||||
CATEGORIZATION_FROM_PATTERN_ANALYSIS = 'categorization-wizard-from-pattern-analysis',
|
||||
}
|
||||
|
||||
export const DEFAULT_MODEL_MEMORY_LIMIT = '10MB';
|
||||
|
|
|
@ -49,6 +49,7 @@ export type MlGenericUrlState = MLPageState<
|
|||
| typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_ADVANCED
|
||||
| typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_FROM_LENS
|
||||
| typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_FROM_MAP
|
||||
| typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_FROM_PATTERN_ANALYSIS
|
||||
| typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE
|
||||
| typeof ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX
|
||||
| typeof ML_PAGES.DATA_FRAME_ANALYTICS_CREATE_JOB
|
||||
|
|
|
@ -65,6 +65,7 @@ export const ChangePointDetectionPage: FC = () => {
|
|||
'share',
|
||||
'storage',
|
||||
'theme',
|
||||
'uiActions',
|
||||
'uiSettings',
|
||||
'unifiedSearch',
|
||||
'usageCollection',
|
||||
|
|
|
@ -56,6 +56,7 @@ export const LogCategorizationPage: FC = () => {
|
|||
'share',
|
||||
'storage',
|
||||
'theme',
|
||||
'uiActions',
|
||||
'uiSettings',
|
||||
'unifiedSearch',
|
||||
])}
|
||||
|
|
|
@ -59,6 +59,7 @@ export const LogRateAnalysisPage: FC = () => {
|
|||
'share',
|
||||
'storage',
|
||||
'theme',
|
||||
'uiActions',
|
||||
'uiSettings',
|
||||
'unifiedSearch',
|
||||
])}
|
||||
|
|
|
@ -361,7 +361,7 @@ const CategoryExamples: FC<{ definition: CategoryDefinition; examples: string[]
|
|||
<EuiFlexGroup
|
||||
direction="column"
|
||||
justifyContent="center"
|
||||
gutterSize="m"
|
||||
gutterSize="xs"
|
||||
className="mlAnomalyCategoryExamples"
|
||||
>
|
||||
{definition !== undefined && definition.terms && (
|
||||
|
|
|
@ -69,21 +69,21 @@ export class QuickJobCreatorBase {
|
|||
datafeedConfig,
|
||||
jobConfig,
|
||||
createdByLabel,
|
||||
dashboard,
|
||||
start,
|
||||
end,
|
||||
startJob,
|
||||
runInRealTime,
|
||||
dashboard,
|
||||
}: {
|
||||
jobId: string;
|
||||
datafeedConfig: Datafeed;
|
||||
jobConfig: Job;
|
||||
createdByLabel: CREATED_BY_LABEL;
|
||||
dashboard: Dashboard;
|
||||
start: number | undefined;
|
||||
end: number | undefined;
|
||||
startJob: boolean;
|
||||
runInRealTime: boolean;
|
||||
dashboard?: Dashboard;
|
||||
}) {
|
||||
const datafeedId = createDatafeedId(jobId);
|
||||
const datafeed = { ...datafeedConfig, job_id: jobId, datafeed_id: datafeedId };
|
||||
|
@ -93,7 +93,7 @@ export class QuickJobCreatorBase {
|
|||
job_id: jobId,
|
||||
custom_settings: {
|
||||
created_by: createdByLabel,
|
||||
...(await this.getCustomUrls(dashboard, datafeed)),
|
||||
...(dashboard ? await this.getCustomUrls(dashboard, datafeed) : {}),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -230,7 +230,7 @@ export class QuickJobCreatorBase {
|
|||
return mergedQueries;
|
||||
}
|
||||
|
||||
protected async createDashboardLink(dashboard: Dashboard, datafeedConfig: estypes.MlDatafeed) {
|
||||
private async createDashboardLink(dashboard: Dashboard, datafeedConfig: estypes.MlDatafeed) {
|
||||
const dashboardTitle = dashboard?.getTitle();
|
||||
if (dashboardTitle === undefined || dashboardTitle === '') {
|
||||
// embeddable may have not been in a dashboard
|
||||
|
@ -274,7 +274,7 @@ export class QuickJobCreatorBase {
|
|||
return { url_name: urlName, url_value: url, time_range: 'auto' };
|
||||
}
|
||||
|
||||
protected async getCustomUrls(dashboard: Dashboard, datafeedConfig: estypes.MlDatafeed) {
|
||||
private async getCustomUrls(dashboard: Dashboard, datafeedConfig: estypes.MlDatafeed) {
|
||||
const customUrls = await this.createDashboardLink(dashboard, datafeedConfig);
|
||||
return dashboard !== undefined && customUrls !== null ? { custom_urls: [customUrls] } : {};
|
||||
}
|
||||
|
|
|
@ -135,7 +135,7 @@ export class QuickLensJobCreator extends QuickJobCreatorBase {
|
|||
}
|
||||
}
|
||||
|
||||
async createJob(
|
||||
private async createJob(
|
||||
chartInfo: ChartInfo,
|
||||
startString: string,
|
||||
endString: string,
|
||||
|
|
|
@ -15,7 +15,7 @@ import type { DashboardStart } from '@kbn/dashboard-plugin/public';
|
|||
import { QuickLensJobCreator } from './quick_create_job';
|
||||
import type { MlApiServices } from '../../../services/ml_api_service';
|
||||
|
||||
import { getDefaultQuery } from '../utils/new_job_utils';
|
||||
import { getDefaultQuery, getRisonValue } from '../utils/new_job_utils';
|
||||
|
||||
interface Dependencies {
|
||||
lens: LensPublicStart;
|
||||
|
@ -27,8 +27,8 @@ interface Dependencies {
|
|||
export async function resolver(
|
||||
deps: Dependencies,
|
||||
lensSavedObjectRisonString: string | undefined,
|
||||
fromRisonStrong: string,
|
||||
toRisonStrong: string,
|
||||
fromRisonString: string,
|
||||
toRisonString: string,
|
||||
queryRisonString: string,
|
||||
filtersRisonString: string,
|
||||
layerIndexRisonString: string
|
||||
|
@ -43,37 +43,11 @@ export async function resolver(
|
|||
throw new Error('Cannot create visualization');
|
||||
}
|
||||
|
||||
let query: Query;
|
||||
let filters: Filter[];
|
||||
try {
|
||||
query = rison.decode(queryRisonString) as Query;
|
||||
} catch (error) {
|
||||
query = getDefaultQuery();
|
||||
}
|
||||
try {
|
||||
filters = rison.decode(filtersRisonString) as Filter[];
|
||||
} catch (error) {
|
||||
filters = [];
|
||||
}
|
||||
|
||||
let from: string;
|
||||
let to: string;
|
||||
try {
|
||||
from = rison.decode(fromRisonStrong) as string;
|
||||
} catch (error) {
|
||||
from = '';
|
||||
}
|
||||
try {
|
||||
to = rison.decode(toRisonStrong) as string;
|
||||
} catch (error) {
|
||||
to = '';
|
||||
}
|
||||
let layerIndex: number | undefined;
|
||||
try {
|
||||
layerIndex = rison.decode(layerIndexRisonString) as number;
|
||||
} catch (error) {
|
||||
layerIndex = undefined;
|
||||
}
|
||||
const query = getRisonValue<Query>(queryRisonString, getDefaultQuery()) as Query;
|
||||
const filters = getRisonValue<Filter[]>(filtersRisonString, []);
|
||||
const from = getRisonValue<string>(fromRisonString, '');
|
||||
const to = getRisonValue<string>(toRisonString, '');
|
||||
const layerIndex = getRisonValue<number | undefined>(layerIndexRisonString, undefined);
|
||||
|
||||
const jobCreator = new QuickLensJobCreator(
|
||||
lens,
|
||||
|
|
|
@ -162,7 +162,7 @@ export class QuickGeoJobCreator extends QuickJobCreatorBase {
|
|||
}
|
||||
}
|
||||
|
||||
async createGeoJob({
|
||||
private async createGeoJob({
|
||||
dataViewId,
|
||||
sourceDataView,
|
||||
from,
|
||||
|
|
|
@ -5,16 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import rison from '@kbn/rison';
|
||||
import type { Query } from '@kbn/es-query';
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
|
||||
import type { TimefilterContract } from '@kbn/data-plugin/public';
|
||||
import type { DashboardStart } from '@kbn/dashboard-plugin/public';
|
||||
import type { MlApiServices } from '../../../services/ml_api_service';
|
||||
import { QuickGeoJobCreator } from './quick_create_job';
|
||||
|
||||
import { getDefaultQuery } from '../utils/new_job_utils';
|
||||
import { getDefaultQuery, getRisonValue } from '../utils/new_job_utils';
|
||||
|
||||
interface Dependencies {
|
||||
kibanaConfig: IUiSettingsClient;
|
||||
|
@ -24,66 +21,32 @@ interface Dependencies {
|
|||
}
|
||||
export async function resolver(
|
||||
deps: Dependencies,
|
||||
dashboard: string,
|
||||
dataViewId: string,
|
||||
embeddable: string,
|
||||
geoField: string,
|
||||
splitField: string,
|
||||
dashboardRisonString: string,
|
||||
dataViewIdRisonString: string,
|
||||
embeddableRisonString: string,
|
||||
geoFieldRisonString: string,
|
||||
splitFieldRisonString: string,
|
||||
fromRisonString: string,
|
||||
toRisonString: string,
|
||||
layer?: string
|
||||
layerRisonString?: string
|
||||
) {
|
||||
const { kibanaConfig, timeFilter, dashboardService, mlApiServices } = deps;
|
||||
let decodedDashboard;
|
||||
let decodedEmbeddable;
|
||||
let decodedLayer;
|
||||
let splitFieldDecoded;
|
||||
let dvId;
|
||||
const defaultLayer = { query: getDefaultQuery(), filters: [] };
|
||||
|
||||
try {
|
||||
dvId = rison.decode(dataViewId) as string;
|
||||
} catch (error) {
|
||||
dvId = '';
|
||||
}
|
||||
const dashboard = getRisonValue<typeof defaultLayer>(dashboardRisonString, defaultLayer);
|
||||
const embeddable = getRisonValue<typeof defaultLayer>(embeddableRisonString, defaultLayer);
|
||||
|
||||
try {
|
||||
decodedDashboard = rison.decode(dashboard) as { query: Query; filters: Filter[] };
|
||||
} catch (error) {
|
||||
decodedDashboard = { query: getDefaultQuery(), filters: [] };
|
||||
}
|
||||
const layer =
|
||||
layerRisonString !== undefined
|
||||
? getRisonValue<typeof defaultLayer>(layerRisonString, defaultLayer)
|
||||
: defaultLayer;
|
||||
|
||||
try {
|
||||
decodedEmbeddable = rison.decode(embeddable) as { query: Query; filters: Filter[] };
|
||||
} catch (error) {
|
||||
decodedEmbeddable = { query: getDefaultQuery(), filters: [] };
|
||||
}
|
||||
const geoField = getRisonValue<string>(geoFieldRisonString, '');
|
||||
const splitField = getRisonValue<string | null>(splitFieldRisonString, null);
|
||||
const dataViewId = getRisonValue<string>(dataViewIdRisonString, '');
|
||||
|
||||
if (layer) {
|
||||
try {
|
||||
decodedLayer = rison.decode(layer) as { query: Query };
|
||||
} catch (error) {
|
||||
decodedLayer = { query: getDefaultQuery(), filters: [] };
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
splitFieldDecoded = rison.decode(splitField) as string;
|
||||
} catch (error) {
|
||||
splitFieldDecoded = null;
|
||||
}
|
||||
|
||||
let from: string;
|
||||
let to: string;
|
||||
try {
|
||||
from = rison.decode(fromRisonString) as string;
|
||||
} catch (error) {
|
||||
from = '';
|
||||
}
|
||||
try {
|
||||
to = rison.decode(toRisonString) as string;
|
||||
} catch (error) {
|
||||
to = '';
|
||||
}
|
||||
const from = getRisonValue<string>(fromRisonString, '');
|
||||
const to = getRisonValue<string>(toRisonString, '');
|
||||
|
||||
const jobCreator = new QuickGeoJobCreator(
|
||||
kibanaConfig,
|
||||
|
@ -93,15 +56,15 @@ export async function resolver(
|
|||
);
|
||||
|
||||
await jobCreator.createAndStashGeoJob(
|
||||
dvId,
|
||||
dataViewId,
|
||||
from,
|
||||
to,
|
||||
decodedDashboard.query,
|
||||
decodedDashboard.filters,
|
||||
decodedEmbeddable.query,
|
||||
decodedEmbeddable.filters,
|
||||
dashboard.query,
|
||||
dashboard.filters,
|
||||
embeddable.query,
|
||||
embeddable.filters,
|
||||
geoField,
|
||||
splitFieldDecoded,
|
||||
decodedLayer?.query
|
||||
splitField,
|
||||
layer?.query
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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 {
|
||||
QuickCategorizationJobCreator,
|
||||
CATEGORIZATION_TYPE,
|
||||
type CategorizationType,
|
||||
} from './quick_create_job';
|
||||
|
||||
export { resolver } from './route_resolver';
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import type { IUiSettingsClient } from '@kbn/core/public';
|
||||
import type { DataPublicPluginStart, TimefilterContract } from '@kbn/data-plugin/public';
|
||||
import type { DashboardStart } from '@kbn/dashboard-plugin/public';
|
||||
import { DataViewField, DataView } from '@kbn/data-views-plugin/common';
|
||||
import type { TimeRange } from '@kbn/es-query';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { MLCATEGORY, ML_JOB_AGGREGATION } from '@kbn/ml-anomaly-utils';
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { CREATED_BY_LABEL, DEFAULT_BUCKET_SPAN } from '../../../../../common/constants/new_job';
|
||||
import { type CreateState, QuickJobCreatorBase } from '../job_from_dashboard/quick_create_job_base';
|
||||
import type { MlApiServices } from '../../../services/ml_api_service';
|
||||
import { createEmptyDatafeed, createEmptyJob } from '../common/job_creator/util/default_configs';
|
||||
import { stashJobForCloning } from '../common/job_creator/util/general';
|
||||
import type { JobCreatorType } from '../common/job_creator';
|
||||
|
||||
export const CATEGORIZATION_TYPE = {
|
||||
COUNT: ML_JOB_AGGREGATION.COUNT,
|
||||
RARE: ML_JOB_AGGREGATION.RARE,
|
||||
} as const;
|
||||
|
||||
export type CategorizationType = typeof CATEGORIZATION_TYPE[keyof typeof CATEGORIZATION_TYPE];
|
||||
|
||||
export class QuickCategorizationJobCreator extends QuickJobCreatorBase {
|
||||
constructor(
|
||||
kibanaConfig: IUiSettingsClient,
|
||||
timeFilter: TimefilterContract,
|
||||
dashboardService: DashboardStart,
|
||||
private data: DataPublicPluginStart,
|
||||
mlApiServices: MlApiServices
|
||||
) {
|
||||
super(kibanaConfig, timeFilter, dashboardService, mlApiServices);
|
||||
}
|
||||
|
||||
public async createAndSaveJob(
|
||||
categorizationType: CategorizationType,
|
||||
jobId: string,
|
||||
bucketSpan: string,
|
||||
dataView: DataView,
|
||||
field: DataViewField,
|
||||
partitionField: DataViewField | null,
|
||||
stopOnWarn: boolean,
|
||||
query: QueryDslQueryContainer,
|
||||
timeRange: TimeRange,
|
||||
startJob: boolean,
|
||||
runInRealTime: boolean
|
||||
): Promise<CreateState> {
|
||||
if (query === undefined) {
|
||||
throw new Error('Cannot create job, query and filters are undefined');
|
||||
}
|
||||
|
||||
const { jobConfig, datafeedConfig, start, end } = await this.createJob(
|
||||
categorizationType,
|
||||
dataView,
|
||||
field,
|
||||
partitionField,
|
||||
stopOnWarn,
|
||||
timeRange,
|
||||
query,
|
||||
bucketSpan
|
||||
);
|
||||
const createdByLabel = CREATED_BY_LABEL.CATEGORIZATION_FROM_PATTERN_ANALYSIS;
|
||||
|
||||
const result = await this.putJobAndDataFeed({
|
||||
jobId,
|
||||
datafeedConfig,
|
||||
jobConfig,
|
||||
createdByLabel,
|
||||
start,
|
||||
end,
|
||||
startJob,
|
||||
runInRealTime,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public async createAndStashADJob(
|
||||
categorizationType: CategorizationType,
|
||||
dataViewId: string,
|
||||
fieldName: string,
|
||||
partitionFieldName: string | null,
|
||||
stopOnWarn: boolean,
|
||||
startString: string,
|
||||
endString: string,
|
||||
query: QueryDslQueryContainer
|
||||
) {
|
||||
try {
|
||||
const dataView = await this.data.dataViews.get(dataViewId);
|
||||
const field = dataView.getFieldByName(fieldName);
|
||||
const partitionField = partitionFieldName
|
||||
? dataView.getFieldByName(partitionFieldName) ?? null
|
||||
: null;
|
||||
|
||||
if (field === undefined) {
|
||||
throw new Error('Cannot create job, field is undefined');
|
||||
}
|
||||
|
||||
const { jobConfig, datafeedConfig, start, end, includeTimeRange } = await this.createJob(
|
||||
categorizationType,
|
||||
dataView,
|
||||
field,
|
||||
partitionField,
|
||||
stopOnWarn,
|
||||
{ from: startString, to: endString },
|
||||
query,
|
||||
DEFAULT_BUCKET_SPAN
|
||||
);
|
||||
|
||||
// add job config and start and end dates to the
|
||||
// job cloning stash, so they can be used
|
||||
// by the new job wizards
|
||||
stashJobForCloning(
|
||||
{
|
||||
jobConfig,
|
||||
datafeedConfig,
|
||||
createdBy: CREATED_BY_LABEL.CATEGORIZATION_FROM_PATTERN_ANALYSIS,
|
||||
start,
|
||||
end,
|
||||
} as JobCreatorType,
|
||||
true,
|
||||
includeTimeRange,
|
||||
!includeTimeRange
|
||||
);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private async createJob(
|
||||
categorizationType: CategorizationType,
|
||||
dataView: DataView,
|
||||
field: DataViewField,
|
||||
partitionField: DataViewField | null,
|
||||
stopOnWarn: boolean,
|
||||
timeRange: TimeRange,
|
||||
query: QueryDslQueryContainer,
|
||||
bucketSpan: string
|
||||
) {
|
||||
const jobConfig = createEmptyJob();
|
||||
const datafeedConfig = createEmptyDatafeed(dataView.getIndexPattern());
|
||||
|
||||
datafeedConfig.query = query;
|
||||
jobConfig.analysis_config = {
|
||||
categorization_field_name: field.name,
|
||||
per_partition_categorization: {
|
||||
enabled: partitionField !== null,
|
||||
stop_on_warn: stopOnWarn,
|
||||
},
|
||||
influencers: [MLCATEGORY],
|
||||
detectors: [
|
||||
{
|
||||
function: categorizationType,
|
||||
by_field_name: MLCATEGORY,
|
||||
},
|
||||
],
|
||||
bucket_span: bucketSpan,
|
||||
};
|
||||
|
||||
if (partitionField !== null) {
|
||||
jobConfig.analysis_config.detectors[0].partition_field_name = partitionField.name;
|
||||
jobConfig.analysis_config.influencers!.push(partitionField.name);
|
||||
}
|
||||
|
||||
jobConfig.data_description.time_field = dataView.timeFieldName;
|
||||
|
||||
let start: number | undefined;
|
||||
let end: number | undefined;
|
||||
let includeTimeRange = true;
|
||||
|
||||
try {
|
||||
// attempt to parse the start and end dates.
|
||||
// if start and end values cannot be determined
|
||||
// instruct the job cloning code to auto-select the
|
||||
// full time range for the index.
|
||||
const { min, max } = this.timeFilter.calculateBounds(timeRange);
|
||||
start = min?.valueOf();
|
||||
end = max?.valueOf();
|
||||
|
||||
if (start === undefined || end === undefined || isNaN(start) || isNaN(end)) {
|
||||
throw Error(
|
||||
i18n.translate('xpack.ml.newJob.fromLens.createJob.error.timeRange', {
|
||||
defaultMessage: 'Incompatible time range',
|
||||
})
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
includeTimeRange = false;
|
||||
start = undefined;
|
||||
end = undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
jobConfig,
|
||||
datafeedConfig,
|
||||
start,
|
||||
end,
|
||||
includeTimeRange,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
|
||||
import type { DataPublicPluginStart, TimefilterContract } from '@kbn/data-plugin/public';
|
||||
import type { DashboardStart } from '@kbn/dashboard-plugin/public';
|
||||
import type { QueryDslQueryContainer } from '@kbn/data-views-plugin/common/types';
|
||||
import {
|
||||
type CategorizationType,
|
||||
QuickCategorizationJobCreator,
|
||||
CATEGORIZATION_TYPE,
|
||||
} from './quick_create_job';
|
||||
import type { MlApiServices } from '../../../services/ml_api_service';
|
||||
|
||||
import { getDefaultDatafeedQuery, getRisonValue } from '../utils/new_job_utils';
|
||||
|
||||
interface Dependencies {
|
||||
kibanaConfig: IUiSettingsClient;
|
||||
timeFilter: TimefilterContract;
|
||||
dashboardService: DashboardStart;
|
||||
data: DataPublicPluginStart;
|
||||
mlApiServices: MlApiServices;
|
||||
}
|
||||
export async function resolver(
|
||||
deps: Dependencies,
|
||||
categorizationTypeRisonString: string,
|
||||
dataViewIdRisonString: string,
|
||||
fieldRisonString: string,
|
||||
partitionFieldRisonString: string | null,
|
||||
stopOnWarnRisonString: string,
|
||||
fromRisonString: string,
|
||||
toRisonString: string,
|
||||
queryRisonString: string
|
||||
) {
|
||||
const { mlApiServices, timeFilter, kibanaConfig, dashboardService, data } = deps;
|
||||
|
||||
const query = getRisonValue<QueryDslQueryContainer>(queryRisonString, getDefaultDatafeedQuery());
|
||||
const from = getRisonValue<string>(fromRisonString, '');
|
||||
const to = getRisonValue<string>(toRisonString, '');
|
||||
const categorizationType = getRisonValue<CategorizationType>(
|
||||
categorizationTypeRisonString,
|
||||
CATEGORIZATION_TYPE.COUNT
|
||||
);
|
||||
const dataViewId = getRisonValue<string>(dataViewIdRisonString, '');
|
||||
const field = getRisonValue<string>(fieldRisonString, '');
|
||||
const partitionField =
|
||||
partitionFieldRisonString === null ? '' : getRisonValue<string>(partitionFieldRisonString, '');
|
||||
const stopOnWarn = getRisonValue<boolean>(stopOnWarnRisonString, false);
|
||||
|
||||
const jobCreator = new QuickCategorizationJobCreator(
|
||||
kibanaConfig,
|
||||
timeFilter,
|
||||
dashboardService,
|
||||
data,
|
||||
mlApiServices
|
||||
);
|
||||
await jobCreator.createAndStashADJob(
|
||||
categorizationType,
|
||||
dataViewId,
|
||||
field,
|
||||
partitionField,
|
||||
stopOnWarn,
|
||||
from,
|
||||
to,
|
||||
query
|
||||
);
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import type { DataViewField, DataView } from '@kbn/data-views-plugin/common';
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import type { SharePluginStart } from '@kbn/share-plugin/public';
|
||||
import type { TimeRange } from '@kbn/es-query';
|
||||
import { ML_APP_LOCATOR } from '../../../../../common/constants/locator';
|
||||
import { ML_PAGES } from '../../../../locator';
|
||||
import type { CategorizationType } from './quick_create_job';
|
||||
|
||||
export async function redirectToADJobWizards(
|
||||
categorizationType: CategorizationType,
|
||||
dataView: DataView,
|
||||
field: DataViewField,
|
||||
partitionField: DataViewField | null,
|
||||
stopOnWarn: boolean,
|
||||
query: QueryDslQueryContainer,
|
||||
timeRange: TimeRange,
|
||||
share: SharePluginStart
|
||||
) {
|
||||
const locator = share.url.locators.get(ML_APP_LOCATOR)!;
|
||||
|
||||
const url = await locator.getUrl({
|
||||
page: ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_FROM_PATTERN_ANALYSIS,
|
||||
pageState: {
|
||||
categorizationType,
|
||||
dataViewId: dataView.id,
|
||||
field: field.name,
|
||||
partitionField: partitionField?.name || null,
|
||||
stopOnWarn,
|
||||
from: timeRange.from,
|
||||
to: timeRange.to,
|
||||
query: JSON.stringify(query),
|
||||
},
|
||||
});
|
||||
|
||||
window.open(url, '_blank');
|
||||
}
|
|
@ -55,6 +55,7 @@ async function getWizardUrlFromCloningJob(createdBy: string | undefined, dataVie
|
|||
page = JOB_TYPE.POPULATION;
|
||||
break;
|
||||
case CREATED_BY_LABEL.CATEGORIZATION:
|
||||
case CREATED_BY_LABEL.CATEGORIZATION_FROM_PATTERN_ANALYSIS:
|
||||
page = JOB_TYPE.CATEGORIZATION;
|
||||
break;
|
||||
case CREATED_BY_LABEL.RARE:
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import rison from '@kbn/rison';
|
||||
import {
|
||||
Query,
|
||||
fromKueryExpression,
|
||||
|
@ -162,3 +163,14 @@ export function checkCardinalitySuccess(data: any) {
|
|||
|
||||
return response;
|
||||
}
|
||||
|
||||
export function getRisonValue<T extends string | boolean | number | object | undefined | null>(
|
||||
risonString: string,
|
||||
defaultValue: T
|
||||
) {
|
||||
try {
|
||||
return rison.decode(risonString) as T;
|
||||
} catch (error) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import { parse } from 'query-string';
|
||||
import { useMlKibana } from '../../../contexts/kibana';
|
||||
import { ML_PAGES } from '../../../../locator';
|
||||
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
|
||||
import { useRouteResolver } from '../../use_resolver';
|
||||
import { resolver } from '../../../jobs/new_job/job_from_pattern_analysis';
|
||||
|
||||
export const fromPatternAnalysisRouteFactory = (): MlRoute => ({
|
||||
path: createPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_FROM_PATTERN_ANALYSIS),
|
||||
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
|
||||
breadcrumbs: [],
|
||||
});
|
||||
|
||||
const PageWrapper: FC<PageProps> = ({ location }) => {
|
||||
const {
|
||||
categorizationType,
|
||||
dataViewId,
|
||||
field,
|
||||
partitionField,
|
||||
stopOnWarn,
|
||||
from,
|
||||
to,
|
||||
query,
|
||||
}: Record<string, any> = parse(location.search, {
|
||||
sort: false,
|
||||
});
|
||||
const {
|
||||
services: {
|
||||
data,
|
||||
dashboard: dashboardService,
|
||||
uiSettings: kibanaConfig,
|
||||
mlServices: { mlApiServices },
|
||||
},
|
||||
} = useMlKibana();
|
||||
|
||||
const { context } = useRouteResolver('full', ['canCreateJob'], {
|
||||
redirect: () =>
|
||||
resolver(
|
||||
{
|
||||
mlApiServices,
|
||||
timeFilter: data.query.timefilter.timefilter,
|
||||
kibanaConfig,
|
||||
dashboardService,
|
||||
data,
|
||||
},
|
||||
categorizationType,
|
||||
dataViewId,
|
||||
field,
|
||||
partitionField,
|
||||
stopOnWarn,
|
||||
from,
|
||||
to,
|
||||
query
|
||||
),
|
||||
});
|
||||
|
||||
return (
|
||||
<PageLoader context={context}>
|
||||
{<Redirect to={createPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB)} />}
|
||||
</PageLoader>
|
||||
);
|
||||
};
|
|
@ -12,3 +12,4 @@ export * from './wizard';
|
|||
export * from './recognize';
|
||||
export * from './from_lens';
|
||||
export * from './from_map';
|
||||
export * from './from_pattern_analysis';
|
||||
|
|
|
@ -14,15 +14,21 @@ import {
|
|||
EVENT_RATE_FIELD_ID,
|
||||
} from '@kbn/ml-anomaly-utils';
|
||||
import { getGeoFields, filterCategoryFields } from '../../../../common/util/fields_utils';
|
||||
import { ml } from '../ml_api_service';
|
||||
import { ml, type MlApiServices } from '../ml_api_service';
|
||||
import { processTextAndKeywordFields, NewJobCapabilitiesServiceBase } from './new_job_capabilities';
|
||||
|
||||
class NewJobCapsService extends NewJobCapabilitiesServiceBase {
|
||||
export class NewJobCapsService extends NewJobCapabilitiesServiceBase {
|
||||
private _catFields: Field[] = [];
|
||||
private _dateFields: Field[] = [];
|
||||
private _geoFields: Field[] = [];
|
||||
private _includeEventRateField: boolean = true;
|
||||
private _removeTextFields: boolean = true;
|
||||
private _mlApiService: MlApiServices;
|
||||
|
||||
constructor(mlApiService: MlApiServices) {
|
||||
super();
|
||||
this._mlApiService = mlApiService;
|
||||
}
|
||||
|
||||
public get catFields(): Field[] {
|
||||
return this._catFields;
|
||||
|
@ -49,7 +55,10 @@ class NewJobCapsService extends NewJobCapabilitiesServiceBase {
|
|||
this._includeEventRateField = includeEventRateField;
|
||||
this._removeTextFields = removeTextFields;
|
||||
|
||||
const resp = await ml.jobs.newJobCaps(dataView.getIndexPattern(), dataView.type === 'rollup');
|
||||
const resp = await this._mlApiService.jobs.newJobCaps(
|
||||
dataView.getIndexPattern(),
|
||||
dataView.type === 'rollup'
|
||||
);
|
||||
const { fields: allFields, aggs } = createObjects(resp, dataView.getIndexPattern());
|
||||
|
||||
if (this._includeEventRateField === true) {
|
||||
|
@ -175,4 +184,4 @@ function addEventRateField(aggs: Aggregation[], fields: Field[]) {
|
|||
fields.splice(0, 0, eventRateField);
|
||||
}
|
||||
|
||||
export const newJobCapsService = new NewJobCapsService();
|
||||
export const newJobCapsService = new NewJobCapsService(ml);
|
||||
|
|
|
@ -0,0 +1,273 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React, { FC, useCallback, useMemo, useState, useEffect } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import {
|
||||
EuiCheckableCard,
|
||||
EuiTitle,
|
||||
EuiSpacer,
|
||||
EuiSwitch,
|
||||
EuiHorizontalRule,
|
||||
EuiComboBoxOptionOption,
|
||||
EuiComboBox,
|
||||
EuiFormRow,
|
||||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import type { DataViewField, DataView } from '@kbn/data-views-plugin/common';
|
||||
import type { TimeRange } from '@kbn/es-query';
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { redirectToADJobWizards } from '../../../../application/jobs/new_job/job_from_pattern_analysis/utils';
|
||||
import { createFieldOptions } from '../../../../application/jobs/new_job/common/job_creator/util/general';
|
||||
import { NewJobCapsService } from '../../../../application/services/new_job_capabilities/new_job_capabilities_service';
|
||||
import {
|
||||
type CategorizationType,
|
||||
CATEGORIZATION_TYPE,
|
||||
QuickCategorizationJobCreator,
|
||||
} from '../../../../application/jobs/new_job/job_from_pattern_analysis';
|
||||
import { useMlFromLensKibanaContext } from '../../common/context';
|
||||
import { JobDetails, type CreateADJobParams } from '../../common/job_details';
|
||||
|
||||
interface Props {
|
||||
dataView: DataView;
|
||||
field: DataViewField;
|
||||
query: QueryDslQueryContainer;
|
||||
timeRange: TimeRange;
|
||||
}
|
||||
|
||||
export const CreateJob: FC<Props> = ({ dataView, field, query, timeRange }) => {
|
||||
const {
|
||||
services: {
|
||||
data,
|
||||
share,
|
||||
uiSettings,
|
||||
mlServices: { mlApiServices },
|
||||
dashboardService,
|
||||
},
|
||||
} = useMlFromLensKibanaContext();
|
||||
|
||||
const [categorizationType, setCategorizationType] = useState<CategorizationType>(
|
||||
CATEGORIZATION_TYPE.COUNT
|
||||
);
|
||||
const [enablePerPartitionCategorization, setEnablePerPartitionCategorization] = useState(false);
|
||||
const [stopOnWarn, setStopOnWarn] = useState(false);
|
||||
const [categoryFieldOptions, setCategoryFieldsOptions] = useState<EuiComboBoxOptionOption[]>([]);
|
||||
const [selectedPartitionFieldOptions, setSelectedPartitionFieldOptions] = useState<
|
||||
EuiComboBoxOptionOption[]
|
||||
>([]);
|
||||
const [formComplete, setFormComplete] = useState<boolean | undefined>(undefined);
|
||||
|
||||
const toggleEnablePerPartitionCategorization = useCallback(
|
||||
() => setEnablePerPartitionCategorization(!enablePerPartitionCategorization),
|
||||
[enablePerPartitionCategorization]
|
||||
);
|
||||
|
||||
const toggleStopOnWarn = useCallback(() => setStopOnWarn(!stopOnWarn), [stopOnWarn]);
|
||||
|
||||
useMemo(() => {
|
||||
const newJobCapsService = new NewJobCapsService(mlApiServices);
|
||||
newJobCapsService.initializeFromDataVIew(dataView).then(() => {
|
||||
const options: EuiComboBoxOptionOption[] = [
|
||||
...createFieldOptions(newJobCapsService.categoryFields, []),
|
||||
].map((o) => ({
|
||||
...o,
|
||||
}));
|
||||
setCategoryFieldsOptions(options);
|
||||
});
|
||||
}, [dataView, mlApiServices]);
|
||||
|
||||
const quickJobCreator = useMemo(
|
||||
() =>
|
||||
new QuickCategorizationJobCreator(
|
||||
uiSettings,
|
||||
data.query.timefilter.timefilter,
|
||||
dashboardService,
|
||||
data,
|
||||
mlApiServices
|
||||
),
|
||||
|
||||
[dashboardService, data, mlApiServices, uiSettings]
|
||||
);
|
||||
|
||||
function createADJobInWizard() {
|
||||
const partitionField = selectedPartitionFieldOptions.length
|
||||
? dataView.getFieldByName(selectedPartitionFieldOptions[0].label) ?? null
|
||||
: null;
|
||||
redirectToADJobWizards(
|
||||
categorizationType,
|
||||
dataView,
|
||||
field,
|
||||
partitionField,
|
||||
stopOnWarn,
|
||||
query,
|
||||
timeRange,
|
||||
share
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedPartitionFieldOptions([]);
|
||||
setStopOnWarn(false);
|
||||
}, [enablePerPartitionCategorization]);
|
||||
|
||||
useEffect(() => {
|
||||
setFormComplete(
|
||||
enablePerPartitionCategorization === false || selectedPartitionFieldOptions.length > 0
|
||||
);
|
||||
}, [enablePerPartitionCategorization, selectedPartitionFieldOptions]);
|
||||
|
||||
async function createADJob({ jobId, bucketSpan, startJob, runInRealTime }: CreateADJobParams) {
|
||||
const partitionField = selectedPartitionFieldOptions.length
|
||||
? dataView.getFieldByName(selectedPartitionFieldOptions[0].label) ?? null
|
||||
: null;
|
||||
const result = await quickJobCreator.createAndSaveJob(
|
||||
categorizationType,
|
||||
jobId,
|
||||
bucketSpan,
|
||||
dataView,
|
||||
field,
|
||||
partitionField,
|
||||
stopOnWarn,
|
||||
query,
|
||||
timeRange,
|
||||
startJob,
|
||||
runInRealTime
|
||||
);
|
||||
return result;
|
||||
}
|
||||
return (
|
||||
<JobDetails
|
||||
createADJob={createADJob}
|
||||
createADJobInWizard={createADJobInWizard}
|
||||
embeddable={undefined}
|
||||
timeRange={timeRange}
|
||||
layer={undefined}
|
||||
layerIndex={0}
|
||||
outerFormComplete={formComplete}
|
||||
>
|
||||
<>
|
||||
<EuiCheckableCard
|
||||
id={'count'}
|
||||
label={
|
||||
<>
|
||||
<EuiTitle size="xs">
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
defaultMessage="Count"
|
||||
id="xpack.ml.newJobFromPatternAnalysisFlyout.count.title"
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<FormattedMessage
|
||||
defaultMessage="Look for anomalies in the event rate of a category."
|
||||
id="xpack.ml.newJobFromPatternAnalysisFlyout.count.description"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
checked={categorizationType === CATEGORIZATION_TYPE.COUNT}
|
||||
onChange={() => setCategorizationType(CATEGORIZATION_TYPE.COUNT)}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiCheckableCard
|
||||
id={'rare'}
|
||||
label={
|
||||
<>
|
||||
<EuiTitle size="xs">
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
defaultMessage="Rare"
|
||||
id="xpack.ml.newJobFromPatternAnalysisFlyout.rare.title"
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<FormattedMessage
|
||||
defaultMessage="Look for categories that occur rarely in time."
|
||||
id="xpack.ml.newJobFromPatternAnalysisFlyout.rare.description"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
checked={categorizationType === CATEGORIZATION_TYPE.RARE}
|
||||
onChange={() => setCategorizationType(CATEGORIZATION_TYPE.RARE)}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiSwitch
|
||||
name="categorizationPerPartitionSwitch"
|
||||
disabled={false}
|
||||
checked={enablePerPartitionCategorization}
|
||||
onChange={toggleEnablePerPartitionCategorization}
|
||||
data-test-subj="mlNewJobFromPatternAnalysisFlyoutSwitchCategorizationPerPartition"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.newJobFromPatternAnalysisFlyout.perPartitionCategorizationSwitchLabel"
|
||||
defaultMessage="Enable per-partition categorization"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
{enablePerPartitionCategorization ? (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiCallOut
|
||||
size="s"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.newJobFromPatternAnalysisFlyout.categorizationPerPartitionField.infoCallout"
|
||||
defaultMessage="Determine categories independently for each value of the partition field."
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.newJobFromPatternAnalysisFlyout.categorizationPerPartitionFieldLabel"
|
||||
defaultMessage="Partition field"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiComboBox
|
||||
singleSelection={{ asPlainText: true }}
|
||||
options={categoryFieldOptions}
|
||||
selectedOptions={selectedPartitionFieldOptions}
|
||||
onChange={setSelectedPartitionFieldOptions}
|
||||
isClearable={true}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiSwitch
|
||||
name="categorizationPerPartitionSwitch"
|
||||
disabled={false}
|
||||
checked={stopOnWarn}
|
||||
onChange={toggleStopOnWarn}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ml.newJobFromPatternAnalysisFlyout.stopOnWarnSwitchLabel"
|
||||
defaultMessage="Stop on warn"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiHorizontalRule margin="m" />
|
||||
</>
|
||||
</JobDetails>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiFlyoutFooter,
|
||||
EuiFlyoutHeader,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonEmpty,
|
||||
EuiFlyoutBody,
|
||||
EuiTitle,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import type { DataViewField, DataView } from '@kbn/data-views-plugin/common';
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import type { TimeRange } from '@kbn/es-query';
|
||||
import { CreateJob } from './create_job';
|
||||
|
||||
interface Props {
|
||||
dataView: DataView;
|
||||
field: DataViewField;
|
||||
query: QueryDslQueryContainer;
|
||||
timeRange: TimeRange;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const CreateCategorizationJobFlyout: FC<Props> = ({
|
||||
onClose,
|
||||
dataView,
|
||||
field,
|
||||
query,
|
||||
timeRange,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.ml.embeddables.newJobFromPatternAnalysisFlyout.title"
|
||||
defaultMessage="Create anomaly detection job"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.embeddables.newJobFromPatternAnalysisFlyout.secondTitle"
|
||||
defaultMessage="Create a categorization job for {field}"
|
||||
values={{ field: field.name }}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<CreateJob dataView={dataView} field={field} query={query} timeRange={timeRange} />
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty iconType="cross" onClick={onClose} flush="left">
|
||||
<FormattedMessage
|
||||
id="xpack.ml.embeddables.newJobFromPatternAnalysisFlyout.closeButton"
|
||||
defaultMessage="Close"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 { CreateCategorizationJobFlyout } from './flyout';
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 { showPatternAnalysisToADJobFlyout } from './show_flyout';
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import type { SharePluginStart } from '@kbn/share-plugin/public';
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { LensPublicStart } from '@kbn/lens-plugin/public';
|
||||
import type { DashboardStart } from '@kbn/dashboard-plugin/public';
|
||||
import type { DataView, DataViewField } from '@kbn/data-views-plugin/common';
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import type { TimeRange } from '@kbn/es-query';
|
||||
import { createFlyout, type FlyoutComponentProps } from '../common/create_flyout';
|
||||
import { CreateCategorizationJobFlyout } from './flyout';
|
||||
|
||||
export async function showPatternAnalysisToADJobFlyout(
|
||||
dataView: DataView,
|
||||
field: DataViewField,
|
||||
query: QueryDslQueryContainer,
|
||||
timeRange: TimeRange,
|
||||
coreStart: CoreStart,
|
||||
share: SharePluginStart,
|
||||
data: DataPublicPluginStart,
|
||||
dashboardService: DashboardStart,
|
||||
lens?: LensPublicStart
|
||||
): Promise<void> {
|
||||
const Comp: FC<FlyoutComponentProps> = ({ onClose }) => (
|
||||
<CreateCategorizationJobFlyout
|
||||
dataView={dataView}
|
||||
field={field}
|
||||
query={query}
|
||||
timeRange={timeRange}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
return createFlyout(Comp, coreStart, share, data, dashboardService, lens);
|
||||
}
|
|
@ -14,15 +14,16 @@ import type { SharePluginStart } from '@kbn/share-plugin/public';
|
|||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import type { LensPublicStart } from '@kbn/lens-plugin/public';
|
||||
import type { MapEmbeddable } from '@kbn/maps-plugin/public';
|
||||
import type { Embeddable } from '@kbn/lens-plugin/public';
|
||||
import type { DashboardStart } from '@kbn/dashboard-plugin/public';
|
||||
|
||||
import { getMlGlobalServices } from '../../../application/app';
|
||||
|
||||
export interface FlyoutComponentProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function createFlyout(
|
||||
FlyoutComponent: React.FunctionComponent<any>,
|
||||
embeddable: MapEmbeddable | Embeddable,
|
||||
coreStart: CoreStart,
|
||||
share: SharePluginStart,
|
||||
data: DataPublicPluginStart,
|
||||
|
@ -57,7 +58,6 @@ export function createFlyout(
|
|||
}}
|
||||
>
|
||||
<FlyoutComponent
|
||||
embeddable={embeddable}
|
||||
onClose={() => {
|
||||
onFlyoutClose();
|
||||
resolve();
|
||||
|
|
|
@ -32,6 +32,7 @@ import type { Embeddable } from '@kbn/lens-plugin/public';
|
|||
import type { MapEmbeddable } from '@kbn/maps-plugin/public';
|
||||
import { extractErrorMessage } from '@kbn/ml-error-utils';
|
||||
|
||||
import type { TimeRange } from '@kbn/es-query';
|
||||
import { QuickLensJobCreator } from '../../../application/jobs/new_job/job_from_lens';
|
||||
import type { LayerResult } from '../../../application/jobs/new_job/job_from_lens';
|
||||
import type { CreateState } from '../../../application/jobs/new_job/job_from_dashboard';
|
||||
|
@ -40,12 +41,12 @@ import { basicJobValidation } from '../../../../common/util/job_utils';
|
|||
import { JOB_ID_MAX_LENGTH } from '../../../../common/constants/validation';
|
||||
import { invalidTimeIntervalMessage } from '../../../application/jobs/new_job/common/job_validator/util';
|
||||
import { ML_APP_LOCATOR, ML_PAGES } from '../../../../common/constants/locator';
|
||||
import { useMlFromLensKibanaContext } from '../lens/context';
|
||||
import { useMlFromLensKibanaContext } from './context';
|
||||
|
||||
export interface CreateADJobParams {
|
||||
jobId: string;
|
||||
bucketSpan: string;
|
||||
embeddable: MapEmbeddable | Embeddable;
|
||||
embeddable: MapEmbeddable | Embeddable | undefined;
|
||||
startJob: boolean;
|
||||
runInRealTime: boolean;
|
||||
}
|
||||
|
@ -56,8 +57,10 @@ interface Props {
|
|||
createADJob: (args: CreateADJobParams) => Promise<CreateState>;
|
||||
layer?: LayerResult;
|
||||
layerIndex: number;
|
||||
embeddable: Embeddable | MapEmbeddable;
|
||||
embeddable: Embeddable | MapEmbeddable | undefined;
|
||||
timeRange: TimeRange | undefined;
|
||||
incomingCreateError?: { text: string; errorText: string };
|
||||
outerFormComplete?: boolean;
|
||||
}
|
||||
|
||||
enum STATE {
|
||||
|
@ -75,7 +78,9 @@ export const JobDetails: FC<Props> = ({
|
|||
layer,
|
||||
layerIndex,
|
||||
embeddable,
|
||||
timeRange,
|
||||
incomingCreateError,
|
||||
outerFormComplete,
|
||||
}) => {
|
||||
const {
|
||||
services: {
|
||||
|
@ -121,7 +126,6 @@ export const JobDetails: FC<Props> = ({
|
|||
|
||||
const viewResults = useCallback(
|
||||
async (type: JOB_TYPE | null) => {
|
||||
const { timeRange } = embeddable.getInput();
|
||||
const locator = share.url.locators.get(ML_APP_LOCATOR);
|
||||
if (locator) {
|
||||
const page = startJob
|
||||
|
@ -144,7 +148,7 @@ export const JobDetails: FC<Props> = ({
|
|||
application.navigateToUrl(url);
|
||||
}
|
||||
},
|
||||
[jobId, embeddable, share, application, startJob]
|
||||
[share, startJob, jobId, timeRange, application]
|
||||
);
|
||||
|
||||
function setStartJobWrapper(start: boolean) {
|
||||
|
@ -313,7 +317,8 @@ export const JobDetails: FC<Props> = ({
|
|||
state === STATE.VALIDATING ||
|
||||
jobId === '' ||
|
||||
jobIdValidationError !== '' ||
|
||||
bucketSpanValidationError !== ''
|
||||
bucketSpanValidationError !== '' ||
|
||||
outerFormComplete === false
|
||||
}
|
||||
onClick={createJob.bind(null, layerIndex)}
|
||||
size="s"
|
||||
|
|
|
@ -23,7 +23,7 @@ import {
|
|||
import { Layer } from './layer';
|
||||
import type { LayerResult } from '../../../../application/jobs/new_job/job_from_lens';
|
||||
import { VisualizationExtractor } from '../../../../application/jobs/new_job/job_from_lens';
|
||||
import { useMlFromLensKibanaContext } from '../context';
|
||||
import { useMlFromLensKibanaContext } from '../../common/context';
|
||||
|
||||
interface Props {
|
||||
embeddable: Embeddable;
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
} from '../../../../../application/jobs/new_job/job_from_lens';
|
||||
import type { LayerResult } from '../../../../../application/jobs/new_job/job_from_lens';
|
||||
import { JOB_TYPE } from '../../../../../../common/constants/new_job';
|
||||
import { useMlFromLensKibanaContext } from '../../context';
|
||||
import { useMlFromLensKibanaContext } from '../../../common/context';
|
||||
import { JobDetails, CreateADJobParams } from '../../../common/job_details';
|
||||
|
||||
interface Props {
|
||||
|
@ -79,6 +79,7 @@ export const CompatibleLayer: FC<Props> = ({ layer, layerIndex, embeddable }) =>
|
|||
createADJob={createADJob}
|
||||
createADJobInWizard={createADJobInWizard}
|
||||
embeddable={embeddable}
|
||||
timeRange={embeddable.getInput().timeRange}
|
||||
layer={layer}
|
||||
layerIndex={layerIndex}
|
||||
>
|
||||
|
|
|
@ -5,13 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import type { Embeddable } from '@kbn/lens-plugin/public';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import type { SharePluginStart } from '@kbn/share-plugin/public';
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { LensPublicStart } from '@kbn/lens-plugin/public';
|
||||
import type { DashboardStart } from '@kbn/dashboard-plugin/public';
|
||||
import { createFlyout } from '../common/create_flyout';
|
||||
import { createFlyout, type FlyoutComponentProps } from '../common/create_flyout';
|
||||
import { LensLayerSelectionFlyout } from './lens_vis_layer_selection_flyout';
|
||||
|
||||
export async function showLensVisToADJobFlyout(
|
||||
|
@ -19,16 +20,11 @@ export async function showLensVisToADJobFlyout(
|
|||
coreStart: CoreStart,
|
||||
share: SharePluginStart,
|
||||
data: DataPublicPluginStart,
|
||||
lens: LensPublicStart,
|
||||
dashboardService: DashboardStart
|
||||
dashboardService: DashboardStart,
|
||||
lens: LensPublicStart
|
||||
): Promise<void> {
|
||||
return createFlyout(
|
||||
LensLayerSelectionFlyout,
|
||||
embeddable,
|
||||
coreStart,
|
||||
share,
|
||||
data,
|
||||
dashboardService,
|
||||
lens
|
||||
const Comp: FC<FlyoutComponentProps> = ({ onClose }) => (
|
||||
<LensLayerSelectionFlyout embeddable={embeddable} onClose={onClose} />
|
||||
);
|
||||
return createFlyout(Comp, coreStart, share, data, dashboardService, lens);
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import {
|
|||
QuickGeoJobCreator,
|
||||
redirectToGeoJobWizard,
|
||||
} from '../../../../../application/jobs/new_job/job_from_map';
|
||||
import { useMlFromLensKibanaContext } from '../../../lens/context';
|
||||
import { useMlFromLensKibanaContext } from '../../../common/context';
|
||||
import { JobDetails, CreateADJobParams } from '../../../common/job_details';
|
||||
|
||||
interface DropDownLabel {
|
||||
|
@ -147,6 +147,7 @@ export const CompatibleLayer: FC<Props> = ({ embeddable, layer, layerIndex }) =>
|
|||
createADJob={createGeoJob}
|
||||
createADJobInWizard={createGeoJobInWizard}
|
||||
embeddable={embeddable}
|
||||
timeRange={embeddable.getInput().timeRange}
|
||||
incomingCreateError={createError}
|
||||
>
|
||||
<>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import type { SharePluginStart } from '@kbn/share-plugin/public';
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
|
@ -12,7 +13,7 @@ import type { MapEmbeddable } from '@kbn/maps-plugin/public';
|
|||
import type { DashboardStart } from '@kbn/dashboard-plugin/public';
|
||||
|
||||
import { GeoJobFlyout } from './flyout';
|
||||
import { createFlyout } from '../common/create_flyout';
|
||||
import { createFlyout, type FlyoutComponentProps } from '../common/create_flyout';
|
||||
|
||||
export async function showMapVisToADJobFlyout(
|
||||
embeddable: MapEmbeddable,
|
||||
|
@ -21,5 +22,8 @@ export async function showMapVisToADJobFlyout(
|
|||
data: DataPublicPluginStart,
|
||||
dashboardService: DashboardStart
|
||||
): Promise<void> {
|
||||
return createFlyout(GeoJobFlyout, embeddable, coreStart, share, data, dashboardService);
|
||||
const Comp: FC<FlyoutComponentProps> = ({ onClose }) => (
|
||||
<GeoJobFlyout embeddable={embeddable} onClose={onClose} />
|
||||
);
|
||||
return createFlyout(Comp, coreStart, share, data, dashboardService);
|
||||
}
|
||||
|
|
|
@ -85,6 +85,7 @@ export class MlLocatorDefinition implements LocatorDefinition<MlLocatorParams> {
|
|||
case ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_ADVANCED:
|
||||
case ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_FROM_LENS:
|
||||
case ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_FROM_MAP:
|
||||
case ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_FROM_PATTERN_ANALYSIS:
|
||||
case ML_PAGES.DATA_VISUALIZER:
|
||||
case ML_PAGES.DATA_VISUALIZER_FILE:
|
||||
case ML_PAGES.DATA_VISUALIZER_INDEX_VIEWER:
|
||||
|
|
|
@ -8,9 +8,14 @@
|
|||
import { CoreSetup } from '@kbn/core/public';
|
||||
import { UiActionsSetup } from '@kbn/ui-actions-plugin/public';
|
||||
import { CONTEXT_MENU_TRIGGER } from '@kbn/embeddable-plugin/public';
|
||||
import { CREATE_PATTERN_ANALYSIS_TO_ML_AD_JOB_TRIGGER } from '@kbn/ml-ui-actions';
|
||||
import { createEditSwimlanePanelAction } from './edit_swimlane_panel_action';
|
||||
import { createOpenInExplorerAction } from './open_in_anomaly_explorer_action';
|
||||
import { createVisToADJobAction } from './open_vis_in_ml_action';
|
||||
import {
|
||||
createCategorizationADJobAction,
|
||||
createCategorizationADJobTrigger,
|
||||
} from './open_create_categorization_job_action';
|
||||
import { MlPluginStart, MlStartDependencies } from '../plugin';
|
||||
import { createApplyInfluencerFiltersAction } from './apply_influencer_filters_action';
|
||||
import {
|
||||
|
@ -45,6 +50,7 @@ export function registerMlUiActions(
|
|||
const clearSelectionAction = createClearSelectionAction(core.getStartServices);
|
||||
const editExplorerPanelAction = createEditAnomalyChartsPanelAction(core.getStartServices);
|
||||
const visToAdJobAction = createVisToADJobAction(core.getStartServices);
|
||||
const categorizationADJobAction = createCategorizationADJobAction(core.getStartServices);
|
||||
|
||||
// Register actions
|
||||
uiActions.registerAction(editSwimlanePanelAction);
|
||||
|
@ -54,6 +60,7 @@ export function registerMlUiActions(
|
|||
uiActions.registerAction(applyTimeRangeSelectionAction);
|
||||
uiActions.registerAction(clearSelectionAction);
|
||||
uiActions.registerAction(editExplorerPanelAction);
|
||||
uiActions.registerAction(categorizationADJobAction);
|
||||
|
||||
// Assign triggers
|
||||
uiActions.attachAction(CONTEXT_MENU_TRIGGER, editSwimlanePanelAction.id);
|
||||
|
@ -62,6 +69,7 @@ export function registerMlUiActions(
|
|||
|
||||
uiActions.registerTrigger(swimLaneSelectionTrigger);
|
||||
uiActions.registerTrigger(entityFieldSelectionTrigger);
|
||||
uiActions.registerTrigger(createCategorizationADJobTrigger);
|
||||
|
||||
uiActions.addTriggerAction(SWIM_LANE_SELECTION_TRIGGER, applyInfluencerFiltersAction);
|
||||
uiActions.addTriggerAction(SWIM_LANE_SELECTION_TRIGGER, applyTimeRangeSelectionAction);
|
||||
|
@ -69,4 +77,8 @@ export function registerMlUiActions(
|
|||
uiActions.addTriggerAction(SWIM_LANE_SELECTION_TRIGGER, clearSelectionAction);
|
||||
uiActions.addTriggerAction(EXPLORER_ENTITY_FIELD_SELECTION_TRIGGER, applyEntityFieldFilterAction);
|
||||
uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, visToAdJobAction);
|
||||
uiActions.addTriggerAction(
|
||||
CREATE_PATTERN_ANALYSIS_TO_ML_AD_JOB_TRIGGER,
|
||||
categorizationADJobAction
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { Trigger, UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
|
||||
import {
|
||||
CREATE_PATTERN_ANALYSIS_TO_ML_AD_JOB_ACTION,
|
||||
CREATE_PATTERN_ANALYSIS_TO_ML_AD_JOB_TRIGGER,
|
||||
type CreateCategorizationADJobContext,
|
||||
} from '@kbn/ml-ui-actions';
|
||||
import type { MlCoreSetup } from '../plugin';
|
||||
|
||||
export const createCategorizationADJobTrigger: Trigger = {
|
||||
id: CREATE_PATTERN_ANALYSIS_TO_ML_AD_JOB_TRIGGER,
|
||||
title: i18n.translate('xpack.ml.actions.createADJobFromPatternAnalysis', {
|
||||
defaultMessage: 'Create categorization anomaly detection job',
|
||||
}),
|
||||
description: i18n.translate('xpack.ml.actions.createADJobFromPatternAnalysis', {
|
||||
defaultMessage: 'Create categorization anomaly detection job',
|
||||
}),
|
||||
};
|
||||
|
||||
export function createCategorizationADJobAction(
|
||||
getStartServices: MlCoreSetup['getStartServices']
|
||||
): UiActionsActionDefinition<CreateCategorizationADJobContext> {
|
||||
return {
|
||||
id: 'create-ml-categorization-ad-job-action',
|
||||
type: CREATE_PATTERN_ANALYSIS_TO_ML_AD_JOB_ACTION,
|
||||
getIconType(context): string {
|
||||
return 'machineLearningApp';
|
||||
},
|
||||
getDisplayName: () =>
|
||||
i18n.translate('xpack.ml.actions.createADJobFromPatternAnalysis', {
|
||||
defaultMessage: 'Create categorization anomaly detection job',
|
||||
}),
|
||||
async execute({ dataView, field, query, timeRange }: CreateCategorizationADJobContext) {
|
||||
if (!dataView) {
|
||||
throw new Error('Not possible to execute an action without the embeddable context');
|
||||
}
|
||||
|
||||
try {
|
||||
const [{ showPatternAnalysisToADJobFlyout }, [coreStart, { share, data, dashboard }]] =
|
||||
await Promise.all([import('../embeddables/job_creation/aiops'), getStartServices()]);
|
||||
|
||||
await showPatternAnalysisToADJobFlyout(
|
||||
dataView,
|
||||
field,
|
||||
query,
|
||||
timeRange,
|
||||
coreStart,
|
||||
share,
|
||||
data,
|
||||
dashboard
|
||||
);
|
||||
} catch (e) {
|
||||
return Promise.reject();
|
||||
}
|
||||
},
|
||||
async isCompatible({ dataView, field }: CreateCategorizationADJobContext) {
|
||||
return (
|
||||
dataView.timeFieldName !== undefined &&
|
||||
dataView.fields.find((f) => f.name === field.name) !== undefined
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
|
@ -39,7 +39,7 @@ export function createVisToADJobAction(
|
|||
if (lens === undefined) {
|
||||
return;
|
||||
}
|
||||
await showLensVisToADJobFlyout(embeddable, coreStart, share, data, lens, dashboard);
|
||||
await showLensVisToADJobFlyout(embeddable, coreStart, share, data, dashboard, lens);
|
||||
} else if (isMapEmbeddable(embeddable)) {
|
||||
const [{ showMapVisToADJobFlyout }, [coreStart, { share, data, dashboard }]] =
|
||||
await Promise.all([import('../embeddables/job_creation/map'), getStartServices()]);
|
||||
|
|
|
@ -96,6 +96,7 @@
|
|||
"@kbn/ml-runtime-field-utils",
|
||||
"@kbn/ml-date-utils",
|
||||
"@kbn/ml-category-validator",
|
||||
"@kbn/ml-ui-actions",
|
||||
"@kbn/deeplinks-ml",
|
||||
"@kbn/core-notifications-browser-mocks",
|
||||
"@kbn/unified-field-list",
|
||||
|
|
|
@ -5069,6 +5069,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/ml-ui-actions@link:x-pack/packages/ml/ui_actions":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/ml-url-state@link:x-pack/packages/ml/url_state":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue