mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
# Backport This will backport the following commits from `main` to `8.18`: - [[ML] AIOps: Change Point Detection in Dashboards embeddable fix (#217178)](https://github.com/elastic/kibana/pull/217178) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Robert Jaszczurek","email":"92210485+rbrtj@users.noreply.github.com"},"sourceCommit":{"committedDate":"2025-04-07T10:06:30Z","message":"[ML] AIOps: Change Point Detection in Dashboards embeddable fix (#217178)\n\nIt fixes an issue where adding the `Change Point Detection` embeddable\ndidn't work properly.\nThe bug was introduced in https://github.com/elastic/kibana/pull/197943\nThe main cause was the use of `<ChangePointDetectionContextProvider>`\nwhich calls `timefilter.getActiveBounds()`. However, for\n`getActiveBounds` to work, `this.isTimeRangeSelectorEnabled()` must\nreturn true. By default, this is not the case within dashboards.\nHowever, we do not actually need the `ChangePointDetectionContext`\ninside the embeddable, so this PR removes its usage.\nA functional test has been added to cover adding the Change Point\nembeddable from the dashboards app. It's a very simple test that does\nnot verify the embeddable's functionality, but it could be improved in a\nfollow-up.\n\n\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"d35988152d9e95e26908046ea3540d34e47cf92f","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix",":ml","Feature:ML/AIOps","backport:version","v9.1.0","v8.19.0","v8.18.1","v9.0.1","v8.17.5"],"title":"[ML] AIOps: Change Point Detection in Dashboards embeddable fix","number":217178,"url":"https://github.com/elastic/kibana/pull/217178","mergeCommit":{"message":"[ML] AIOps: Change Point Detection in Dashboards embeddable fix (#217178)\n\nIt fixes an issue where adding the `Change Point Detection` embeddable\ndidn't work properly.\nThe bug was introduced in https://github.com/elastic/kibana/pull/197943\nThe main cause was the use of `<ChangePointDetectionContextProvider>`\nwhich calls `timefilter.getActiveBounds()`. However, for\n`getActiveBounds` to work, `this.isTimeRangeSelectorEnabled()` must\nreturn true. By default, this is not the case within dashboards.\nHowever, we do not actually need the `ChangePointDetectionContext`\ninside the embeddable, so this PR removes its usage.\nA functional test has been added to cover adding the Change Point\nembeddable from the dashboards app. It's a very simple test that does\nnot verify the embeddable's functionality, but it could be improved in a\nfollow-up.\n\n\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"d35988152d9e95e26908046ea3540d34e47cf92f"}},"sourceBranch":"main","suggestedTargetBranches":["8.x","8.18","9.0"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/217178","number":217178,"mergeCommit":{"message":"[ML] AIOps: Change Point Detection in Dashboards embeddable fix (#217178)\n\nIt fixes an issue where adding the `Change Point Detection` embeddable\ndidn't work properly.\nThe bug was introduced in https://github.com/elastic/kibana/pull/197943\nThe main cause was the use of `<ChangePointDetectionContextProvider>`\nwhich calls `timefilter.getActiveBounds()`. However, for\n`getActiveBounds` to work, `this.isTimeRangeSelectorEnabled()` must\nreturn true. By default, this is not the case within dashboards.\nHowever, we do not actually need the `ChangePointDetectionContext`\ninside the embeddable, so this PR removes its usage.\nA functional test has been added to cover adding the Change Point\nembeddable from the dashboards app. It's a very simple test that does\nnot verify the embeddable's functionality, but it could be improved in a\nfollow-up.\n\n\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"d35988152d9e95e26908046ea3540d34e47cf92f"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.1","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"9.0","label":"v9.0.1","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.17","label":"v8.17.5","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"url":"https://github.com/elastic/kibana/pull/217315","number":217315,"state":"OPEN"}]}] BACKPORT--> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
0e90f2d2f2
commit
a9a1667338
4 changed files with 113 additions and 50 deletions
|
@ -20,21 +20,16 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker';
|
||||
import { UI_SETTINGS } from '@kbn/data-plugin/common';
|
||||
import type { FieldStatsServices } from '@kbn/unified-field-list/src/components/field_stats';
|
||||
import { ES_FIELD_TYPES } from '@kbn/field-types';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
|
||||
import { FieldStatsFlyoutProvider } from '@kbn/ml-field-stats-flyout';
|
||||
import { useTimefilter } from '@kbn/ml-date-picker';
|
||||
import { pick } from 'lodash';
|
||||
import type { FC } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import usePrevious from 'react-use/lib/usePrevious';
|
||||
import {
|
||||
ChangePointDetectionContextProvider,
|
||||
ChangePointDetectionControlsContextProvider,
|
||||
useChangePointDetectionContext,
|
||||
useChangePointDetectionControlsContext,
|
||||
} from '../../components/change_point_detection/change_point_detection_context';
|
||||
import { DEFAULT_AGG_FUNCTION } from '../../components/change_point_detection/constants';
|
||||
|
@ -45,7 +40,7 @@ import { PartitionsSelector } from '../../components/change_point_detection/part
|
|||
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 { useDataSource, DataSourceContextProvider } from '../../hooks/use_data_source';
|
||||
import { DataSourceContextProvider } from '../../hooks/use_data_source';
|
||||
import { FilterQueryContextProvider } from '../../hooks/use_filters_query';
|
||||
import { DEFAULT_SERIES } from './const';
|
||||
import type { ChangePointEmbeddableRuntimeState } from './types';
|
||||
|
@ -134,7 +129,7 @@ export const ChangePointChartInitializer: FC<AnomalyChartsInitializerProps> = ({
|
|||
</EuiFlyoutHeader>
|
||||
|
||||
<EuiFlyoutBody>
|
||||
<EuiForm>
|
||||
<EuiForm data-test-subj="aiopsChangePointDetectionControls">
|
||||
<ViewTypeSelector value={viewType} onChange={setViewType} />
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
|
@ -156,21 +151,20 @@ export const ChangePointChartInitializer: FC<AnomalyChartsInitializerProps> = ({
|
|||
onChange={(newId) => {
|
||||
setDataViewId(newId ?? '');
|
||||
}}
|
||||
data-test-subj="aiopsChangePointChartEmbeddableDataViewSelector"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiHorizontalRule margin={'s'} />
|
||||
<DataSourceContextProvider dataViews={dataViews} dataViewId={dataViewId}>
|
||||
<DatePickerContextProvider {...datePickerDeps}>
|
||||
<FilterQueryContextProvider>
|
||||
<ChangePointDetectionContextProvider>
|
||||
<ChangePointDetectionControlsContextProvider>
|
||||
<FormControls
|
||||
formInput={formInput}
|
||||
onChange={setFormInput}
|
||||
onValidationChange={setIsFormValid}
|
||||
/>
|
||||
</ChangePointDetectionControlsContextProvider>
|
||||
</ChangePointDetectionContextProvider>
|
||||
<ChangePointDetectionControlsContextProvider>
|
||||
<FormControls
|
||||
formInput={formInput}
|
||||
onChange={setFormInput}
|
||||
onValidationChange={setIsFormValid}
|
||||
/>
|
||||
</ChangePointDetectionControlsContextProvider>
|
||||
</FilterQueryContextProvider>
|
||||
</DatePickerContextProvider>
|
||||
</DataSourceContextProvider>
|
||||
|
@ -219,12 +213,7 @@ export const FormControls: FC<{
|
|||
onChange: (update: FormControlsProps) => void;
|
||||
onValidationChange: (isValid: boolean) => void;
|
||||
}> = ({ formInput, onChange, onValidationChange }) => {
|
||||
const { charts, data, fieldFormats, theme, uiSettings } = useAiopsAppContext();
|
||||
const { dataView } = useDataSource();
|
||||
const { combinedQuery } = useChangePointDetectionContext();
|
||||
const { metricFieldOptions, splitFieldsOptions } = useChangePointDetectionControlsContext();
|
||||
const timefilter = useTimefilter();
|
||||
const timefilterActiveBounds = timefilter.getActiveBounds();
|
||||
|
||||
const prevMetricFieldOptions = usePrevious(metricFieldOptions);
|
||||
|
||||
|
@ -273,33 +262,10 @@ export const FormControls: FC<{
|
|||
[formInput, onChange]
|
||||
);
|
||||
|
||||
const fieldStatsServices: FieldStatsServices = useMemo(() => {
|
||||
return {
|
||||
uiSettings,
|
||||
dataViews: data.dataViews,
|
||||
data,
|
||||
fieldFormats,
|
||||
charts,
|
||||
};
|
||||
}, [uiSettings, data, fieldFormats, charts]);
|
||||
|
||||
if (!isPopulatedObject(formInput)) return null;
|
||||
|
||||
return (
|
||||
<FieldStatsFlyoutProvider
|
||||
fieldStatsServices={fieldStatsServices}
|
||||
dataView={dataView}
|
||||
dslQuery={combinedQuery}
|
||||
timeRangeMs={
|
||||
timefilterActiveBounds
|
||||
? {
|
||||
from: timefilterActiveBounds.min!.valueOf(),
|
||||
to: timefilterActiveBounds.max!.valueOf(),
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
theme={theme}
|
||||
>
|
||||
<>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={
|
||||
|
@ -340,6 +306,6 @@ export const FormControls: FC<{
|
|||
onChange={(v) => updateCallback({ maxSeriesToPlot: v })}
|
||||
onValidationChange={(result) => onValidationChange(result === null)}
|
||||
/>
|
||||
</FieldStatsFlyoutProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,8 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EMBEDDABLE_CHANGE_POINT_CHART_TYPE } from '@kbn/aiops-change-point-detection/constants';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
const dashboardTitle = 'Change point detection';
|
||||
|
||||
export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||
const elasticChart = getService('elasticChart');
|
||||
const esArchiver = getService('esArchiver');
|
||||
|
@ -15,6 +18,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
// aiops lives in the ML UI so we need some related services.
|
||||
const ml = getService('ml');
|
||||
|
||||
const PageObjects = getPageObjects(['dashboard']);
|
||||
|
||||
describe('change point detection in dashboard', function () {
|
||||
before(async () => {
|
||||
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/ecommerce');
|
||||
|
@ -24,6 +29,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
after(async () => {
|
||||
await ml.testResources.deleteDashboardByTitle(dashboardTitle);
|
||||
await ml.testResources.deleteDataViewByTitle('ft_ecommerce');
|
||||
});
|
||||
|
||||
|
@ -44,5 +50,29 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
maxSeries: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('attaches change point charts to a dashboard from the dashboard app', async () => {
|
||||
await PageObjects.dashboard.navigateToApp();
|
||||
await PageObjects.dashboard.clickNewDashboard();
|
||||
|
||||
await aiops.dashboardEmbeddables.assertDashboardIsEmpty();
|
||||
await aiops.dashboardEmbeddables.openEmbeddableInitializer(
|
||||
EMBEDDABLE_CHANGE_POINT_CHART_TYPE
|
||||
);
|
||||
|
||||
await aiops.dashboardEmbeddables.assertInitializerConfirmButtonEnabled(
|
||||
'aiopsChangePointChartsInitializerConfirmButton',
|
||||
false
|
||||
);
|
||||
await aiops.dashboardEmbeddables.assertChangePointChartEmbeddableDataViewSelectorExists();
|
||||
await aiops.dashboardEmbeddables.selectChangePointChartEmbeddableDataView('ft_ecommerce');
|
||||
|
||||
await aiops.dashboardEmbeddables.assertInitializerConfirmButtonEnabled(
|
||||
'aiopsChangePointChartsInitializerConfirmButton'
|
||||
);
|
||||
await aiops.dashboardEmbeddables.submitChangePointInitForm();
|
||||
await aiops.dashboardEmbeddables.assertChangePointPanelExists();
|
||||
await PageObjects.dashboard.saveDashboard(dashboardTitle);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ import expect from '@kbn/expect';
|
|||
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
type AiopsEmbeddableType = 'aiopsLogRateAnalysisEmbeddable' | 'aiopsChangePointChart';
|
||||
|
||||
export function AiopsDashboardEmbeddablesProvider({ getService }: FtrProviderContext) {
|
||||
const comboBox = getService('comboBox');
|
||||
const retry = getService('retry');
|
||||
|
@ -33,10 +35,29 @@ export function AiopsDashboardEmbeddablesProvider({ getService }: FtrProviderCon
|
|||
});
|
||||
},
|
||||
|
||||
async assertInitializerConfirmButtonEnabled(subj: string) {
|
||||
async assertChangePointChartInitializerExists(expectExists = true) {
|
||||
await retry.tryForTime(10 * 1000, async () => {
|
||||
if (expectExists) {
|
||||
await testSubjects.existOrFail('aiopsChangePointChartEmbeddableInitializer', {
|
||||
timeout: 1000,
|
||||
});
|
||||
} else {
|
||||
await testSubjects.missingOrFail('aiopsChangePointChartEmbeddableInitializer', {
|
||||
timeout: 1000,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async assertInitializerConfirmButtonEnabled(subj: string, expectEnabled = true) {
|
||||
await retry.tryForTime(60 * 1000, async () => {
|
||||
await testSubjects.existOrFail(subj);
|
||||
await testSubjects.isEnabled(subj);
|
||||
const isEnabled = await testSubjects.isEnabled(subj);
|
||||
if (expectEnabled) {
|
||||
expect(isEnabled).to.be(true);
|
||||
} else {
|
||||
expect(isEnabled).to.be(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -68,9 +89,18 @@ export function AiopsDashboardEmbeddablesProvider({ getService }: FtrProviderCon
|
|||
});
|
||||
},
|
||||
|
||||
async openEmbeddableInitializer(mlEmbeddableType: 'aiopsLogRateAnalysisEmbeddable') {
|
||||
async submitChangePointInitForm() {
|
||||
const subj = 'aiopsChangePointChartsInitializerConfirmButton';
|
||||
await retry.tryForTime(60 * 1000, async () => {
|
||||
await testSubjects.clickWhenNotDisabledWithoutRetry(subj);
|
||||
await this.assertChangePointChartInitializerExists(false);
|
||||
});
|
||||
},
|
||||
|
||||
async openEmbeddableInitializer(mlEmbeddableType: AiopsEmbeddableType) {
|
||||
const name = {
|
||||
aiopsLogRateAnalysisEmbeddable: 'Log rate analysis',
|
||||
aiopsChangePointChart: 'Change point detection',
|
||||
};
|
||||
await retry.tryForTime(60 * 1000, async () => {
|
||||
await dashboardAddPanel.clickEditorMenuButton();
|
||||
|
@ -79,16 +109,34 @@ export function AiopsDashboardEmbeddablesProvider({ getService }: FtrProviderCon
|
|||
await dashboardAddPanel.verifyEmbeddableFactoryGroupExists('logs-aiops');
|
||||
|
||||
await dashboardAddPanel.clickAddNewPanelFromUIActionLink(name[mlEmbeddableType]);
|
||||
await testSubjects.existOrFail('aiopsLogRateAnalysisControls', { timeout: 2000 });
|
||||
await this.assertEmbeddableControlsExist(mlEmbeddableType);
|
||||
});
|
||||
},
|
||||
|
||||
async assertEmbeddableControlsExist(mlEmbeddableType: AiopsEmbeddableType) {
|
||||
const controlSelectors = {
|
||||
aiopsLogRateAnalysisEmbeddable: 'aiopsLogRateAnalysisControls',
|
||||
aiopsChangePointChart: 'aiopsChangePointDetectionControls',
|
||||
};
|
||||
await testSubjects.existOrFail(controlSelectors[mlEmbeddableType], { timeout: 2000 });
|
||||
},
|
||||
|
||||
async assertChangePointPanelExists() {
|
||||
await testSubjects.existOrFail('aiopsEmbeddableChangePointChart');
|
||||
},
|
||||
|
||||
async assertLogRateAnalysisEmbeddableDataViewSelectorExists() {
|
||||
await testSubjects.existOrFail(
|
||||
'aiopsLogRateAnalysisEmbeddableDataViewSelector > comboBoxInput'
|
||||
);
|
||||
},
|
||||
|
||||
async assertChangePointChartEmbeddableDataViewSelectorExists() {
|
||||
await testSubjects.existOrFail(
|
||||
'aiopsChangePointChartEmbeddableDataViewSelector > comboBoxInput'
|
||||
);
|
||||
},
|
||||
|
||||
async assertLogRateAnalysisEmbeddableDataViewSelection(dataViewValue: string) {
|
||||
const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
|
||||
'aiopsLogRateAnalysisEmbeddableDataViewSelector > comboBoxInput'
|
||||
|
@ -99,6 +147,16 @@ export function AiopsDashboardEmbeddablesProvider({ getService }: FtrProviderCon
|
|||
);
|
||||
},
|
||||
|
||||
async assertChangePointChartEmbeddableDataViewSelection(dataViewValue: string) {
|
||||
const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions(
|
||||
'aiopsChangePointChartEmbeddableDataViewSelector > comboBoxInput'
|
||||
);
|
||||
expect(comboBoxSelectedOptions).to.eql(
|
||||
[dataViewValue],
|
||||
`Expected data view selection to be '${dataViewValue}' (got '${comboBoxSelectedOptions}')`
|
||||
);
|
||||
},
|
||||
|
||||
async selectLogRateAnalysisEmbeddableDataView(dataViewValue: string) {
|
||||
await comboBox.set(
|
||||
'aiopsLogRateAnalysisEmbeddableDataViewSelector > comboBoxInput',
|
||||
|
@ -106,5 +164,13 @@ export function AiopsDashboardEmbeddablesProvider({ getService }: FtrProviderCon
|
|||
);
|
||||
await this.assertLogRateAnalysisEmbeddableDataViewSelection(dataViewValue);
|
||||
},
|
||||
|
||||
async selectChangePointChartEmbeddableDataView(dataViewValue: string) {
|
||||
await comboBox.set(
|
||||
'aiopsChangePointChartEmbeddableDataViewSelector > comboBoxInput',
|
||||
dataViewValue
|
||||
);
|
||||
await this.assertChangePointChartEmbeddableDataViewSelection(dataViewValue);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -196,5 +196,6 @@
|
|||
"@kbn/server-route-repository-utils",
|
||||
"@kbn/streams-plugin",
|
||||
"@kbn/response-ops-rule-params",
|
||||
"@kbn/aiops-change-point-detection",
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue