Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -134,7 +129,7 @@ export const ChangePointChartInitializer: FC<AnomalyChartsInitializerProps> = ({
</EuiFlyoutHeader>

<EuiFlyoutBody>
<EuiForm>
<EuiForm data-test-subj="aiopsChangePointDetectionControls">
<ViewTypeSelector value={viewType} onChange={setViewType} />
<EuiFormRow
fullWidth
Expand All @@ -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>
Expand Down Expand Up @@ -219,12 +213,7 @@ export const FormControls: FC<{
onChange: (update: FormControlsProps) => void;
onValidationChange: (isValid: boolean) => void;
}> = ({ formInput, onChange, onValidationChange }) => {
const { charts, data, fieldFormats, uiSettings } = useAiopsAppContext();
const { dataView } = useDataSource();
const { combinedQuery } = useChangePointDetectionContext();
const { metricFieldOptions, splitFieldsOptions } = useChangePointDetectionControlsContext();
const timefilter = useTimefilter();
const timefilterActiveBounds = timefilter.getActiveBounds();

const prevMetricFieldOptions = usePrevious(metricFieldOptions);

Expand Down Expand Up @@ -273,32 +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
}
>
<>
<EuiFormRow
fullWidth
label={
Expand Down Expand Up @@ -339,6 +306,6 @@ export const FormControls: FC<{
onChange={(v) => updateCallback({ maxSeriesToPlot: v })}
onValidationChange={(result) => onValidationChange(result === null)}
/>
</FieldStatsFlyoutProvider>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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');
Expand All @@ -24,6 +29,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
});

after(async () => {
await ml.testResources.deleteDashboardByTitle(dashboardTitle);
await ml.testResources.deleteDataViewByTitle('ft_ecommerce');
});

Expand All @@ -44,5 +50,22 @@ 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
);

Comment thread
darnautov marked this conversation as resolved.
await aiops.dashboardEmbeddables.assertChangePointChartEmbeddableDataViewSelectorExists();
await aiops.dashboardEmbeddables.selectChangePointChartEmbeddableDataView('ft_ecommerce');

Comment thread
darnautov marked this conversation as resolved.
await aiops.dashboardEmbeddables.clickChangePointChartInitializerConfirmButtonEnabled();
await aiops.dashboardEmbeddables.assertChangePointPanelExists();
await PageObjects.dashboard.saveDashboard(dashboardTitle);
});
});
}
60 changes: 58 additions & 2 deletions x-pack/test/functional/services/aiops/dashboard_embeddables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -33,6 +35,14 @@ export function AiopsDashboardEmbeddablesProvider({ getService }: FtrProviderCon
});
},

async assertChangePointChartInitializerNotExists() {
await retry.tryForTime(10 * 1000, async () => {
await testSubjects.missingOrFail('aiopsChangePointChartEmbeddableInitializer', {
timeout: 1000,
});
});
},
Comment thread
darnautov marked this conversation as resolved.
Outdated

async assertInitializerConfirmButtonEnabled(subj: string) {
await retry.tryForTime(60 * 1000, async () => {
await testSubjects.existOrFail(subj);
Expand Down Expand Up @@ -68,9 +78,19 @@ export function AiopsDashboardEmbeddablesProvider({ getService }: FtrProviderCon
});
},

async openEmbeddableInitializer(mlEmbeddableType: 'aiopsLogRateAnalysisEmbeddable') {
async clickChangePointChartInitializerConfirmButtonEnabled() {
Comment thread
darnautov marked this conversation as resolved.
Outdated
const subj = 'aiopsChangePointChartsInitializerConfirmButton';
await retry.tryForTime(60 * 1000, async () => {
await this.assertInitializerConfirmButtonEnabled(subj);
await testSubjects.clickWhenNotDisabledWithoutRetry(subj);
await this.assertChangePointChartInitializerNotExists();
});
},

async openEmbeddableInitializer(mlEmbeddableType: AiopsEmbeddableType) {
const name = {
aiopsLogRateAnalysisEmbeddable: 'Log rate analysis',
aiopsChangePointChart: 'Change point detection',
};
await retry.tryForTime(60 * 1000, async () => {
await dashboardAddPanel.clickEditorMenuButton();
Expand All @@ -79,16 +99,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'
Expand All @@ -99,12 +137,30 @@ 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',
dataViewValue
);
await this.assertLogRateAnalysisEmbeddableDataViewSelection(dataViewValue);
},

async selectChangePointChartEmbeddableDataView(dataViewValue: string) {
await comboBox.set(
'aiopsChangePointChartEmbeddableDataViewSelector > comboBoxInput',
dataViewValue
);
await this.assertChangePointChartEmbeddableDataViewSelection(dataViewValue);
},
};
}
1 change: 1 addition & 0 deletions x-pack/test/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -190,5 +190,6 @@
"@kbn/scout-info",
"@kbn/inference-common",
"@kbn/apm-sources-access-plugin",
"@kbn/aiops-change-point-detection",
]
}