diff --git a/x-pack/plugins/aiops/public/embeddables/change_point_chart/change_point_chart_initializer.tsx b/x-pack/plugins/aiops/public/embeddables/change_point_chart/change_point_chart_initializer.tsx index e69511fe45f92..b87482ef2395d 100644 --- a/x-pack/plugins/aiops/public/embeddables/change_point_chart/change_point_chart_initializer.tsx +++ b/x-pack/plugins/aiops/public/embeddables/change_point_chart/change_point_chart_initializer.tsx @@ -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'; @@ -126,7 +121,7 @@ export const ChangePointChartInitializer: FC = ({ - + = ({ onChange={(newId) => { setDataViewId(newId ?? ''); }} + data-test-subj="aiopsChangePointChartEmbeddableDataViewSelector" /> - - - - - + + + @@ -211,12 +205,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); @@ -265,33 +254,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 ( - + <> updateCallback({ maxSeriesToPlot: v })} onValidationChange={(result) => onValidationChange(result === null)} /> - + ); }; diff --git a/x-pack/test/functional/apps/aiops/change_point_detection_dashboard.ts b/x-pack/test/functional/apps/aiops/change_point_detection_dashboard.ts index b152bc45bd17b..74bc2f4139659 100644 --- a/x-pack/test/functional/apps/aiops/change_point_detection_dashboard.ts +++ b/x-pack/test/functional/apps/aiops/change_point_detection_dashboard.ts @@ -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); + }); }); } diff --git a/x-pack/test/functional/services/aiops/dashboard_embeddables.ts b/x-pack/test/functional/services/aiops/dashboard_embeddables.ts index a24cec24734da..9616148b2777c 100644 --- a/x-pack/test/functional/services/aiops/dashboard_embeddables.ts +++ b/x-pack/test/functional/services/aiops/dashboard_embeddables.ts @@ -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); + }, }; } diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 9db41aecbb612..8f8663664ac92 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -188,5 +188,6 @@ "@kbn/ai-assistant-common", "@kbn/core-deprecations-common", "@kbn/usage-collection-plugin", + "@kbn/aiops-change-point-detection" ] }