diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/connection_status/connection_status.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/connection_status/connection_status.js index 5202683e3417f..73dc09898ba2c 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/connection_status/connection_status.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/components/connection_status/connection_status.js @@ -41,11 +41,15 @@ export function ConnectionStatus({ isConnected, mode }) { return ( - {icon} + + {icon} + - {message} + + {message} + {!isConnected && mode === SNIFF_MODE && ( diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js index 35c51268f71e8..83b85aaefd785 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js @@ -19,6 +19,7 @@ import { EuiInMemoryTable, EuiLink, EuiToolTip, + EuiText, } from '@elastic/eui'; import { reactRouterNavigate } from '@kbn/kibana-react-plugin/public'; import { UIM_SHOW_DETAILS_CLICK } from '../../../constants'; @@ -205,24 +206,47 @@ export class RemoteClusterTable extends Component { defaultMessage: 'Mode', }), sortable: true, - render: (mode) => + render: (mode) => { + let modeMessage; mode === PROXY_MODE - ? mode - : i18n.translate('xpack.remoteClusters.remoteClusterList.table.sniffModeDescription', { - defaultMessage: 'default', - }), + ? (modeMessage = mode) + : (modeMessage = i18n.translate( + 'xpack.remoteClusters.remoteClusterList.table.sniffModeDescription', + { + defaultMessage: 'default', + } + )); + const modeMessageComponent = ( + + + {modeMessage} + + + ); + return modeMessageComponent; + }, }, { field: 'mode', name: i18n.translate('xpack.remoteClusters.remoteClusterList.table.addressesColumnTitle', { defaultMessage: 'Addresses', }), + dataTestSubj: 'remoteClustersAddress', truncateText: true, render: (mode, { seeds, proxyAddress }) => { - if (mode === PROXY_MODE) { - return proxyAddress; - } - return seeds.join(', '); + const clusterAddressString = mode === PROXY_MODE ? proxyAddress : seeds.join(', '); + const connectionMode = ( + + + {clusterAddressString} + + + ); + return connectionMode; }, }, { @@ -236,10 +260,16 @@ export class RemoteClusterTable extends Component { sortable: true, width: '160px', render: (mode, { connectedNodesCount, connectedSocketsCount }) => { - if (mode === PROXY_MODE) { - return connectedSocketsCount; - } - return connectedNodesCount; + const remoteNodesCount = + mode === PROXY_MODE ? connectedSocketsCount : connectedNodesCount; + const connectionMode = ( + + + {remoteNodesCount} + + + ); + return connectionMode; }, }, { diff --git a/x-pack/test/accessibility/apps/cross_cluster_replication.ts b/x-pack/test/accessibility/apps/cross_cluster_replication.ts index e3cbc4d48f845..8081c8fd142b0 100644 --- a/x-pack/test/accessibility/apps/cross_cluster_replication.ts +++ b/x-pack/test/accessibility/apps/cross_cluster_replication.ts @@ -46,7 +46,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.common.navigateToApp('crossClusterReplication'); await PageObjects.crossClusterReplication.clickCreateFollowerIndexButton(); await a11y.testAppSnapshot(); - await PageObjects.crossClusterReplication.createFollowerIndex(testLeader, testFollower); + await PageObjects.crossClusterReplication.createFollowerIndex( + testLeader, + testFollower, + false + ); }); it('follower index flyout', async () => { // https://github.com/elastic/kibana/issues/135503 diff --git a/x-pack/test/functional/apps/remote_clusters/ccs/remote_clusters_index_management_flow.ts b/x-pack/test/functional/apps/remote_clusters/ccs/remote_clusters_index_management_flow.ts new file mode 100644 index 0000000000000..a0b35cbe1c2ef --- /dev/null +++ b/x-pack/test/functional/apps/remote_clusters/ccs/remote_clusters_index_management_flow.ts @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const pageObjects = getPageObjects([ + 'common', + 'remoteClusters', + 'indexManagement', + 'crossClusterReplication', + ]); + const security = getService('security'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + const remoteEs = getService('remoteEs' as 'es'); + const localEs = getService('es'); + + describe('CCS Remote Clusters > Index Management', function () { + const leaderName = 'my-index'; + const followerName = 'my-follower'; + before(async () => { + await security.testUser.setRoles(['superuser']); + // This test is temporarily using superuser because of an issue with the permissions + // of the follower index creation wizard. There is an open issue to address the issue. + // We can change the permissions to use follower_index_user once the issue is fixed. + // https://github.com/elastic/kibana/issues/143720 + // await security.testUser.setRoles(['follower_index_user']); + }); + + describe('Remote Clusters', function () { + before(async () => { + await pageObjects.common.navigateToApp('remoteClusters'); + }); + + it('Verify "ftr-remote" remote cluster exists', async () => { + await retry.waitFor('table to be visible', async () => { + return await testSubjects.isDisplayed('remoteClusterListTable'); + }); + const remotes = await pageObjects.remoteClusters.getRemoteClustersList(); + expect(remotes.length).to.eql(1); + expect(remotes[0].remoteName).to.eql('ftr-remote'); + expect(remotes[0].remoteAddress).to.contain('localhost'); + expect(remotes[0].remoteStatus).to.eql('Connected'); + expect(remotes[0].remoteConnectionCount).to.eql('1'); + expect(remotes[0].remoteMode).to.eql('default'); + }); + }); + + describe('Cross Cluster Replication', function () { + before(async () => { + await remoteEs.indices.create({ + index: leaderName, + body: { + settings: { number_of_shards: 1, soft_deletes: { enabled: true } }, + }, + }); + await pageObjects.common.navigateToApp('crossClusterReplication'); + await retry.waitFor('indices table to be visible', async () => { + return await testSubjects.isDisplayed('createFollowerIndexButton'); + }); + }); + it('Create Follower Index', async () => { + await pageObjects.crossClusterReplication.clickCreateFollowerIndexButton(); + await pageObjects.crossClusterReplication.createFollowerIndex( + leaderName, + followerName, + true, + '1s' + ); + }); + }); + describe('Index Management', function () { + before(async () => { + await remoteEs.index({ + index: leaderName, + body: { a: 'b' }, + }); + await pageObjects.common.navigateToApp('indexManagement'); + await retry.waitForWithTimeout('indice table to be visible', 15000, async () => { + return await testSubjects.isDisplayed('indicesList'); + }); + }); + it('Verify that the follower index is duplicating from the remote.', async () => { + await pageObjects.indexManagement.clickIndiceAt(0); + await pageObjects.indexManagement.performIndexActionInDetailPanel('flush'); + await testSubjects.click('euiFlyoutCloseButton'); + await pageObjects.common.navigateToApp('indexManagement'); + await retry.waitForWithTimeout('indice table to be visible', 15000, async () => { + return await testSubjects.isDisplayed('indicesList'); + }); + + const indicesList = await pageObjects.indexManagement.getIndexList(); + const followerIndex = indicesList[0]; + expect(followerIndex.indexDocuments).to.eql('1'); + }); + }); + + after(async () => { + await localEs.indices.delete({ + index: followerName, + }); + await remoteEs.indices.delete({ + index: leaderName, + }); + await security.testUser.restoreDefaults(); + }); + }); +}; diff --git a/x-pack/test/functional/config.base.js b/x-pack/test/functional/config.base.js index 11ba4d6ebd447..f9953dc861ea0 100644 --- a/x-pack/test/functional/config.base.js +++ b/x-pack/test/functional/config.base.js @@ -499,6 +499,33 @@ export default async function ({ readConfigFile }) { cluster: ['manage', 'manage_ccr'], }, }, + // There is an issue open for follower_index_user permissions not working correctly + // in kibana. + // https://github.com/elastic/kibana/issues/143720 + // follower_index_user: { + // elasticsearch: { + // cluster: ['monitor', 'manage', 'manage_ccr', 'transport_client', 'read_ccr', 'all'], + // indices: [ + // { + // names: ['*'], + // privileges: [ + // 'write', + // 'monitor', + // 'manage_follow_index', + // 'manage_leader_index', + // 'read', + // 'view_index_metadata', + // ], + // }, + // ], + // }, + // kibana: [ + // { + // base: ['all'], + // spaces: ['*'], + // }, + // ], + // }, manage_ilm: { elasticsearch: { diff --git a/x-pack/test/functional/config.ccs.ts b/x-pack/test/functional/config.ccs.ts index d04b542cfb965..62f988d8f2f02 100644 --- a/x-pack/test/functional/config.ccs.ts +++ b/x-pack/test/functional/config.ccs.ts @@ -18,6 +18,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { testFiles: [ require.resolve('./apps/canvas'), require.resolve('./apps/lens/group1'), + require.resolve('./apps/remote_clusters/ccs/remote_clusters_index_management_flow'), require.resolve('./apps/rollup_job'), ], @@ -29,10 +30,11 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.get('security'), remoteEsRoles: { ccs_remote_search: { + cluster: ['manage', 'manage_ccr'], indices: [ { names: ['*'], - privileges: ['read', 'view_index_metadata', 'read_cross_cluster'], + privileges: ['read', 'view_index_metadata', 'read_cross_cluster', 'monitor'], }, ], }, diff --git a/x-pack/test/functional/page_objects/cross_cluster_replication_page.ts b/x-pack/test/functional/page_objects/cross_cluster_replication_page.ts index c51b764f2de06..558de0f6e6412 100644 --- a/x-pack/test/functional/page_objects/cross_cluster_replication_page.ts +++ b/x-pack/test/functional/page_objects/cross_cluster_replication_page.ts @@ -39,9 +39,23 @@ export function CrossClusterReplicationPageProvider({ getService }: FtrProviderC return await testSubjects.isDisplayed('nameInput'); }); }, - async createFollowerIndex(leader: string, follower: string) { + async createFollowerIndex( + leader: string, + follower: string, + advancedSettings: boolean = false, + readPollTimeout?: string + ) { await testSubjects.setValue('leaderIndexInput', leader); await testSubjects.setValue('followerIndexInput', follower); + if (advancedSettings) { + await this.clickAdvancedSettingsToggle(); + await retry.waitFor('advanced settings to be shown', async () => { + return await testSubjects.isDisplayed('readPollTimeoutInput'); + }); + if (readPollTimeout) { + await testSubjects.setValue('readPollTimeoutInput', readPollTimeout); + } + } await testSubjects.click('submitButton'); await retry.waitForWithTimeout('follower index to be in table', 45000, async () => { return await testSubjects.isDisplayed('maxReadReqSize'); @@ -55,5 +69,8 @@ export function CrossClusterReplicationPageProvider({ getService }: FtrProviderC return await testSubjects.isDisplayed('settingsValues'); }); }, + async clickAdvancedSettingsToggle() { + await testSubjects.click('advancedSettingsToggle'); + }, }; } diff --git a/x-pack/test/functional/page_objects/index_management_page.ts b/x-pack/test/functional/page_objects/index_management_page.ts index fc2382eb5a931..6872b9449a2eb 100644 --- a/x-pack/test/functional/page_objects/index_management_page.ts +++ b/x-pack/test/functional/page_objects/index_management_page.ts @@ -41,26 +41,46 @@ export function IndexManagementPageProvider({ getService }: FtrProviderContext) }); }, + async performIndexActionInDetailPanel(action: string) { + await this.clickContextMenuInDetailPanel(); + if (action === 'flush') { + await testSubjects.click('flushIndexMenuButton'); + } + }, + + async clickContextMenuInDetailPanel() { + await testSubjects.click('indexActionsContextMenuButton'); + }, + async getIndexList() { const table = await find.byCssSelector('table'); - const $ = await table.parseDomContent(); - const indexList = await $.findTestSubjects('indexTableRow') - .toArray() - .map((row) => { + const rows = await table.findAllByTestSubject('indexTableRow'); + return await Promise.all( + rows.map(async (row) => { return { - indexName: $(row).findTestSubject('indexTableIndexNameLink').text(), - indexHealth: $(row).findTestSubject('indexTableCell-health').text(), - indexStatus: $(row).findTestSubject('indexTableCell-status').text(), - indexPrimary: $(row).findTestSubject('indexTableCell-primary').text(), - indexReplicas: $(row).findTestSubject('indexTableCell-replica').text(), - indexDocuments: $(row) - .findTestSubject('indexTableCell-documents') - .text() - .replace('documents', ''), - indexSize: $(row).findTestSubject('indexTableCell-size').text(), + indexLink: await row.findByTestSubject('indexTableIndexNameLink'), + indexName: await ( + await row.findByTestSubject('indexTableIndexNameLink') + ).getVisibleText(), + indexHealth: await ( + await row.findByTestSubject('indexTableCell-health') + ).getVisibleText(), + indexStatus: await ( + await row.findByTestSubject('indexTableCell-status') + ).getVisibleText(), + indexPrimary: await ( + await row.findByTestSubject('indexTableCell-primary') + ).getVisibleText(), + indexReplicas: await ( + await row.findByTestSubject('indexTableCell-replica') + ).getVisibleText(), + indexDocuments: await ( + await (await row.findByTestSubject('indexTableCell-documents')).getVisibleText() + ).replace('documents', ''), + indexSize: await (await row.findByTestSubject('indexTableCell-size')).getVisibleText(), }; - }); - return indexList; + }) + ); }, async changeTabs( diff --git a/x-pack/test/functional/page_objects/remote_clusters_page.ts b/x-pack/test/functional/page_objects/remote_clusters_page.ts index 9dfa2db9ce6e8..b6ce2eb4a39bd 100644 --- a/x-pack/test/functional/page_objects/remote_clusters_page.ts +++ b/x-pack/test/functional/page_objects/remote_clusters_page.ts @@ -31,5 +31,31 @@ export function RemoteClustersPageProvider({ getService }: FtrProviderContext) { await comboBox.setCustom('comboBoxInput', seedNode); await testSubjects.click('remoteClusterFormSaveButton'); }, + async getRemoteClustersList() { + const table = await testSubjects.find('remoteClusterListTable'); + const rows = await table.findAllByCssSelector('.euiTableRow'); + return await Promise.all( + rows.map(async (row) => { + return { + remoteLink: await row.findByTestSubject('remoteClustersTableListClusterLink'), + remoteName: await ( + await row.findByTestSubject('remoteClustersTableListClusterLink') + ).getVisibleText(), + remoteStatus: await ( + await row.findByTestSubject('remoteClusterConnectionStatusMessage') + ).getVisibleText(), + remoteMode: await ( + await row.findByTestSubject('remoteClusterConnectionModeMessage') + ).getVisibleText(), + remoteAddress: await ( + await row.findByTestSubject('remoteClusterConnectionAddressMessage') + ).getVisibleText(), + remoteConnectionCount: await ( + await row.findByTestSubject('remoteClusterNodeCountMessage') + ).getVisibleText(), + }; + }) + ); + }, }; }