import expect from '@kbn/expect' ; import { ProvidedType } from '@kbn/test' ; import { WebElementWrapper } from 'test/functional/services/lib/web_element_wrapper' ; import { FtrProviderContext } from '../../ftr_provider_context' ; type ExpectedSectionTableEntries = Record < string , string > ; export interface ExpectedSectionTable { section : string ; expectedEntries : ExpectedSectionTableEntries ; } export type AnalyticsTableRowDetails = Record < 'jobDetails' , ExpectedSectionTable [ ] > ; export type MlDFAJobTable = ProvidedType < typeof MachineLearningDataFrameAnalyticsTableProvider > ; export function MachineLearningDataFrameAnalyticsTableProvider ( { getService } ) { const find = getService ( 'find' ) ; const retry = getService ( 'retry' ) ; const testSubjects = getService ( 'testSubjects' ) ; return new ( class AnalyticsTable { public async parseAnalyticsTable ( tableEnvironment = 'mlDataFrameAnalytics' ) { const table = await testSubjects . find ( '~mlAnalyticsTable' ) ; const $ = await table . parseDomContent ( ) ; const rows = [ ] ; for ( const tr of $ . findTestSubjects ( '~mlAnalyticsTableRow' ) . toArray ( ) ) { const $tr = $ ( tr ) ; const rowObject = { id : $tr . findTestSubject ( 'mlAnalyticsTableColumnId' ) . find ( '.euiTableCellContent' ) . text ( ) . trim ( ) , description : $tr . findTestSubject ( 'mlAnalyticsTableColumnJobDescription' ) . find ( '.euiTableCellContent' ) . text ( ) . trim ( ) , memoryStatus : $tr . findTestSubject ( 'mlAnalyticsTableColumnJobMemoryStatus' ) . find ( '.euiTableCellContent' ) . text ( ) . trim ( ) , sourceIndex : $tr . findTestSubject ( 'mlAnalyticsTableColumnSourceIndex' ) . find ( '.euiTableCellContent' ) . text ( ) . trim ( ) , destinationIndex : $tr . findTestSubject ( 'mlAnalyticsTableColumnDestIndex' ) . find ( '.euiTableCellContent' ) . text ( ) . trim ( ) , type : $tr . findTestSubject ( 'mlAnalyticsTableColumnType' ) . find ( '.euiTableCellContent' ) . text ( ) . trim ( ) , status : $tr . findTestSubject ( 'mlAnalyticsTableColumnStatus' ) . find ( '.euiTableCellContent' ) . text ( ) . trim ( ) , progress : $tr . findTestSubject ( 'mlAnalyticsTableColumnProgress' ) . findTestSubject ( 'mlAnalyticsTableProgress' ) . attr ( 'value' ) , } ; if ( tableEnvironment === 'stackMgmtJobList' ) { const $spaces = $tr . findTestSubject ( 'mlAnalyticsTableColumnSpaces' ) . find ( '.euiTableCellContent' ) . find ( '.euiAvatar--space' ) ; const spaces = [ ] ; for ( const el of $spaces . toArray ( ) ) { spaces . push ( $ ( el ) . attr ( 'data-test-subj' ) . replace ( 'space-avatar-' , '' ) ) ; } rowObject . spaces = spaces ; } rows . push ( rowObject ) ; } return rows ; } public rowSelector ( analyticsId , subSelector ? ) { const row = `~mlAnalyticsTable > ~row-${ analyticsId }` ; return ! subSelector ? row : `${ row } > ${ subSelector }` ; } public async waitForRefreshButtonLoaded ( buttonTestSubj ) { await testSubjects . existOrFail ( `~${ buttonTestSubj }` , { timeout : 10 * 1000 } ) ; await testSubjects . existOrFail ( `${ buttonTestSubj } loaded` , { timeout : 30 * 1000 } ) ; } public async refreshAnalyticsTable ( tableEnvironment = 'mlDataFrameAnalytics' ) { const testSubjStr = tableEnvironment === 'mlDataFrameAnalytics' ? 'mlRefreshPageButton' : 'mlAnalyticsRefreshListButton' ; await this . waitForRefreshButtonLoaded ( testSubjStr ) ; await testSubjects . click ( `~${ testSubjStr }` ) ; await this . waitForRefreshButtonLoaded ( testSubjStr ) ; await this . waitForAnalyticsToLoad ( ) ; } public async waitForAnalyticsToLoad ( ) { await testSubjects . existOrFail ( '~mlAnalyticsTable' , { timeout : 60 * 1000 } ) ; await testSubjects . existOrFail ( 'mlAnalyticsTable loaded' , { timeout : 30 * 1000 } ) ; } async getAnalyticsSearchInput ( ) { const tableListContainer = await testSubjects . find ( 'mlAnalyticsTableContainer' ) ; return await tableListContainer . findByClassName ( 'euiFieldSearch' ) ; } public async assertJobRowViewButtonExists ( analyticsId ) { await testSubjects . existOrFail ( this . rowSelector ( analyticsId , 'mlAnalyticsJobViewButton' ) ) ; } public async assertJobRowMapButtonExists ( analyticsId ) { await testSubjects . existOrFail ( this . rowSelector ( analyticsId , 'mlAnalyticsJobMapButton' ) ) ; } public async assertJobRowViewButtonEnabled ( analyticsId , expectedValue ) { const isEnabled = await testSubjects . isEnabled ( this . rowSelector ( analyticsId , 'mlAnalyticsJobViewButton' ) ) ; expect ( isEnabled ) . to . eql ( expectedValue , `Expected data frame analytics row "view results" button for job '${ analyticsId }' to be '${ expectedValue ? 'enabled' : 'disabled' }' (got '${ isEnabled ? 'enabled' : 'disabled' }')` ) ; } public async openResultsView ( analyticsId ) { await retry . tryForTime ( 20 * 1000 , async ( ) => { await this . assertJobRowViewButtonExists ( analyticsId ) ; await testSubjects . click ( this . rowSelector ( analyticsId , 'mlAnalyticsJobViewButton' ) ) ; await testSubjects . existOrFail ( 'mlPageDataFrameAnalyticsExploration' , { timeout : 5 * 1000 , } ) ; } ) ; } public async openMapView ( analyticsId ) { await retry . tryForTime ( 20 * 1000 , async ( ) => { await this . assertJobRowMapButtonExists ( analyticsId ) ; await testSubjects . click ( this . rowSelector ( analyticsId , 'mlAnalyticsJobMapButton' ) ) ; await testSubjects . existOrFail ( 'mlPageDataFrameAnalyticsMap' , { timeout : 5 * 1000 } ) ; } ) ; } public async assertAnalyticsSearchInputValue ( expectedSearchValue ) { const searchBarInput = await this . getAnalyticsSearchInput ( ) ; const actualSearchValue = await searchBarInput . getAttribute ( 'value' ) ; expect ( actualSearchValue ) . to . eql ( expectedSearchValue , `Analytics search input value should be '${ expectedSearchValue }' (got '${ actualSearchValue }')` ) ; } public async filterWithSearchString ( filter , expectedRowCount = 1 ) { await this . waitForAnalyticsToLoad ( ) ; const searchBarInput = await this . getAnalyticsSearchInput ( ) ; await searchBarInput . clearValueWithKeyboard ( ) ; await searchBarInput . type ( filter ) ; await this . assertAnalyticsSearchInputValue ( filter ) ; const rows = await this . parseAnalyticsTable ( ) ; const filteredRows = rows . filter ( ( row ) => row . id === filter ) ; expect ( filteredRows ) . to . have . length ( expectedRowCount , `Filtered DFA job table should have ${ expectedRowCount } row(s) for filter '${ filter }' (got matching items '${ filteredRows }')` ) ; } public async assertAnalyticsJobDisplayedInTable ( analyticsId , shouldBeDisplayed , refreshButtonTestSubj = 'mlRefreshPageButton' ) { await this . waitForRefreshButtonLoaded ( refreshButtonTestSubj ) ; await testSubjects . click ( `~${ refreshButtonTestSubj }` ) ; await this . waitForRefreshButtonLoaded ( refreshButtonTestSubj ) ; await testSubjects . existOrFail ( 'mlAnalyticsJobList' , { timeout : 30 * 1000 } ) ; if ( shouldBeDisplayed ) { await this . filterWithSearchString ( analyticsId , 1 ) ; } else { if ( await testSubjects . exists ( 'mlNoDataFrameAnalyticsFound' , { timeout : 1000 } ) ) { return ; } await this . filterWithSearchString ( analyticsId , 0 ) ; } } public async assertAnalyticsRowFields ( analyticsId , expectedRow ) { await this . refreshAnalyticsTable ( ) ; const rows = await this . parseAnalyticsTable ( ) ; const analyticsRow = rows . filter ( ( row ) => row . id === analyticsId ) [ 0 ] ; expect ( analyticsRow ) . to . eql ( expectedRow , `Expected analytics row to be '${ JSON . stringify ( expectedRow ) }' (got '${ JSON . stringify ( analyticsRow ) }')` ) ; } public async assertJobRowActionsMenuButtonEnabled ( analyticsId , expectedValue ) { const isEnabled = await testSubjects . isEnabled ( this . rowSelector ( analyticsId , 'euiCollapsedItemActionsButton' ) ) ; expect ( isEnabled ) . to . eql ( expectedValue , `Expected row action menu button for DFA job '${ analyticsId }' to be '${ expectedValue ? 'enabled' : 'disabled' }' (got '${ isEnabled ? 'enabled' : 'disabled' }')` ) ; } public async ensureJobActionsMenuOpen ( analyticsId ) { await retry . tryForTime ( 30 * 1000 , async ( ) => { if ( ! ( await testSubjects . exists ( 'mlAnalyticsJobDeleteButton' ) ) ) { await testSubjects . click ( this . rowSelector ( analyticsId , 'euiCollapsedItemActionsButton' ) ) ; await testSubjects . existOrFail ( 'mlAnalyticsJobDeleteButton' , { timeout : 5000 } ) ; } } ) ; } public async ensureJobActionsMenuClosed ( analyticsId ) { await retry . tryForTime ( 30 * 1000 , async ( ) => { if ( await testSubjects . exists ( 'mlAnalyticsJobDeleteButton' ) ) { await testSubjects . click ( this . rowSelector ( analyticsId , 'euiCollapsedItemActionsButton' ) ) ; await testSubjects . missingOrFail ( 'mlAnalyticsJobDeleteButton' , { timeout : 5000 } ) ; } } ) ; } public async assertJobActionViewButtonEnabled ( analyticsId , expectedValue ) { await this . ensureJobActionsMenuOpen ( analyticsId ) ; const actionMenuViewButton = await find . byCssSelector ( '[data-test-subj="mlAnalyticsJobViewButton"][class="euiContextMenuItem"]' ) ; const isEnabled = await actionMenuViewButton . isEnabled ( ) ; expect ( isEnabled ) . to . eql ( expectedValue , `Expected "view" action menu button for DFA job '${ analyticsId }' to be '${ expectedValue ? 'enabled' : 'disabled' }' (got '${ isEnabled ? 'enabled' : 'disabled' }')` ) ; } public async assertJobActionStartButtonEnabled ( analyticsId , expectedValue ) { await this . ensureJobActionsMenuOpen ( analyticsId ) ; const isEnabled = await testSubjects . isEnabled ( 'mlAnalyticsJobStartButton' ) ; expect ( isEnabled ) . to . eql ( expectedValue , `Expected "start" action menu button for DFA job '${ analyticsId }' to be '${ expectedValue ? 'enabled' : 'disabled' }' (got '${ isEnabled ? 'enabled' : 'disabled' }')` ) ; } public async assertJobActionEditButtonEnabled ( analyticsId , expectedValue ) { await this . ensureJobActionsMenuOpen ( analyticsId ) ; const isEnabled = await testSubjects . isEnabled ( 'mlAnalyticsJobEditButton' ) ; expect ( isEnabled ) . to . eql ( expectedValue , `Expected "edit" action menu button for DFA job '${ analyticsId }' to be '${ expectedValue ? 'enabled' : 'disabled' }' (got '${ isEnabled ? 'enabled' : 'disabled' }')` ) ; } public async assertJobActionCloneButtonEnabled ( analyticsId , expectedValue ) { await this . ensureJobActionsMenuOpen ( analyticsId ) ; const isEnabled = await testSubjects . isEnabled ( 'mlAnalyticsJobCloneButton' ) ; expect ( isEnabled ) . to . eql ( expectedValue , `Expected "clone" action menu button for DFA job '${ analyticsId }' to be '${ expectedValue ? 'enabled' : 'disabled' }' (got '${ isEnabled ? 'enabled' : 'disabled' }')` ) ; } public async assertJobActionDeleteButtonEnabled ( analyticsId , expectedValue ) { await this . ensureJobActionsMenuOpen ( analyticsId ) ; const isEnabled = await testSubjects . isEnabled ( 'mlAnalyticsJobDeleteButton' ) ; expect ( isEnabled ) . to . eql ( expectedValue , `Expected "delete" action menu button for DFA job '${ analyticsId }' to be '${ expectedValue ? 'enabled' : 'disabled' }' (got '${ isEnabled ? 'enabled' : 'disabled' }')` ) ; } public async openEditFlyout ( analyticsId ) { await this . ensureJobActionsMenuOpen ( analyticsId ) ; await testSubjects . click ( 'mlAnalyticsJobEditButton' ) ; await testSubjects . existOrFail ( 'mlAnalyticsEditFlyout' , { timeout : 5000 } ) ; } public async cloneJob ( analyticsId ) { await this . ensureJobActionsMenuOpen ( analyticsId ) ; await testSubjects . click ( `mlAnalyticsJobCloneButton` ) ; await testSubjects . existOrFail ( 'mlAnalyticsCreationContainer' ) ; } public detailsSelector ( jobId , subSelector ? ) { const row = `mlAnalyticsTableRowDetails-${ jobId }` ; return ! subSelector ? row : `${ row } > ${ subSelector }` ; } public async assertRowDetailsTabExist ( jobId , tabId ) { const selector = `~mlAnalyticsTableRowDetailsTab > ~${ tabId } > ${ jobId }` ; await testSubjects . existOrFail ( selector ) ; } public async withDetailsOpen < T > ( jobId , block ) { await this . ensureDetailsOpen ( jobId ) ; try { return await block ( ) ; } finally { await this . ensureDetailsClosed ( jobId ) ; } } public async ensureDetailsOpen ( jobId ) { await retry . tryForTime ( 10000 , async ( ) => { if ( ! ( await testSubjects . exists ( this . detailsSelector ( jobId ) ) ) ) { await testSubjects . click ( this . rowSelector ( jobId , 'mlAnalyticsTableRowDetailsToggle' ) ) ; await testSubjects . existOrFail ( this . detailsSelector ( jobId ) , { timeout : 1000 } ) ; } } ) ; } public async ensureDetailsClosed ( jobId ) { await retry . tryForTime ( 10000 , async ( ) => { if ( await testSubjects . exists ( this . detailsSelector ( jobId ) ) ) { await testSubjects . click ( this . rowSelector ( jobId , 'mlAnalyticsTableRowDetailsToggle' ) ) ; await testSubjects . missingOrFail ( this . detailsSelector ( jobId ) , { timeout : 1000 } ) ; } } ) ; } public async assertRowDetailsTabsExist ( tabTypeSubject , areaSubjects ) { await retry . tryForTime ( 10000 , async ( ) => { const allTabs = await testSubjects . findAll ( `~${ tabTypeSubject }` , 3 ) ; expect ( allTabs ) . to . have . length ( areaSubjects . length , `Expected number of '${ tabTypeSubject }' to be '${ areaSubjects . length }' (got '${ allTabs . length }')` ) ; for ( const areaSubj of areaSubjects ) { await testSubjects . existOrFail ( `~${ tabTypeSubject }&~${ areaSubj }` , { timeout : 1000 } ) ; } } ) ; } public async assertRowDetailsTabEnabled ( tabSubject , expectedValue ) { const isEnabled = await testSubjects . isEnabled ( tabSubject ) ; expect ( isEnabled ) . to . eql ( expectedValue , `Expected Analytics details tab '${ tabSubject }' to be '${ expectedValue ? 'enabled' : 'disabled' }' (got '${ isEnabled ? 'enabled' : 'disabled' }')` ) ; } public async ensureDetailsTabOpen ( jobId , tabSubject ) { const tabSelector = `~mlAnalyticsTableRowDetailsTab&~${ tabSubject }&~${ jobId }` ; const tabContentSelector = `~mlAnalyticsTableRowDetailsTabContent&~${ tabSubject }&~${ jobId }` ; await retry . tryForTime ( 10000 , async ( ) => { if ( ! ( await testSubjects . exists ( tabContentSelector ) ) ) { await this . assertRowDetailsTabEnabled ( tabSelector , true ) ; await testSubjects . click ( tabSelector ) ; await testSubjects . existOrFail ( tabContentSelector , { timeout : 1000 } ) ; } } ) ; } public detailsSectionSelector ( jobId , sectionSubject ) { const subSelector = `~mlAnalyticsTableRowDetailsSection&~${ sectionSubject }` ; return this . detailsSelector ( jobId , subSelector ) ; } public async assertDetailsSectionExists ( jobId , sectionSubject ) { const selector = this . detailsSectionSelector ( jobId , sectionSubject ) ; await retry . tryForTime ( 10000 , async ( ) => { await testSubjects . existOrFail ( selector , { timeout : 1000 } ) ; } ) ; } public async parseDetailsSectionTable ( el ) { const $ = await el . parseDomContent ( ) ; const vars = { } ; for ( const row of $ ( 'tr' ) . toArray ( ) ) { const [ name , value ] = $ ( row ) . find ( 'td' ) . toArray ( ) ; vars [ $ ( name ) . text ( ) . trim ( ) ] = $ ( value ) . text ( ) . trim ( ) ; } return vars ; } public async assertRowDetailsSectionContent ( jobId , sectionSubject , expectedEntries ) { const sectionSelector = this . detailsSectionSelector ( jobId , sectionSubject ) ; await this . assertDetailsSectionExists ( jobId , sectionSubject ) ; const sectionTable = await testSubjects . find ( `${ sectionSelector }-table` ) ; const parsedSectionTableEntries = await this . parseDetailsSectionTable ( sectionTable ) ; for ( const [ key , value ] of Object . entries ( expectedEntries ) ) { expect ( parsedSectionTableEntries ) . to . have . property ( key ) . eql ( value , `Expected ${ sectionSubject } property '${ key }' to exist with value '${ value }'` ) ; } } public async assertJobDetailsTabContent ( jobId , sections ) { const tabSubject = 'job-details' ; await this . ensureDetailsTabOpen ( jobId , tabSubject ) ; for ( const { section , expectedEntries } of sections ) { await this . assertRowDetailsSectionContent ( jobId , section , expectedEntries ) ; } } public async assertJobStatsTabContent ( jobId ) { const tabSubject = 'job-stats' ; await this . ensureDetailsTabOpen ( jobId , tabSubject ) ; await this . assertDetailsSectionExists ( jobId , 'stats' ) ; await this . assertDetailsSectionExists ( jobId , 'analysisStats' ) ; } public async assertJsonTabContent ( jobId ) { const tabSubject = 'json' ; await this . ensureDetailsTabOpen ( jobId , tabSubject ) ; await testSubjects . existOrFail ( this . detailsSelector ( jobId , 'mlAnalyticsDetailsJsonPreview' ) ) ; } public async assertJobMessagesTabContent ( jobId ) { const tabSubject = 'job-messages' ; await this . ensureDetailsTabOpen ( jobId , tabSubject ) ; await testSubjects . existOrFail ( this . detailsSelector ( jobId , 'mlAnalyticsDetailsJobMessagesTable' ) ) ; } public async assertAnalyticsRowDetails ( jobId , expectedRowDetails ) { return await this . withDetailsOpen ( jobId , async ( ) => { await this . assertRowDetailsTabsExist ( 'mlAnalyticsTableRowDetailsTab' , [ 'job-details' , 'job-stats' , 'json' , 'job-messages' , ] ) ; await this . assertJobDetailsTabContent ( jobId , expectedRowDetails . jobDetails ) ; await this . assertJobStatsTabContent ( jobId ) ; await this . assertJsonTabContent ( jobId ) ; await this . assertJobMessagesTabContent ( jobId ) ; } ) ; } } ) ( ) ; }