diff --git a/x-pack/plugins/ml/public/application/explorer/add_to_dashboard_control.tsx b/x-pack/plugins/ml/public/application/explorer/add_to_dashboard_control.tsx index eafdd671db6e..51cf98ea2d06 100644 --- a/x-pack/plugins/ml/public/application/explorer/add_to_dashboard_control.tsx +++ b/x-pack/plugins/ml/public/application/explorer/add_to_dashboard_control.tsx @@ -272,6 +272,7 @@ export const AddToDashboardControl: FC = ({ search={search} pagination={true} sorting={true} + data-test-subj="mlDashboardSelection" /> @@ -293,6 +294,7 @@ export const AddToDashboardControl: FC = ({ ); }); }} + data-test-subj="mlAddAndEditDashboardButton" > = ({ fill onClick={onClose.bind(null, addSwimlaneToDashboardCallback)} disabled={noSwimlaneSelected || selectedItems.length === 0} + data-test-subj="mlAddToDashboardsButton" > { await esArchiver.loadIfNeeded('ml/farequote'); await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); + await ml.testResources.createMLTestDashboardIfNeeded(); await ml.testResources.setKibanaTimeZoneToUTC(); await ml.securityUI.loginAsMlPowerUser(); @@ -125,6 +126,12 @@ export default function ({ getService }: FtrProviderContext) { it('anomalies table is not empty', async () => { await ml.anomaliesTable.assertTableNotEmpty(); }); + + it('should allow to attach anomaly swimlane embeddable to the dashboard', async () => { + await ml.anomalyExplorer.openAddToDashboardControl(); + const dashboardId = await ml.testResources.getDashboardId('ML Test'); + await ml.anomalyExplorer.addAndEditSwimlaneInDashboard(dashboardId!); + }); }); } }); diff --git a/x-pack/test/functional/services/ml/anomaly_explorer.ts b/x-pack/test/functional/services/ml/anomaly_explorer.ts index 6ec72c76bb9c..d6937f83bb3f 100644 --- a/x-pack/test/functional/services/ml/anomaly_explorer.ts +++ b/x-pack/test/functional/services/ml/anomaly_explorer.ts @@ -9,6 +9,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export function MachineLearningAnomalyExplorerProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); + const find = getService('find'); return { async assertAnomalyExplorerEmptyListMessageExists() { @@ -66,5 +67,20 @@ export function MachineLearningAnomalyExplorerProvider({ getService }: FtrProvid async assertSwimlaneViewByExists() { await testSubjects.existOrFail('mlAnomalyExplorerSwimlaneViewBy'); }, + + async openAddToDashboardControl() { + await testSubjects.click('mlAnomalyTimelinePanelMenu'); + await testSubjects.click('mlAnomalyTimelinePanelAddToDashboardButton'); + await testSubjects.existOrFail('mlAddToDashboardModal'); + }, + + async addAndEditSwimlaneInDashboard(dashboardId: string) { + await testSubjects.isDisplayed('mlDashboardSelection'); + await testSubjects.isDisplayed(`checkboxSelectRow-${dashboardId}`); + await testSubjects.click(`checkboxSelectRow-${dashboardId}`); + await testSubjects.clickWhenNotDisabled('mlAddAndEditDashboardButton'); + const swimlane = await find.byClassName('ml-swimlanes'); + expect(await swimlane.isDisplayed()).to.be(true); + }, }; } diff --git a/x-pack/test/functional/services/ml/test_resources.ts b/x-pack/test/functional/services/ml/test_resources.ts index 739fd844f119..10f5af9ddc7d 100644 --- a/x-pack/test/functional/services/ml/test_resources.ts +++ b/x-pack/test/functional/services/ml/test_resources.ts @@ -5,7 +5,7 @@ */ import { ProvidedType } from '@kbn/test/types/ftr'; -import { savedSearches } from './test_resources_data'; +import { savedSearches, dashboards } from './test_resources_data'; import { COMMON_REQUEST_HEADERS } from './common'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -70,6 +70,10 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider return this.getSavedObjectIdByTitle(title, SavedObjectType.SEARCH); }, + async getDashboardId(title: string): Promise { + return this.getSavedObjectIdByTitle(title, SavedObjectType.DASHBOARD); + }, + async createIndexPattern(title: string, timeFieldName?: string): Promise { log.debug( `Creating index pattern with title '${title}'${ @@ -127,6 +131,20 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider return createResponse.id; }, + async createDashboard(title: string, body: object): Promise { + log.debug(`Creating dashboard with title '${title}'`); + + const createResponse = await supertest + .post(`/api/saved_objects/${SavedObjectType.DASHBOARD}`) + .set(COMMON_REQUEST_HEADERS) + .send(body) + .expect(200) + .then((res: any) => res.body); + + log.debug(` > Created with id '${createResponse.id}'`); + return createResponse.id; + }, + async createSavedSearchIfNeeded(savedSearch: any): Promise { const title = savedSearch.requestBody.attributes.title; const savedSearchId = await this.getSavedSearchId(title); @@ -171,6 +189,21 @@ export function MachineLearningTestResourcesProvider({ getService }: FtrProvider await this.createSavedSearchIfNeeded(savedSearches.farequoteFilter); }, + async createMLTestDashboardIfNeeded() { + await this.createDashboardIfNeeded(dashboards.mlTestDashboard); + }, + + async createDashboardIfNeeded(dashboard: any) { + const title = dashboard.requestBody.attributes.title; + const dashboardId = await this.getDashboardId(title); + if (dashboardId !== undefined) { + log.debug(`Dashboard with title '${title}' already exists. Nothing to create.`); + return dashboardId; + } else { + return await this.createDashboard(title, dashboard.requestBody); + } + }, + async createSavedSearchFarequoteLuceneIfNeeded() { await this.createSavedSearchIfNeeded(savedSearches.farequoteLucene); }, diff --git a/x-pack/test/functional/services/ml/test_resources_data.ts b/x-pack/test/functional/services/ml/test_resources_data.ts index dd600077182f..2ab1f4de5422 100644 --- a/x-pack/test/functional/services/ml/test_resources_data.ts +++ b/x-pack/test/functional/services/ml/test_resources_data.ts @@ -247,3 +247,22 @@ export const savedSearches = { }, }, }; + +export const dashboards = { + mlTestDashboard: { + requestBody: { + attributes: { + title: 'ML Test', + hits: 0, + description: '', + panelsJSON: '[]', + optionsJSON: '{"hidePanelTitles":false,"useMargins":true}', + version: 1, + timeRestore: false, + kibanaSavedObjectMeta: { + searchSourceJSON: '{"query":{"language":"kuery","query":""},"filter":[]}', + }, + }, + }, + }, +};