[ML][Embeddables Rebuild] Migrate Change Point Charts embeddable (#182246)

## Summary

Closes https://github.com/elastic/kibana/issues/174963

- Decouples UI actions from the embeddable framework 
- Removes custom Edit action and makes use of the default one
- Adds Add action 
- Migrates Change point detection charts to the react embeddable
registry
- Replaces the modal for initializing and editing embeddable config with
the flyout

<img width="1680" alt="image"
src="7648d2ae-927e-40e7-8a87-647ae89bc726">

### Checklist

- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [x] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
This commit is contained in:
Dima Arnautov 2024-05-13 17:35:22 +02:00 committed by GitHub
parent af91e552e4
commit bbd1017b95
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 923 additions and 699 deletions

View file

@ -15,6 +15,8 @@ export const CASES_ATTACHMENT_CHANGE_POINT_CHART = 'aiopsChangePointChart';
export const EMBEDDABLE_CHANGE_POINT_CHART_TYPE = 'aiopsChangePointChart' as const;
export const CHANGE_POINT_CHART_DATA_VIEW_REF_NAME = 'aiopsChangePointChartDataViewId';
export type EmbeddableChangePointType = typeof EMBEDDABLE_CHANGE_POINT_CHART_TYPE;
export const CHANGE_POINT_DETECTION_VIEW_TYPE = {

View file

@ -25,11 +25,12 @@
"usageCollection"
],
"requiredBundles": [
"dataViews",
"fieldFormats",
"kibanaReact",
"kibanaUtils",
"embeddable",
"cases"
"cases",
]
}
}

View file

@ -7,17 +7,22 @@
import { memoize } from 'lodash';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import type { FC } from 'react';
import React from 'react';
import type { PersistableStateAttachmentViewProps } from '@kbn/cases-plugin/public/client/attachment_framework/types';
import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiDescriptionList } from '@elastic/eui';
import deepEqual from 'fast-deep-equal';
import type { EmbeddableChangePointChartProps } from '../embeddable';
import type {
ChangePointDetectionProps,
ChangePointDetectionSharedComponent,
} from '../shared_components/change_point_detection';
export const initComponent = memoize(
(fieldFormats: FieldFormatsStart, EmbeddableComponent: FC<EmbeddableChangePointChartProps>) => {
(
fieldFormats: FieldFormatsStart,
ChangePointDetectionComponent: ChangePointDetectionSharedComponent
) => {
return React.memo(
(props: PersistableStateAttachmentViewProps) => {
const { persistableStateAttachmentState } = props;
@ -26,8 +31,7 @@ export const initComponent = memoize(
id: FIELD_FORMAT_IDS.DATE,
});
const inputProps =
persistableStateAttachmentState as unknown as EmbeddableChangePointChartProps;
const inputProps = persistableStateAttachmentState as unknown as ChangePointDetectionProps;
const listItems = [
{
@ -46,7 +50,7 @@ export const initComponent = memoize(
return (
<>
<EuiDescriptionList compressed type={'inline'} listItems={listItems} />
<EmbeddableComponent {...inputProps} embeddingOrigin={'cases'} />
<ChangePointDetectionComponent {...inputProps} embeddingOrigin={'cases'} />
</>
);
},

View file

@ -10,11 +10,8 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import type { CasesPublicSetup } from '@kbn/cases-plugin/public';
import type { CoreStart } from '@kbn/core/public';
import {
CASES_ATTACHMENT_CHANGE_POINT_CHART,
EMBEDDABLE_CHANGE_POINT_CHART_TYPE,
} from '@kbn/aiops-change-point-detection/constants';
import { getEmbeddableChangePointChart } from '../embeddable/embeddable_change_point_chart_component';
import { CASES_ATTACHMENT_CHANGE_POINT_CHART } from '@kbn/aiops-change-point-detection/constants';
import { getChangePointDetectionComponent } from '../shared_components';
import type { AiopsPluginStartDeps } from '../types';
export function registerChangePointChartsAttachment(
@ -22,11 +19,7 @@ export function registerChangePointChartsAttachment(
coreStart: CoreStart,
pluginStart: AiopsPluginStartDeps
) {
const EmbeddableComponent = getEmbeddableChangePointChart(
EMBEDDABLE_CHANGE_POINT_CHART_TYPE,
coreStart,
pluginStart
);
const ChangePointDetectionComponent = getChangePointDetectionComponent(coreStart, pluginStart);
cases.attachmentFramework.registerPersistableState({
id: CASES_ATTACHMENT_CHANGE_POINT_CHART,
@ -46,7 +39,7 @@ export function registerChangePointChartsAttachment(
const { initComponent } = await import('./change_point_charts_attachment');
return {
default: initComponent(pluginStart.fieldFormats, EmbeddableComponent),
default: initComponent(pluginStart.fieldFormats, ChangePointDetectionComponent),
};
}),
}),

View file

@ -37,9 +37,9 @@ import {
CHANGE_POINT_DETECTION_VIEW_TYPE,
EMBEDDABLE_CHANGE_POINT_CHART_TYPE,
} from '@kbn/aiops-change-point-detection/constants';
import type { ChangePointEmbeddableRuntimeState } from '../../embeddables/change_point_chart/types';
import { MaxSeriesControl } from './max_series_control';
import { useCasesModal } from '../../hooks/use_cases_modal';
import { type EmbeddableChangePointChartInput } from '../../embeddable/embeddable_change_point_chart';
import { useDataSource } from '../../hooks/use_data_source';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
import { ChangePointsTable } from './change_points_table';
@ -470,7 +470,7 @@ const FieldPanel: FC<FieldPanelProps> = ({
({ dashboardId, newTitle, newDescription }) => {
const stateTransfer = embeddable!.getStateTransfer();
const embeddableInput: Partial<EmbeddableChangePointChartInput> = {
const embeddableInput: Partial<ChangePointEmbeddableRuntimeState> = {
title: newTitle,
description: newDescription,
viewType: dashboardAttachment.viewType,

View file

@ -10,7 +10,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { EuiFieldNumber, EuiFormRow, EuiIcon, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { type NumberValidationResult, numberValidator } from '@kbn/ml-agg-utils';
import { MAX_SERIES } from '../../embeddable/const';
import { MAX_SERIES } from '../../embeddables/change_point_chart/const';
const maxSeriesValidator = numberValidator({ min: 1, max: MAX_SERIES, integerOnly: true });

View file

@ -1,161 +0,0 @@
/*
* 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, { Suspense } from 'react';
import ReactDOM from 'react-dom';
import type { EmbeddableInput, EmbeddableOutput, IContainer } from '@kbn/embeddable-plugin/public';
import { Embeddable as AbstractEmbeddable } from '@kbn/embeddable-plugin/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { UI_SETTINGS } from '@kbn/data-plugin/public';
import type { IUiSettingsClient } from '@kbn/core/public';
import { type CoreStart } from '@kbn/core/public';
import { DatePickerContextProvider } from '@kbn/ml-date-picker';
import { pick } from 'lodash';
import type { LensPublicStart } from '@kbn/lens-plugin/public';
import { Subject } from 'rxjs';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/common';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants';
import type { EmbeddableChangePointType } from '@kbn/aiops-change-point-detection/constants';
import { EmbeddableInputTracker } from './embeddable_chart_component_wrapper';
import { AiopsAppContext, type AiopsAppDependencies } from '../hooks/use_aiops_app_context';
import type { EmbeddableChangePointChartProps } from './embeddable_change_point_chart_component';
export type EmbeddableChangePointChartInput = EmbeddableInput & EmbeddableChangePointChartProps;
export type EmbeddableChangePointChartOutput = EmbeddableOutput & { indexPatterns?: DataView[] };
export interface EmbeddableChangePointChartDeps {
analytics: CoreStart['analytics'];
theme: CoreStart['theme'];
data: DataPublicPluginStart;
uiSettings: IUiSettingsClient;
http: CoreStart['http'];
notifications: CoreStart['notifications'];
i18n: CoreStart['i18n'];
lens: LensPublicStart;
usageCollection: UsageCollectionSetup;
fieldFormats: FieldFormatsStart;
}
export type IEmbeddableChangePointChart = typeof EmbeddableChangePointChart;
export class EmbeddableChangePointChart extends AbstractEmbeddable<
EmbeddableChangePointChartInput,
EmbeddableChangePointChartOutput
> {
private reload$ = new Subject<number>();
public reload(): void {
this.reload$.next(Date.now());
}
private node?: HTMLElement;
// Need to defer embeddable load in order to resolve data views
deferEmbeddableLoad = true;
constructor(
public readonly type: EmbeddableChangePointType,
private readonly deps: EmbeddableChangePointChartDeps,
initialInput: EmbeddableChangePointChartInput,
parent?: IContainer
) {
super(initialInput, {}, parent);
this.initOutput().finally(() => this.setInitializationFinished());
}
private async initOutput() {
const {
data: { dataViews: dataViewsService },
} = this.deps;
const { dataViewId } = this.getInput();
const dataView = await dataViewsService.get(dataViewId);
this.updateOutput({
indexPatterns: [dataView],
});
}
public reportsEmbeddableLoad() {
return true;
}
public onLoading(isLoading: boolean) {
this.renderComplete.dispatchInProgress();
this.updateOutput({ loading: isLoading, error: undefined });
}
public onError(error: Error) {
this.renderComplete.dispatchError();
this.updateOutput({ loading: false, error: { name: error.name, message: error.message } });
}
public onRenderComplete() {
this.renderComplete.dispatchComplete();
this.updateOutput({ loading: false, rendered: true, error: undefined });
}
render(el: HTMLElement): void {
super.render(el);
this.node = el;
// required for the export feature to work
this.node.setAttribute('data-shared-item', '');
// test subject selector for functional tests
this.node.setAttribute('data-test-subj', 'aiopsEmbeddableChangePointChart');
const startServices = pick(this.deps, 'analytics', 'i18n', 'theme');
const datePickerDeps = {
...pick(this.deps, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']),
uiSettingsKeys: UI_SETTINGS,
};
const input = this.getInput();
const input$ = this.getInput$();
const aiopsAppContextValue = {
embeddingOrigin: this.parent?.type ?? input.embeddingOrigin ?? EMBEDDABLE_ORIGIN,
...this.deps,
} as unknown as AiopsAppDependencies;
ReactDOM.render(
<KibanaRenderContextProvider {...startServices}>
<AiopsAppContext.Provider value={aiopsAppContextValue}>
<DatePickerContextProvider {...datePickerDeps}>
<Suspense fallback={null}>
<EmbeddableInputTracker
input$={input$}
initialInput={input}
reload$={this.reload$}
onOutputChange={this.updateOutput.bind(this)}
onRenderComplete={this.onRenderComplete.bind(this)}
onLoading={this.onLoading.bind(this)}
onError={this.onError.bind(this)}
/>
</Suspense>
</DatePickerContextProvider>
</AiopsAppContext.Provider>
</KibanaRenderContextProvider>,
el
);
}
public destroy() {
super.destroy();
if (this.node) {
ReactDOM.unmountComponentAtNode(this.node);
}
}
}

View file

@ -1,77 +0,0 @@
/*
* 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 { CoreStart } from '@kbn/core/public';
import type { TimeRange } from '@kbn/es-query';
import React from 'react';
import type { EmbeddableFactory, EmbeddableOutput } from '@kbn/embeddable-plugin/public';
import { EmbeddableRoot, useEmbeddableFactory } from '@kbn/embeddable-plugin/public';
import { EuiLoadingChart } from '@elastic/eui';
import type {
ChangePointDetectionViewType,
EmbeddableChangePointType,
} from '@kbn/aiops-change-point-detection/constants';
import type { AiopsPluginStartDeps } from '../types';
import type { EmbeddableChangePointChartInput } from './embeddable_change_point_chart';
import type { ChangePointAnnotation } from '../components/change_point_detection/change_point_detection_context';
export interface EmbeddableChangePointChartProps {
viewType?: ChangePointDetectionViewType;
dataViewId: string;
timeRange: TimeRange;
fn: 'avg' | 'sum' | 'min' | 'max' | string;
metricField: string;
splitField?: string;
partitions?: string[];
maxSeriesToPlot?: number;
/**
* Component to render if there are no change points found
*/
emptyState?: React.ReactElement;
/**
* Outputs the most recent change point data
*/
onChange?: (changePointData: ChangePointAnnotation[]) => void;
/**
* Last reload request time, can be used for manual reload
*/
lastReloadRequestTime?: number;
/** Origin of the embeddable instance */
embeddingOrigin?: string;
}
export function getEmbeddableChangePointChart(
visType: EmbeddableChangePointType,
core: CoreStart,
plugins: AiopsPluginStartDeps
) {
const { embeddable: embeddableStart } = plugins;
const factory = embeddableStart.getEmbeddableFactory<EmbeddableChangePointChartInput>(visType)!;
return (props: EmbeddableChangePointChartProps) => {
const input = { ...props };
return (
<EmbeddableRootWrapper factory={factory} input={input as EmbeddableChangePointChartInput} />
);
};
}
function EmbeddableRootWrapper({
factory,
input,
}: {
factory: EmbeddableFactory<EmbeddableChangePointChartInput, EmbeddableOutput>;
input: EmbeddableChangePointChartInput;
}) {
const [embeddable, loading, error] = useEmbeddableFactory<EmbeddableChangePointChartInput>({
factory,
input,
});
if (loading) {
return <EuiLoadingChart />;
}
return <EmbeddableRoot embeddable={embeddable} loading={loading} error={error} input={input} />;
}

View file

@ -1,94 +0,0 @@
/*
* 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 { EmbeddableFactoryDefinition, IContainer } from '@kbn/embeddable-plugin/public';
import { ErrorEmbeddable } from '@kbn/embeddable-plugin/public';
import { i18n } from '@kbn/i18n';
import { type DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { StartServicesAccessor } from '@kbn/core-lifecycle-browser';
import type {
EMBEDDABLE_CHANGE_POINT_CHART_TYPE,
EmbeddableChangePointType,
} from '@kbn/aiops-change-point-detection/constants';
import type { AiopsPluginStart, AiopsPluginStartDeps } from '../types';
import type { EmbeddableChangePointChartInput } from './embeddable_change_point_chart';
import { EmbeddableChangePointChart } from './embeddable_change_point_chart';
export interface EmbeddableChangePointChartStartServices {
data: DataPublicPluginStart;
}
export type EmbeddableChangePointChartType = typeof EMBEDDABLE_CHANGE_POINT_CHART_TYPE;
export class EmbeddableChangePointChartFactory implements EmbeddableFactoryDefinition {
public readonly grouping = [
{
id: 'ml',
getDisplayName: () =>
i18n.translate('xpack.aiops.navMenu.mlAppNameText', {
defaultMessage: 'Machine Learning',
}),
getIconType: () => 'machineLearningApp',
},
];
constructor(
public readonly type: EmbeddableChangePointType,
private readonly name: string,
private readonly getStartServices: StartServicesAccessor<AiopsPluginStartDeps, AiopsPluginStart>
) {}
public isEditable = async () => {
return true;
};
getDisplayName() {
return this.name;
}
canCreateNew() {
return true;
}
public async getExplicitInput(): Promise<Partial<EmbeddableChangePointChartInput>> {
const [coreStart, pluginStart] = await this.getStartServices();
try {
const { resolveEmbeddableChangePointUserInput } = await import('./handle_explicit_input');
return await resolveEmbeddableChangePointUserInput(coreStart, pluginStart);
} catch (e) {
return Promise.reject();
}
}
async create(input: EmbeddableChangePointChartInput, parent?: IContainer) {
try {
const [
{ http, uiSettings, notifications, ...startServices },
{ lens, data, usageCollection, fieldFormats },
] = await this.getStartServices();
return new EmbeddableChangePointChart(
this.type,
{
http,
uiSettings,
data,
notifications,
lens,
usageCollection,
fieldFormats,
...startServices,
},
input,
parent
);
} catch (e) {
return new ErrorEmbeddable(e, input, parent);
}
}
}

View file

@ -1,27 +0,0 @@
/*
* 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 { CoreSetup } from '@kbn/core-lifecycle-browser';
import type { EmbeddableSetup } from '@kbn/embeddable-plugin/public';
import { i18n } from '@kbn/i18n';
import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '@kbn/aiops-change-point-detection/constants';
import type { AiopsPluginStart, AiopsPluginStartDeps } from '../types';
import { EmbeddableChangePointChartFactory } from './embeddable_change_point_chart_factory';
export const registerEmbeddable = (
core: CoreSetup<AiopsPluginStartDeps, AiopsPluginStart>,
embeddable: EmbeddableSetup
) => {
const changePointChartFactory = new EmbeddableChangePointChartFactory(
EMBEDDABLE_CHANGE_POINT_CHART_TYPE,
i18n.translate('xpack.aiops.embeddableChangePointChartDisplayName', {
defaultMessage: 'Change point detection',
}),
core.getStartServices
);
embeddable.registerEmbeddableFactory(changePointChartFactory.type, changePointChartFactory);
};

View file

@ -1,29 +0,0 @@
/*
* 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 { FC } from 'react';
import type { IEmbeddable } from '@kbn/embeddable-plugin/public';
import type { SelectedChangePoint } from '../components/change_point_detection/change_point_detection_context';
import type {
EmbeddableChangePointChartInput,
EmbeddableChangePointChartOutput,
} from './embeddable_change_point_chart';
import type { EmbeddableChangePointChartProps } from './embeddable_change_point_chart_component';
export type EmbeddableChangePointChartExplicitInput = {
title: string;
} & Omit<EmbeddableChangePointChartProps, 'timeRange'>;
export interface EditChangePointChartsPanelContext {
embeddable: IEmbeddable<EmbeddableChangePointChartInput, EmbeddableChangePointChartOutput>;
}
export type ViewComponent = FC<{
changePoints: SelectedChangePoint[];
interval: string;
onRenderComplete?: () => void;
}>;

View file

@ -8,14 +8,15 @@
import {
EuiButton,
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiFlyoutBody,
EuiFlyoutFooter,
EuiFlyoutHeader,
EuiForm,
EuiFormRow,
EuiHorizontalRule,
EuiModal,
EuiModalBody,
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
EuiTitle,
} from '@elastic/eui';
import { ES_FIELD_TYPES } from '@kbn/field-types';
import { i18n } from '@kbn/i18n';
@ -28,24 +29,22 @@ import usePrevious from 'react-use/lib/usePrevious';
import {
ChangePointDetectionControlsContextProvider,
useChangePointDetectionControlsContext,
} from '../components/change_point_detection/change_point_detection_context';
import { DEFAULT_AGG_FUNCTION } from '../components/change_point_detection/constants';
import { FunctionPicker } from '../components/change_point_detection/function_picker';
import { MaxSeriesControl } from '../components/change_point_detection/max_series_control';
import { MetricFieldSelector } from '../components/change_point_detection/metric_field_selector';
import { PartitionsSelector } from '../components/change_point_detection/partitions_selector';
import { SplitFieldSelector } from '../components/change_point_detection/split_field_selector';
import { ViewTypeSelector } from '../components/change_point_detection/view_type_selector';
import { useAiopsAppContext } from '../hooks/use_aiops_app_context';
import { DataSourceContextProvider } from '../hooks/use_data_source';
} from '../../components/change_point_detection/change_point_detection_context';
import { DEFAULT_AGG_FUNCTION } from '../../components/change_point_detection/constants';
import { FunctionPicker } from '../../components/change_point_detection/function_picker';
import { MaxSeriesControl } from '../../components/change_point_detection/max_series_control';
import { MetricFieldSelector } from '../../components/change_point_detection/metric_field_selector';
import { PartitionsSelector } from '../../components/change_point_detection/partitions_selector';
import { SplitFieldSelector } from '../../components/change_point_detection/split_field_selector';
import { ViewTypeSelector } from '../../components/change_point_detection/view_type_selector';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
import { DataSourceContextProvider } from '../../hooks/use_data_source';
import { DEFAULT_SERIES } from './const';
import type { EmbeddableChangePointChartInput } from './embeddable_change_point_chart';
import type { EmbeddableChangePointChartProps } from './embeddable_change_point_chart_component';
import { type EmbeddableChangePointChartExplicitInput } from './types';
import type { ChangePointEmbeddableRuntimeState } from './types';
export interface AnomalyChartsInitializerProps {
initialInput?: Partial<EmbeddableChangePointChartInput>;
onCreate: (props: EmbeddableChangePointChartExplicitInput) => void;
initialInput?: Partial<ChangePointEmbeddableRuntimeState>;
onCreate: (props: ChangePointEmbeddableRuntimeState) => void;
onCancel: () => void;
}
@ -99,17 +98,19 @@ export const ChangePointChartInitializer: FC<AnomalyChartsInitializerProps> = ({
}, [formInput, dataViewId, viewType]);
return (
<EuiModal onClose={onCancel} data-test-subj={'aiopsChangePointChartEmbeddableInitializer'}>
<EuiModalHeader>
<EuiModalHeaderTitle>
<FormattedMessage
id="xpack.aiops.embeddableChangePointChart.modalTitle"
defaultMessage="Change point detection configuration"
/>
</EuiModalHeaderTitle>
</EuiModalHeader>
<>
<EuiFlyoutHeader>
<EuiTitle>
<h2 id={'changePointConfig'}>
<FormattedMessage
id="xpack.aiops.embeddableChangePointChart.modalTitle"
defaultMessage="Change point detection configuration"
/>
</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiModalBody>
<EuiFlyoutBody>
<EuiForm>
<ViewTypeSelector value={viewType} onChange={setViewType} />
<EuiFormRow
@ -145,37 +146,42 @@ export const ChangePointChartInitializer: FC<AnomalyChartsInitializerProps> = ({
</ChangePointDetectionControlsContextProvider>
</DataSourceContextProvider>
</EuiForm>
</EuiModalBody>
</EuiFlyoutBody>
<EuiModalFooter>
<EuiButtonEmpty
onClick={onCancel}
data-test-subj="aiopsChangePointChartsInitializerCancelButton"
>
<FormattedMessage
id="xpack.aiops.embeddableChangePointChart.setupModal.cancelButtonLabel"
defaultMessage="Cancel"
/>
</EuiButtonEmpty>
<EuiButton
data-test-subj="aiopsChangePointChartsInitializerConfirmButton"
isDisabled={!isFormValid || !dataViewId}
onClick={onCreate.bind(null, updatedProps)}
fill
>
<FormattedMessage
id="xpack.aiops.embeddableChangePointChart.setupModal.confirmButtonLabel"
defaultMessage="Confirm configurations"
/>
</EuiButton>
</EuiModalFooter>
</EuiModal>
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty
onClick={onCancel}
data-test-subj="aiopsChangePointChartsInitializerCancelButton"
>
<FormattedMessage
id="xpack.aiops.embeddableChangePointChart.setupModal.cancelButtonLabel"
defaultMessage="Cancel"
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
data-test-subj="aiopsChangePointChartsInitializerConfirmButton"
isDisabled={!isFormValid || !dataViewId}
onClick={onCreate.bind(null, updatedProps)}
fill
>
<FormattedMessage
id="xpack.aiops.embeddableChangePointChart.setupModal.confirmButtonLabel"
defaultMessage="Confirm configurations"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
</>
);
};
export type FormControlsProps = Pick<
EmbeddableChangePointChartProps,
ChangePointEmbeddableRuntimeState,
'metricField' | 'splitField' | 'fn' | 'maxSeriesToPlot' | 'partitions'
>;

View file

@ -0,0 +1,258 @@
/*
* 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 {
CHANGE_POINT_CHART_DATA_VIEW_REF_NAME,
EMBEDDABLE_CHANGE_POINT_CHART_TYPE,
} from '@kbn/aiops-change-point-detection/constants';
import type { Reference } from '@kbn/content-management-utils';
import type { StartServicesAccessor } from '@kbn/core-lifecycle-browser';
import { type DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/common';
import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common';
import type { ReactEmbeddableFactory } from '@kbn/embeddable-plugin/public';
import { i18n } from '@kbn/i18n';
import {
apiHasExecutionContext,
fetch$,
initializeTimeRange,
initializeTitles,
useBatchedPublishingSubjects,
} from '@kbn/presentation-publishing';
import fastIsEqual from 'fast-deep-equal';
import { cloneDeep } from 'lodash';
import React, { useMemo } from 'react';
import useObservable from 'react-use/lib/useObservable';
import { BehaviorSubject, distinctUntilChanged, map, skipWhile } from 'rxjs';
import { getChangePointDetectionComponent } from '../../shared_components';
import type { AiopsPluginStart, AiopsPluginStartDeps } from '../../types';
import { initializeChangePointControls } from './initialize_change_point_controls';
import type {
ChangePointEmbeddableApi,
ChangePointEmbeddableRuntimeState,
ChangePointEmbeddableState,
} from './types';
export interface EmbeddableChangePointChartStartServices {
data: DataPublicPluginStart;
}
export type EmbeddableChangePointChartType = typeof EMBEDDABLE_CHANGE_POINT_CHART_TYPE;
export const getDependencies = async (
getStartServices: StartServicesAccessor<AiopsPluginStartDeps, AiopsPluginStart>
) => {
const [
{ http, uiSettings, notifications, ...startServices },
{ lens, data, usageCollection, fieldFormats },
] = await getStartServices();
return {
http,
uiSettings,
data,
notifications,
lens,
usageCollection,
fieldFormats,
...startServices,
};
};
export const getChangePointChartEmbeddableFactory = (
getStartServices: StartServicesAccessor<AiopsPluginStartDeps, AiopsPluginStart>
) => {
const factory: ReactEmbeddableFactory<
ChangePointEmbeddableState,
ChangePointEmbeddableApi,
ChangePointEmbeddableRuntimeState
> = {
type: EMBEDDABLE_CHANGE_POINT_CHART_TYPE,
deserializeState: (state) => {
const serializedState = cloneDeep(state.rawState);
// inject the reference
const dataViewIdRef = state.references?.find(
(ref) => ref.name === CHANGE_POINT_CHART_DATA_VIEW_REF_NAME
);
// if the serializedState already contains a dataViewId, we don't want to overwrite it. (Unsaved state can cause this)
if (dataViewIdRef && serializedState && !serializedState.dataViewId) {
serializedState.dataViewId = dataViewIdRef?.id;
}
return serializedState;
},
buildEmbeddable: async (state, buildApi, uuid, parentApi) => {
const [coreStart, pluginStart] = await getStartServices();
const { http, uiSettings, notifications, ...startServices } = coreStart;
const { lens, data, usageCollection, fieldFormats } = pluginStart;
const deps = {
http,
uiSettings,
data,
notifications,
lens,
usageCollection,
fieldFormats,
...startServices,
};
const {
api: timeRangeApi,
comparators: timeRangeComparators,
serialize: serializeTimeRange,
} = initializeTimeRange(state);
const { titlesApi, titleComparators, serializeTitles } = initializeTitles(state);
const {
changePointControlsApi,
changePointControlsComparators,
serializeChangePointChartState,
} = initializeChangePointControls(state);
const dataLoading = new BehaviorSubject<boolean | undefined>(true);
const blockingError = new BehaviorSubject<Error | undefined>(undefined);
const dataViews$ = new BehaviorSubject<DataView[] | undefined>([
await deps.data.dataViews.get(state.dataViewId),
]);
const api = buildApi(
{
...timeRangeApi,
...titlesApi,
...changePointControlsApi,
getTypeDisplayName: () =>
i18n.translate('xpack.aiops.changePointDetection.typeDisplayName', {
defaultMessage: 'change point charts',
}),
isEditingEnabled: () => true,
onEdit: async () => {
try {
const { resolveEmbeddableChangePointUserInput } = await import(
'./resolve_change_point_config_input'
);
const result = await resolveEmbeddableChangePointUserInput(
coreStart,
pluginStart,
parentApi,
uuid,
serializeChangePointChartState()
);
changePointControlsApi.updateUserInput(result);
} catch (e) {
return Promise.reject();
}
},
dataLoading,
blockingError,
dataViews: dataViews$,
serializeState: () => {
const dataViewId = changePointControlsApi.dataViewId.getValue();
const references: Reference[] = dataViewId
? [
{
type: DATA_VIEW_SAVED_OBJECT_TYPE,
name: CHANGE_POINT_CHART_DATA_VIEW_REF_NAME,
id: dataViewId,
},
]
: [];
return {
rawState: {
timeRange: undefined,
...serializeTitles(),
...serializeTimeRange(),
...serializeChangePointChartState(),
},
references,
};
},
},
{
...timeRangeComparators,
...titleComparators,
...changePointControlsComparators,
}
);
const ChangePointDetectionComponent = getChangePointDetectionComponent(
coreStart,
pluginStart
);
return {
api,
Component: () => {
if (!apiHasExecutionContext(parentApi)) {
throw new Error('Parent API does not have execution context');
}
const [dataViewId, viewType, fn, metricField, splitField, maxSeriesToPlot, partitions] =
useBatchedPublishingSubjects(
api.dataViewId,
api.viewType,
api.fn,
api.metricField,
api.splitField,
api.maxSeriesToPlot,
api.partitions
);
const reload$ = useMemo(
() =>
fetch$(api).pipe(
skipWhile((fetchContext) => !fetchContext.isReload),
map((fetchContext) => Date.now())
),
[]
);
const timeRange$ = useMemo(
() =>
fetch$(api).pipe(
map((fetchContext) => fetchContext.timeRange),
distinctUntilChanged(fastIsEqual)
),
[]
);
const lastReloadRequestTime = useObservable(reload$, Date.now());
const timeRange = useObservable(timeRange$, undefined);
let embeddingOrigin;
if (apiHasExecutionContext(parentApi)) {
embeddingOrigin = parentApi.executionContext.type;
}
return (
<ChangePointDetectionComponent
viewType={viewType}
timeRange={timeRange}
fn={fn}
metricField={metricField}
splitField={splitField}
maxSeriesToPlot={maxSeriesToPlot}
dataViewId={dataViewId}
partitions={partitions}
onLoading={(v) => dataLoading.next(v)}
onRenderComplete={() => dataLoading.next(false)}
onError={(error) => blockingError.next(error)}
embeddingOrigin={embeddingOrigin}
lastReloadRequestTime={lastReloadRequestTime}
/>
);
},
};
},
};
return factory;
};

View file

@ -5,109 +5,29 @@
* 2.0.
*/
import { BehaviorSubject, combineLatest, type Observable } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs';
import type { FC } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import React, { useEffect, useMemo } from 'react';
import { css } from '@emotion/react';
import useObservable from 'react-use/lib/useObservable';
import { CHANGE_POINT_DETECTION_VIEW_TYPE } from '@kbn/aiops-change-point-detection/constants';
import { ChangePointsTable } from '../components/change_point_detection/change_points_table';
import { ReloadContextProvider } from '../hooks/use_reload';
import type { ChangePointDetectionProps } from '../../shared_components/change_point_detection';
import { ChangePointsTable } from '../../components/change_point_detection/change_points_table';
import {
type ChangePointAnnotation,
ChangePointDetectionControlsContextProvider,
type ChangePointDetectionRequestParams,
} from '../components/change_point_detection/change_point_detection_context';
import type {
EmbeddableChangePointChartInput,
EmbeddableChangePointChartOutput,
} from './embeddable_change_point_chart';
import type { EmbeddableChangePointChartProps } from './embeddable_change_point_chart_component';
import { FilterQueryContextProvider, useFilterQueryUpdates } from '../hooks/use_filters_query';
import { DataSourceContextProvider, useDataSource } from '../hooks/use_data_source';
import { useAiopsAppContext } from '../hooks/use_aiops_app_context';
import { createMergedEsQuery } from '../application/utils/search_utils';
import { useChangePointResults } from '../components/change_point_detection/use_change_point_agg_request';
import { ChartsGrid } from '../components/change_point_detection/charts_grid';
import { NoChangePointsWarning } from '../components/change_point_detection/no_change_points_warning';
} from '../../components/change_point_detection/change_point_detection_context';
import { useFilterQueryUpdates } from '../../hooks/use_filters_query';
import { useDataSource } from '../../hooks/use_data_source';
import { useAiopsAppContext } from '../../hooks/use_aiops_app_context';
import { createMergedEsQuery } from '../../application/utils/search_utils';
import { useChangePointResults } from '../../components/change_point_detection/use_change_point_agg_request';
import { ChartsGrid } from '../../components/change_point_detection/charts_grid';
import { NoChangePointsWarning } from '../../components/change_point_detection/no_change_points_warning';
const defaultSort = {
field: 'p_value' as keyof ChangePointAnnotation,
direction: 'asc',
};
export interface EmbeddableInputTrackerProps {
input$: Observable<EmbeddableChangePointChartInput>;
initialInput: EmbeddableChangePointChartInput;
reload$: Observable<number>;
onOutputChange: (output: Partial<EmbeddableChangePointChartOutput>) => void;
onRenderComplete: () => void;
onLoading: (isLoading: boolean) => void;
onError: (error: Error) => void;
}
export const EmbeddableInputTracker: FC<EmbeddableInputTrackerProps> = ({
input$,
initialInput,
reload$,
onOutputChange,
onRenderComplete,
onLoading,
onError,
}) => {
const input = useObservable(input$, initialInput);
const [manualReload$] = useState<BehaviorSubject<number>>(
new BehaviorSubject<number>(initialInput.lastReloadRequestTime ?? Date.now())
);
useEffect(
function updateManualReloadSubject() {
if (
input.lastReloadRequestTime === initialInput.lastReloadRequestTime ||
!input.lastReloadRequestTime
)
return;
manualReload$.next(input.lastReloadRequestTime);
},
[input.lastReloadRequestTime, initialInput.lastReloadRequestTime, manualReload$]
);
const resultObservable$ = useMemo<Observable<number>>(() => {
return combineLatest([reload$, manualReload$]).pipe(
map(([reload, manualReload]) => Math.max(reload, manualReload)),
distinctUntilChanged()
);
}, [manualReload$, reload$]);
return (
<ReloadContextProvider reload$={resultObservable$}>
<DataSourceContextProvider dataViewId={input.dataViewId}>
<FilterQueryContextProvider timeRange={input.timeRange}>
<ChangePointDetectionControlsContextProvider>
<ChartGridEmbeddableWrapper
viewType={input.viewType}
timeRange={input.timeRange}
fn={input.fn}
metricField={input.metricField}
splitField={input.splitField}
maxSeriesToPlot={input.maxSeriesToPlot}
dataViewId={input.dataViewId}
partitions={input.partitions}
onLoading={onLoading}
onRenderComplete={onRenderComplete}
onError={onError}
onChange={input.onChange}
emptyState={input.emptyState}
/>
</ChangePointDetectionControlsContextProvider>
</FilterQueryContextProvider>
</DataSourceContextProvider>
</ReloadContextProvider>
);
};
/**
* Grid component wrapper for embeddable.
*
@ -119,13 +39,7 @@ export const EmbeddableInputTracker: FC<EmbeddableInputTrackerProps> = ({
* @param partitions
* @constructor
*/
export const ChartGridEmbeddableWrapper: FC<
EmbeddableChangePointChartProps & {
onRenderComplete: () => void;
onLoading: (isLoading: boolean) => void;
onError: (error: Error) => void;
}
> = ({
export const ChartGridEmbeddableWrapper: FC<ChangePointDetectionProps> = ({
viewType = CHANGE_POINT_DETECTION_VIEW_TYPE.CHARTS,
fn,
metricField,

View file

@ -5,5 +5,4 @@
* 2.0.
*/
export { EmbeddableChangePointChartFactory } from './embeddable_change_point_chart_factory';
export { type EmbeddableChangePointChartProps } from './embeddable_change_point_chart_component';
export { getChangePointChartEmbeddableFactory } from './embeddable_change_point_chart_factory';

View file

@ -0,0 +1,74 @@
/*
* 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 { StateComparators } from '@kbn/presentation-publishing';
import fastIsEqual from 'fast-deep-equal';
import { BehaviorSubject } from 'rxjs';
import type { ChangePointDetectionViewType } from '@kbn/aiops-change-point-detection/constants';
import type { ChangePointComponentApi, ChangePointEmbeddableState } from './types';
type ChangePointEmbeddableCustomState = Omit<
ChangePointEmbeddableState,
'timeRange' | 'title' | 'description' | 'hidePanelTitles'
>;
export const initializeChangePointControls = (rawState: ChangePointEmbeddableState) => {
const viewType = new BehaviorSubject<ChangePointDetectionViewType>(rawState.viewType);
const dataViewId = new BehaviorSubject<string>(rawState.dataViewId);
const fn = new BehaviorSubject(rawState.fn);
const metricField = new BehaviorSubject(rawState.metricField);
const splitField = new BehaviorSubject(rawState.splitField);
const partitions = new BehaviorSubject(rawState.partitions);
const maxSeriesToPlot = new BehaviorSubject(rawState.maxSeriesToPlot);
const updateUserInput = (update: ChangePointEmbeddableCustomState) => {
viewType.next(update.viewType);
dataViewId.next(update.dataViewId);
fn.next(update.fn);
metricField.next(update.metricField);
splitField.next(update.splitField);
partitions.next(update.partitions);
maxSeriesToPlot.next(update.maxSeriesToPlot);
};
const serializeChangePointChartState = (): ChangePointEmbeddableCustomState => {
return {
viewType: viewType.getValue(),
dataViewId: dataViewId.getValue(),
fn: fn.getValue(),
metricField: metricField.getValue(),
splitField: splitField.getValue(),
partitions: partitions.getValue(),
maxSeriesToPlot: maxSeriesToPlot.getValue(),
};
};
const changePointControlsComparators: StateComparators<ChangePointEmbeddableCustomState> = {
viewType: [viewType, (arg) => viewType.next(arg)],
dataViewId: [dataViewId, (arg) => dataViewId.next(arg)],
fn: [fn, (arg) => fn.next(arg)],
metricField: [metricField, (arg) => metricField.next(arg)],
splitField: [splitField, (arg) => splitField.next(arg)],
partitions: [partitions, (arg) => partitions.next(arg), fastIsEqual],
maxSeriesToPlot: [maxSeriesToPlot, (arg) => maxSeriesToPlot.next(arg)],
};
return {
changePointControlsApi: {
viewType,
dataViewId,
fn,
metricField,
splitField,
partitions,
maxSeriesToPlot,
updateUserInput,
} as unknown as ChangePointComponentApi,
serializeChangePointChartState,
changePointControlsComparators,
};
};

View file

@ -6,25 +6,29 @@
*/
import type { CoreStart } from '@kbn/core/public';
import { tracksOverlays } from '@kbn/presentation-containers';
import { toMountPoint } from '@kbn/react-kibana-mount';
import React from 'react';
import type { EmbeddableChangePointChartExplicitInput } from './types';
import type { AiopsAppDependencies } from '..';
import { AiopsAppContext } from '../hooks/use_aiops_app_context';
import type { AiopsPluginStartDeps } from '../types';
import type { AiopsAppDependencies } from '../..';
import { AiopsAppContext } from '../../hooks/use_aiops_app_context';
import type { AiopsPluginStartDeps } from '../../types';
import { ChangePointChartInitializer } from './change_point_chart_initializer';
import type { EmbeddableChangePointChartInput } from './embeddable_change_point_chart';
import type { ChangePointEmbeddableState } from './types';
export async function resolveEmbeddableChangePointUserInput(
coreStart: CoreStart,
pluginStart: AiopsPluginStartDeps,
input?: EmbeddableChangePointChartInput
): Promise<EmbeddableChangePointChartExplicitInput> {
parentApi: unknown,
focusedPanelId: string,
input?: ChangePointEmbeddableState
): Promise<ChangePointEmbeddableState> {
const { overlays } = coreStart;
const overlayTracker = tracksOverlays(parentApi) ? parentApi : undefined;
return new Promise(async (resolve, reject) => {
try {
const modalSession = overlays.openModal(
const flyoutSession = overlays.openFlyout(
toMountPoint(
<AiopsAppContext.Provider
value={
@ -36,19 +40,37 @@ export async function resolveEmbeddableChangePointUserInput(
>
<ChangePointChartInitializer
initialInput={input}
onCreate={(update: EmbeddableChangePointChartExplicitInput) => {
modalSession.close();
onCreate={(update) => {
resolve(update);
flyoutSession.close();
overlayTracker?.clearOverlays();
}}
onCancel={() => {
modalSession.close();
reject();
flyoutSession.close();
overlayTracker?.clearOverlays();
}}
/>
</AiopsAppContext.Provider>,
coreStart
)
),
{
ownFocus: true,
size: 's',
type: 'push',
'data-test-subj': 'aiopsChangePointChartEmbeddableInitializer',
'aria-labelledby': 'changePointConfig',
onClose: () => {
reject();
flyoutSession.close();
overlayTracker?.clearOverlays();
},
}
);
if (tracksOverlays(parentApi)) {
parentApi.openOverlay(flyoutSession, { focusedPanelId });
}
} catch (error) {
reject(error);
}

View file

@ -0,0 +1,54 @@
/*
* 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 { ChangePointDetectionViewType } from '@kbn/aiops-change-point-detection/constants';
import type { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public';
import type {
HasEditCapabilities,
PublishesDataViews,
PublishesTimeRange,
PublishingSubject,
SerializedTimeRange,
SerializedTitles,
} from '@kbn/presentation-publishing';
import type { FC } from 'react';
import type { SelectedChangePoint } from '../../components/change_point_detection/change_point_detection_context';
export type ViewComponent = FC<{
changePoints: SelectedChangePoint[];
interval: string;
onRenderComplete?: () => void;
}>;
export interface ChangePointComponentApi {
viewType: PublishingSubject<ChangePointEmbeddableState['viewType']>;
dataViewId: PublishingSubject<ChangePointEmbeddableState['dataViewId']>;
fn: PublishingSubject<ChangePointEmbeddableState['fn']>;
metricField: PublishingSubject<ChangePointEmbeddableState['metricField']>;
splitField: PublishingSubject<ChangePointEmbeddableState['splitField']>;
partitions: PublishingSubject<ChangePointEmbeddableState['partitions']>;
maxSeriesToPlot: PublishingSubject<ChangePointEmbeddableState['maxSeriesToPlot']>;
updateUserInput: (update: ChangePointEmbeddableState) => void;
}
export type ChangePointEmbeddableApi = DefaultEmbeddableApi<ChangePointEmbeddableState> &
HasEditCapabilities &
PublishesDataViews &
PublishesTimeRange &
ChangePointComponentApi;
export interface ChangePointEmbeddableState extends SerializedTitles, SerializedTimeRange {
viewType: ChangePointDetectionViewType;
dataViewId: string;
fn: 'avg' | 'sum' | 'min' | 'max' | string;
metricField: string;
splitField?: string;
partitions?: string[];
maxSeriesToPlot?: number;
}
export type ChangePointEmbeddableRuntimeState = ChangePointEmbeddableState;

View file

@ -0,0 +1,21 @@
/*
* 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 { CoreSetup } from '@kbn/core-lifecycle-browser';
import type { EmbeddableSetup } from '@kbn/embeddable-plugin/public';
import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '@kbn/aiops-change-point-detection/constants';
import type { AiopsPluginStart, AiopsPluginStartDeps } from '../types';
export const registerEmbeddables = (
embeddable: EmbeddableSetup,
core: CoreSetup<AiopsPluginStartDeps, AiopsPluginStart>
) => {
embeddable.registerReactEmbeddableFactory(EMBEDDABLE_CHANGE_POINT_CHART_TYPE, async () => {
const { getChangePointChartEmbeddableFactory } = await import('./change_point_chart');
return getChangePointChartEmbeddableFactory(core.getStartServices);
});
};

View file

@ -8,8 +8,8 @@
import { useCallback } from 'react';
import { stringHash } from '@kbn/ml-string-hash';
import { AttachmentType } from '@kbn/cases-plugin/common';
import type { EmbeddableChangePointChartInput } from '../embeddable/embeddable_change_point_chart';
import type { EmbeddableChangePointChartType } from '../embeddable/embeddable_change_point_chart_factory';
import type { ChangePointEmbeddableRuntimeState } from '../embeddables/change_point_chart/types';
import type { EmbeddableChangePointChartType } from '../embeddables/change_point_chart/embeddable_change_point_chart_factory';
import { useAiopsAppContext } from './use_aiops_app_context';
/**
@ -23,7 +23,7 @@ export const useCasesModal = <EmbeddableType extends EmbeddableChangePointChartT
const selectCaseModal = cases?.hooks.useCasesAddToExistingCaseModal();
return useCallback(
(persistableState: Partial<Omit<EmbeddableChangePointChartInput, 'id'>>) => {
(persistableState: Partial<Omit<ChangePointEmbeddableRuntimeState, 'id'>>) => {
const persistableStateAttachmentState = {
...persistableState,
// Creates unique id based on the input

View file

@ -8,14 +8,13 @@
import type { CoreStart, Plugin } from '@kbn/core/public';
import { type CoreSetup } from '@kbn/core/public';
import { firstValueFrom } from 'rxjs';
import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '@kbn/aiops-change-point-detection/constants';
import { getChangePointDetectionComponent } from './shared_components';
import type {
AiopsPluginSetup,
AiopsPluginSetupDeps,
AiopsPluginStart,
AiopsPluginStartDeps,
} from './types';
import { getEmbeddableChangePointChart } from './embeddable/embeddable_change_point_chart_component';
export type AiopsCoreSetup = CoreSetup<AiopsPluginStartDeps, AiopsPluginStart>;
@ -28,21 +27,21 @@ export class AiopsPlugin
) {
Promise.all([
firstValueFrom(licensing.license$),
import('./embeddable/register_embeddable'),
import('./embeddables'),
import('./ui_actions'),
import('./cases/register_change_point_charts_attachment'),
core.getStartServices(),
]).then(
([
license,
{ registerEmbeddable },
{ registerEmbeddables },
{ registerAiopsUiActions },
{ registerChangePointChartsAttachment },
[coreStart, pluginStart],
]) => {
if (license.hasAtLeast('platinum')) {
if (embeddable) {
registerEmbeddable(core, embeddable);
registerEmbeddables(embeddable, core);
}
if (uiActions) {
@ -59,11 +58,7 @@ export class AiopsPlugin
public start(core: CoreStart, plugins: AiopsPluginStartDeps): AiopsPluginStart {
return {
EmbeddableChangePointChart: getEmbeddableChangePointChart(
EMBEDDABLE_CHANGE_POINT_CHART_TYPE,
core,
plugins
),
ChangePointDetectionComponent: getChangePointDetectionComponent(core, plugins),
};
}

View file

@ -0,0 +1,175 @@
/*
* 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 { css } from '@emotion/react';
import type { ChangePointDetectionViewType } from '@kbn/aiops-change-point-detection/constants';
import { EMBEDDABLE_ORIGIN } from '@kbn/aiops-common/constants';
import type { CoreStart } from '@kbn/core-lifecycle-browser';
import { UI_SETTINGS } from '@kbn/data-service';
import type { TimeRange } from '@kbn/es-query';
import { DatePickerContextProvider } from '@kbn/ml-date-picker';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { pick } from 'lodash';
import React, { useEffect, useMemo, useState, type FC } from 'react';
import type { Observable } from 'rxjs';
import { BehaviorSubject, combineLatest, distinctUntilChanged, map } from 'rxjs';
import {
ChangePointDetectionControlsContextProvider,
type ChangePointAnnotation,
} from '../components/change_point_detection/change_point_detection_context';
import { ChartGridEmbeddableWrapper } from '../embeddables/change_point_chart/embeddable_chart_component_wrapper';
import { AiopsAppContext, type AiopsAppDependencies } from '../hooks/use_aiops_app_context';
import { DataSourceContextProvider } from '../hooks/use_data_source';
import { FilterQueryContextProvider } from '../hooks/use_filters_query';
import { ReloadContextProvider } from '../hooks/use_reload';
import type { AiopsPluginStartDeps } from '../types';
/**
* Only used to initialize internally
*/
export type ChangePointDetectionPropsWithDeps = ChangePointDetectionProps & {
coreStart: CoreStart;
pluginStart: AiopsPluginStartDeps;
};
export type ChangePointDetectionSharedComponent = FC<ChangePointDetectionProps>;
export interface ChangePointDetectionProps {
viewType?: ChangePointDetectionViewType;
dataViewId: string;
timeRange: TimeRange;
fn: 'avg' | 'sum' | 'min' | 'max' | string;
metricField: string;
splitField?: string;
partitions?: string[];
maxSeriesToPlot?: number;
/**
* Component to render if there are no change points found
*/
emptyState?: React.ReactElement;
/**
* Outputs the most recent change point data
*/
onChange?: (changePointData: ChangePointAnnotation[]) => void;
/**
* Last reload request time, can be used for manual reload
*/
lastReloadRequestTime?: number;
/** Origin of the embeddable instance */
embeddingOrigin?: string;
onLoading: (isLoading: boolean) => void;
onRenderComplete: () => void;
onError: (error: Error) => void;
}
const ChangePointDetectionWrapper: FC<ChangePointDetectionPropsWithDeps> = ({
// Component dependencies
coreStart,
pluginStart,
// Component props
viewType,
dataViewId,
fn,
metricField,
splitField,
partitions,
maxSeriesToPlot,
timeRange,
onLoading,
onError,
onRenderComplete,
embeddingOrigin,
lastReloadRequestTime,
}) => {
const deps = useMemo(() => {
const { http, uiSettings, notifications, ...startServices } = coreStart;
const { lens, data, usageCollection, fieldFormats } = pluginStart;
return {
http,
uiSettings,
data,
notifications,
lens,
usageCollection,
fieldFormats,
...startServices,
};
}, [coreStart, pluginStart]);
const datePickerDeps = {
...pick(deps, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']),
uiSettingsKeys: UI_SETTINGS,
};
const aiopsAppContextValue = useMemo<AiopsAppDependencies>(() => {
return {
embeddingOrigin: embeddingOrigin ?? EMBEDDABLE_ORIGIN,
...deps,
} as unknown as AiopsAppDependencies;
}, [deps, embeddingOrigin]);
const [manualReload$] = useState<BehaviorSubject<number>>(
new BehaviorSubject<number>(lastReloadRequestTime ?? Date.now())
);
useEffect(
function updateManualReloadSubject() {
if (!lastReloadRequestTime) return;
manualReload$.next(lastReloadRequestTime);
},
[lastReloadRequestTime, manualReload$]
);
const resultObservable$ = useMemo<Observable<number>>(() => {
return combineLatest([manualReload$]).pipe(
map(([manualReload]) => Math.max(manualReload)),
distinctUntilChanged()
);
}, [manualReload$]);
// TODO: Remove data-shared-item as part of https://github.com/elastic/kibana/issues/179376>
return (
<div
data-shared-item=""
data-test-subj="aiopsEmbeddableChangePointChart"
css={css`
width: 100%;
`}
>
<KibanaRenderContextProvider {...coreStart}>
<AiopsAppContext.Provider value={aiopsAppContextValue}>
<DatePickerContextProvider {...datePickerDeps}>
<ReloadContextProvider reload$={resultObservable$}>
<DataSourceContextProvider dataViewId={dataViewId}>
<FilterQueryContextProvider timeRange={timeRange}>
<ChangePointDetectionControlsContextProvider>
<ChartGridEmbeddableWrapper
viewType={viewType}
timeRange={timeRange}
fn={fn}
metricField={metricField}
splitField={splitField}
maxSeriesToPlot={maxSeriesToPlot}
dataViewId={dataViewId}
partitions={partitions}
onLoading={onLoading}
onRenderComplete={onRenderComplete}
onError={onError}
/>
</ChangePointDetectionControlsContextProvider>
</FilterQueryContextProvider>
</DataSourceContextProvider>
</ReloadContextProvider>
</DatePickerContextProvider>
</AiopsAppContext.Provider>
</KibanaRenderContextProvider>
</div>
);
};
// eslint-disable-next-line import/no-default-export
export default ChangePointDetectionWrapper;

View file

@ -0,0 +1,25 @@
/*
* 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 from 'react';
import { dynamic } from '@kbn/shared-ux-utility';
import type { CoreStart } from '@kbn/core-lifecycle-browser';
import type { AiopsPluginStartDeps } from '../types';
import type { ChangePointDetectionSharedComponent } from './change_point_detection';
const ChangePointDetectionLazy = dynamic(async () => import('./change_point_detection'));
export const getChangePointDetectionComponent = (
coreStart: CoreStart,
pluginStart: AiopsPluginStartDeps
): ChangePointDetectionSharedComponent => {
return (props) => {
return <ChangePointDetectionLazy coreStart={coreStart} pluginStart={pluginStart} {...props} />;
};
};
export type { ChangePointDetectionSharedComponent } from './change_point_detection';

View file

@ -18,7 +18,7 @@ import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/
import type { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public';
import type { CasesPublicSetup } from '@kbn/cases-plugin/public';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import type { EmbeddableChangePointChartInput } from './embeddable/embeddable_change_point_chart';
import type { ChangePointDetectionSharedComponent } from './shared_components';
export interface AiopsPluginSetupDeps {
embeddable: EmbeddableSetup;
@ -45,5 +45,5 @@ export interface AiopsPluginStartDeps {
export type AiopsPluginSetup = void;
export interface AiopsPluginStart {
EmbeddableChangePointChart: React.ComponentType<EmbeddableChangePointChartInput>;
ChangePointDetectionComponent: ChangePointDetectionSharedComponent;
}

View file

@ -0,0 +1,24 @@
/*
* 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 { isPopulatedObject } from '@kbn/ml-is-populated-object';
import { apiIsOfType, type EmbeddableApiContext } from '@kbn/presentation-publishing';
import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '@kbn/aiops-change-point-detection/constants';
import type { ChangePointEmbeddableApi } from '../embeddables/change_point_chart/types';
export interface ChangePointChartActionContext extends EmbeddableApiContext {
embeddable: ChangePointEmbeddableApi;
}
export function isChangePointChartEmbeddableContext(
arg: unknown
): arg is ChangePointChartActionContext {
return (
isPopulatedObject(arg, ['embeddable']) &&
apiIsOfType(arg.embeddable, EMBEDDABLE_CHANGE_POINT_CHART_TYPE)
);
}

View file

@ -0,0 +1,74 @@
/*
* 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 { PresentationContainer } from '@kbn/presentation-containers';
import type { EmbeddableApiContext } from '@kbn/presentation-publishing';
import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '@kbn/aiops-change-point-detection/constants';
import type { CoreStart } from '@kbn/core-lifecycle-browser';
import type { AiopsPluginStartDeps } from '../types';
import type { ChangePointChartActionContext } from './change_point_action_context';
const parentApiIsCompatible = async (
parentApi: unknown
): Promise<PresentationContainer | undefined> => {
const { apiIsPresentationContainer } = await import('@kbn/presentation-containers');
// we cannot have an async type check, so return the casted parentApi rather than a boolean
return apiIsPresentationContainer(parentApi) ? (parentApi as PresentationContainer) : undefined;
};
export function createAddChangePointChartAction(
coreStart: CoreStart,
pluginStart: AiopsPluginStartDeps
): UiActionsActionDefinition<ChangePointChartActionContext> {
return {
id: 'create-change-point-chart',
grouping: [
{
id: 'ml',
getDisplayName: () =>
i18n.translate('xpack.aiops.navMenu.mlAppNameText', {
defaultMessage: 'Machine Learning',
}),
getIconType: () => 'machineLearningApp',
},
],
getDisplayName: () =>
i18n.translate('xpack.aiops.embeddableChangePointChartDisplayName', {
defaultMessage: 'Change point detection',
}),
async isCompatible(context: EmbeddableApiContext) {
return Boolean(await parentApiIsCompatible(context.embeddable));
},
async execute(context) {
const presentationContainerParent = await parentApiIsCompatible(context.embeddable);
if (!presentationContainerParent) throw new IncompatibleActionError();
try {
const { resolveEmbeddableChangePointUserInput } = await import(
'../embeddables/change_point_chart/resolve_change_point_config_input'
);
const initialState = await resolveEmbeddableChangePointUserInput(
coreStart,
pluginStart,
context.embeddable,
context.embeddable.uuid
);
presentationContainerParent.addNewPanel({
panelType: EMBEDDABLE_CHANGE_POINT_CHART_TYPE,
initialState,
});
} catch (e) {
return Promise.reject();
}
},
};
}

View file

@ -1,59 +0,0 @@
/*
* 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 { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
import { i18n } from '@kbn/i18n';
import { ViewMode } from '@kbn/embeddable-plugin/common';
import type { CoreStart } from '@kbn/core/public';
import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '@kbn/aiops-change-point-detection/constants';
import type { EditChangePointChartsPanelContext } from '../embeddable/types';
import type { AiopsPluginStartDeps } from '../types';
export const EDIT_CHANGE_POINT_CHARTS_ACTION = 'editChangePointChartsPanelAction';
export function createEditChangePointChartsPanelAction(
coreStart: CoreStart,
pluginStart: AiopsPluginStartDeps
): UiActionsActionDefinition<EditChangePointChartsPanelContext> {
return {
id: 'edit-change-point-charts',
type: EDIT_CHANGE_POINT_CHARTS_ACTION,
getIconType(context): string {
return 'pencil';
},
getDisplayName: () =>
i18n.translate('xpack.aiops.actions.editChangePointChartsName', {
defaultMessage: 'Edit change point charts',
}),
async execute({ embeddable }) {
if (!embeddable) {
throw new Error('Not possible to execute an action without the embeddable context');
}
try {
const { resolveEmbeddableChangePointUserInput } = await import(
'../embeddable/handle_explicit_input'
);
const result = await resolveEmbeddableChangePointUserInput(
coreStart,
pluginStart,
embeddable.getInput()
);
embeddable.updateInput(result);
} catch (e) {
return Promise.reject();
}
},
async isCompatible({ embeddable }) {
return (
embeddable.type === EMBEDDABLE_CHANGE_POINT_CHART_TYPE &&
embeddable.getInput().viewMode === ViewMode.EDIT
);
},
};
}

View file

@ -13,9 +13,9 @@ import {
} from '@kbn/ml-ui-actions/src/aiops/ui_actions';
import type { CoreStart } from '@kbn/core/public';
import { createAddChangePointChartAction } from './create_change_point_chart';
import { createOpenChangePointInMlAppAction } from './open_change_point_ml';
import type { AiopsPluginStartDeps } from '../types';
import { createEditChangePointChartsPanelAction } from './edit_change_point_charts_panel';
import { createCategorizeFieldAction } from '../components/log_categorization';
export function registerAiopsUiActions(
@ -23,15 +23,10 @@ export function registerAiopsUiActions(
coreStart: CoreStart,
pluginStart: AiopsPluginStartDeps
) {
// Initialize actions
const editChangePointChartPanelAction = createEditChangePointChartsPanelAction(
coreStart,
pluginStart
);
const openChangePointInMlAppAction = createOpenChangePointInMlAppAction(coreStart, pluginStart);
const addChangePointChartAction = createAddChangePointChartAction(coreStart, pluginStart);
// // Register actions and triggers
uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, editChangePointChartPanelAction);
uiActions.addTriggerAction('ADD_PANEL_TRIGGER', addChangePointChartAction);
uiActions.registerTrigger(categorizeFieldTrigger);

View file

@ -6,18 +6,34 @@
*/
import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
import { i18n } from '@kbn/i18n';
import type { CoreStart } from '@kbn/core/public';
import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '@kbn/aiops-change-point-detection/constants';
import type { TimeRange } from '@kbn/es-query';
import { apiHasParentApi, apiPublishesTimeRange } from '@kbn/presentation-publishing';
import type { ChangePointEmbeddableApi } from '../embeddables/change_point_chart/types';
import type { AiopsPluginStartDeps } from '../types';
import type { EditChangePointChartsPanelContext } from '../embeddable/types';
import type { ChangePointChartActionContext } from './change_point_action_context';
import { isChangePointChartEmbeddableContext } from './change_point_action_context';
export const OPEN_CHANGE_POINT_IN_ML_APP_ACTION = 'openChangePointInMlAppAction';
export const getEmbeddableTimeRange = (
embeddable: ChangePointEmbeddableApi
): TimeRange | undefined => {
let timeRange = embeddable.timeRange$?.getValue();
if (!timeRange && apiHasParentApi(embeddable) && apiPublishesTimeRange(embeddable.parentApi)) {
timeRange = embeddable.parentApi.timeRange$.getValue();
}
return timeRange;
};
export function createOpenChangePointInMlAppAction(
coreStart: CoreStart,
pluginStart: AiopsPluginStartDeps
): UiActionsActionDefinition<EditChangePointChartsPanelContext> {
): UiActionsActionDefinition<ChangePointChartActionContext> {
return {
id: 'open-change-point-in-ml-app',
type: OPEN_CHANGE_POINT_IN_ML_APP_ACTION,
@ -29,30 +45,40 @@ export function createOpenChangePointInMlAppAction(
defaultMessage: 'Open in AIOps Labs',
}),
async getHref(context): Promise<string | undefined> {
if (!isChangePointChartEmbeddableContext(context)) {
throw new IncompatibleActionError();
}
const locator = pluginStart.share.url.locators.get('ML_APP_LOCATOR')!;
const { timeRange, metricField, fn, splitField, dataViewId } = context.embeddable.getInput();
const { metricField, fn, splitField, dataViewId } = context.embeddable;
return locator.getUrl({
page: 'aiops/change_point_detection',
pageState: {
index: dataViewId,
timeRange,
fieldConfigs: [{ fn, metricField, ...(splitField ? { splitField } : {}) }],
index: dataViewId.getValue(),
timeRange: getEmbeddableTimeRange(context.embeddable),
fieldConfigs: [
{
fn: fn.getValue(),
metricField: metricField.getValue(),
...(splitField.getValue() ? { splitField: splitField.getValue() } : {}),
},
],
},
});
},
async execute(context) {
if (!context.embeddable) {
throw new Error('Not possible to execute an action without the embeddable context');
if (!isChangePointChartEmbeddableContext(context)) {
throw new IncompatibleActionError();
}
const aiopsChangePointUrl = await this.getHref!(context);
if (aiopsChangePointUrl) {
await coreStart.application.navigateToUrl(aiopsChangePointUrl!);
}
},
async isCompatible({ embeddable }) {
return embeddable.type === EMBEDDABLE_CHANGE_POINT_CHART_TYPE;
async isCompatible(context) {
return isChangePointChartEmbeddableContext(context);
},
};
}

View file

@ -70,7 +70,12 @@
"@kbn/aiops-change-point-detection",
"@kbn/react-kibana-context-theme",
"@kbn/react-kibana-context-render",
"@kbn/presentation-publishing",
"@kbn/data-service",
"@kbn/shared-ux-utility",
"@kbn/presentation-containers",
"@kbn/search-types",
"@kbn/content-management-utils",
],
"exclude": [
"target/**/*",

View file

@ -152,6 +152,8 @@ export const getAnomalySwimLaneEmbeddableFactory = (
const result = await resolveAnomalySwimlaneUserInput(
{ ...coreStartServices, ...pluginsStartServices },
parentApi,
uuid,
{
...serializeTitles(),
...serializeSwimLaneState(),

View file

@ -5,26 +5,25 @@
* 2.0.
*/
import React from 'react';
import type { CoreStart } from '@kbn/core/public';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { distinctUntilChanged, from, skip, takeUntil } from 'rxjs';
import { tracksOverlays } from '@kbn/presentation-containers';
import { toMountPoint } from '@kbn/react-kibana-mount';
import React from 'react';
import type { AnomalySwimLaneEmbeddableState, AnomalySwimlaneEmbeddableUserInput } from '..';
import { HttpService } from '../../application/services/http_service';
import { jobsApiProvider } from '../../application/services/ml_api_service/jobs';
import { AnomalySwimlaneInitializer } from './anomaly_swimlane_initializer';
import { HttpService } from '../../application/services/http_service';
import type { AnomalySwimLaneEmbeddableState, AnomalySwimlaneEmbeddableUserInput } from '..';
export async function resolveAnomalySwimlaneUserInput(
coreStart: CoreStart,
parentApi: unknown,
focusedPanelId: string,
input?: Partial<AnomalySwimLaneEmbeddableState>
): Promise<AnomalySwimlaneEmbeddableUserInput> {
const {
http,
overlays,
application: { currentAppId$ },
...startServices
} = coreStart;
const { http, overlays, ...startServices } = coreStart;
const overlayTracker = tracksOverlays(parentApi) ? parentApi : undefined;
return new Promise(async (resolve, reject) => {
try {
@ -37,12 +36,14 @@ export async function resolveAnomalySwimlaneUserInput(
adJobsApiService={adJobsApiService}
initialInput={input}
onCreate={(explicitInput) => {
flyoutSession.close();
resolve(explicitInput);
flyoutSession.close();
overlayTracker?.clearOverlays();
}}
onCancel={() => {
flyoutSession.close();
reject();
flyoutSession.close();
overlayTracker?.clearOverlays();
}}
/>
</KibanaContextProvider>,
@ -53,18 +54,18 @@ export async function resolveAnomalySwimlaneUserInput(
ownFocus: true,
size: 's',
onClose: () => {
flyoutSession.close();
reject();
flyoutSession.close();
overlayTracker?.clearOverlays();
},
}
);
// Close the flyout when user navigates out of the current plugin
currentAppId$
.pipe(skip(1), takeUntil(from(flyoutSession.onClose)), distinctUntilChanged())
.subscribe(() => {
flyoutSession.close();
if (tracksOverlays(parentApi)) {
parentApi.openOverlay(flyoutSession, {
focusedPanelId,
});
}
} catch (error) {
reject(error);
}

View file

@ -63,10 +63,14 @@ export function createAddSwimlanePanelAction(
'../embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout'
);
const initialState = await resolveAnomalySwimlaneUserInput({
...coreStart,
...pluginStart,
});
const initialState = await resolveAnomalySwimlaneUserInput(
{
...coreStart,
...pluginStart,
},
context.embeddable,
context.embeddable.uuid
);
presentationContainerParent.addNewPanel({
panelType: ANOMALY_SWIMLANE_EMBEDDABLE_TYPE,

View file

@ -8282,7 +8282,6 @@
"xpack.aiops.searchPanel.sampleProbabilityNumber": "{sampleProbability, plural, other {#}}",
"xpack.aiops.searchPanel.totalDocCountLabel": "Total des documents : {strongTotalCount}",
"xpack.aiops.searchPanel.totalDocCountNumber": "{totalCount, plural, other {#}}",
"xpack.aiops.actions.editChangePointChartsName": "Modifier les graphiques de points de modification",
"xpack.aiops.actions.openChangePointInMlAppName": "Ouvrir dans AIOps Labs",
"xpack.aiops.analysis.analysisTypeDipCallOutContent": "Le taux de log médian pour la plage temporelle d'écart-type sélectionnée est inférieur à la référence de base. Le tableau des résultats de l'analyse présente donc des éléments statistiquement significatifs inclus dans la plage temporelle de base qui sont moins nombreux ou manquant dans la plage temporelle d'écart-type. La colonne \"doc count\" (décompte de documents) renvoie à la quantité de documents dans la plage temporelle de base.",
"xpack.aiops.analysis.analysisTypeDipCallOutContentFallback": "La plage temporelle de déviation ne contient aucun document. Les résultats montrent donc les catégories de message des meilleurs logs et les valeurs des champs pour la période de référence.",
@ -45112,4 +45111,4 @@
"xpack.serverlessObservability.nav.projectSettings": "Paramètres de projet",
"xpack.serverlessObservability.nav.synthetics": "Synthetics"
}
}
}

View file

@ -8270,7 +8270,6 @@
"xpack.aiops.searchPanel.sampleProbabilityNumber": "{sampleProbability, plural, other {#}}",
"xpack.aiops.searchPanel.totalDocCountLabel": "合計ドキュメント数:{strongTotalCount}",
"xpack.aiops.searchPanel.totalDocCountNumber": "{totalCount, plural, other {#}}",
"xpack.aiops.actions.editChangePointChartsName": "変化点チャートを編集",
"xpack.aiops.actions.openChangePointInMlAppName": "AIOps Labsで開く",
"xpack.aiops.analysis.analysisTypeDipCallOutContent": "選択された偏差時間範囲におけるログレートの中央値は、ベースラインよりも低くなります。そのため、分析結果テーブルでは、ベースライン時間範囲内では統計的に有意な項目が、偏差値時間範囲内では数が少ないか欠落していることが示されています。\"ドキュメントカウント\"列は、基準時間範囲のドキュメント量です。",
"xpack.aiops.analysis.analysisTypeDipCallOutContentFallback": "偏差時間範囲にはドキュメントが含まれていません。したがって、この結果は、ベースライン時間範囲の上位のログメッセージカテゴリーとフィールド値を示しています。",
@ -45082,4 +45081,4 @@
"xpack.serverlessObservability.nav.projectSettings": "プロジェクト設定",
"xpack.serverlessObservability.nav.synthetics": "Synthetics"
}
}
}

View file

@ -8285,7 +8285,6 @@
"xpack.aiops.searchPanel.sampleProbabilityNumber": "{sampleProbability, plural, other {#}}",
"xpack.aiops.searchPanel.totalDocCountLabel": "文档总数:{strongTotalCount}",
"xpack.aiops.searchPanel.totalDocCountNumber": "{totalCount, plural, other {#}}",
"xpack.aiops.actions.editChangePointChartsName": "编辑更改点图表",
"xpack.aiops.actions.openChangePointInMlAppName": "在 Aiops 实验室中打开",
"xpack.aiops.analysis.analysisTypeDipCallOutContent": "选定偏差时间范围中的中位日志速率低于基线。因此,分析结果表将显示基线时间范围内数量较少或偏差时间范围内缺失的具有统计意义的项目。“文档计数”列统计基线时间范围内的文档数量。",
"xpack.aiops.analysis.analysisTypeDipCallOutContentFallback": "偏差时间范围不包含任何文档。因此,结果将显示基线时间范围的主要日志消息类别和字段值。",
@ -45130,4 +45129,4 @@
"xpack.serverlessObservability.nav.projectSettings": "项目设置",
"xpack.serverlessObservability.nav.synthetics": "Synthetics"
}
}
}