diff --git a/x-pack/platform/plugins/private/snapshot_restore/public/application/components/collapsible_lists/collapsible_indices_list.tsx b/x-pack/platform/plugins/private/snapshot_restore/public/application/components/collapsible_lists/collapsible_indices_list.tsx index 7362a6634565a..8cc29eab33dad 100644 --- a/x-pack/platform/plugins/private/snapshot_restore/public/application/components/collapsible_lists/collapsible_indices_list.tsx +++ b/x-pack/platform/plugins/private/snapshot_restore/public/application/components/collapsible_lists/collapsible_indices_list.tsx @@ -56,7 +56,10 @@ export const CollapsibleIndicesList: React.FunctionComponent = ({ indices values={{ count: hiddenItemsCount }} /> )}{' '} - + ) : null} diff --git a/x-pack/platform/plugins/private/snapshot_restore/public/application/components/policy_form/steps/step_settings/fields/indices_and_data_streams_field/indices_and_data_streams_field.tsx b/x-pack/platform/plugins/private/snapshot_restore/public/application/components/policy_form/steps/step_settings/fields/indices_and_data_streams_field/indices_and_data_streams_field.tsx index 6ad3e1704720f..4ebf98bf00efb 100644 --- a/x-pack/platform/plugins/private/snapshot_restore/public/application/components/policy_form/steps/step_settings/fields/indices_and_data_streams_field/indices_and_data_streams_field.tsx +++ b/x-pack/platform/plugins/private/snapshot_restore/public/application/components/policy_form/steps/step_settings/fields/indices_and_data_streams_field/indices_and_data_streams_field.tsx @@ -211,6 +211,7 @@ export const IndicesAndDataStreamsField: FunctionComponent = ({ setSelectIndicesMode('custom'); onUpdate({ indices: indexPatterns.join(',') }); }} + data-test-subj="useIndexPatternsButton" > = }); } }} + data-test-subj="allDsAndIndicesToggle" /> {isAllIndicesAndDataStreams ? null : ( @@ -281,6 +282,7 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent = setSelectIndicesMode('custom'); updateRestoreSettings({ indices: restoreIndexPatterns.join(',') }); }} + data-test-subj="restoreIndexPatternsButton" > = }); } }} + data-test-subj="restoreRenameToggle" /> {!isRenamingIndices ? null : ( @@ -510,6 +513,7 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent = renamePattern: e.target.value, }); }} + data-test-subj="capturePattern" /> @@ -536,6 +540,7 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent = renameReplacement: e.target.value, }); }} + data-test-subj="replacementPattern" /> diff --git a/x-pack/platform/plugins/private/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_review.tsx b/x-pack/platform/plugins/private/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_review.tsx index 09feaf61ca9e7..b3e3879e332b5 100644 --- a/x-pack/platform/plugins/private/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_review.tsx +++ b/x-pack/platform/plugins/private/snapshot_restore/public/application/components/restore_snapshot_form/steps/step_review.tsx @@ -301,7 +301,7 @@ export const RestoreSnapshotStepReview: React.FunctionComponent = ({ return ( - +

= ( {/* Step title and doc link */} - +

= ({ ); }, disabled: Boolean(policyDetails.policy.inProgress), + 'data-test-subj': 'policyActionMenuRunPolicy', }, { name: i18n.translate( diff --git a/x-pack/platform/plugins/private/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx b/x-pack/platform/plugins/private/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx index b3c510e0fa0c7..db4efbbf80965 100644 --- a/x-pack/platform/plugins/private/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx +++ b/x-pack/platform/plugins/private/snapshot_restore/public/application/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx @@ -233,6 +233,7 @@ export const SnapshotDetails: React.FunctionComponent = ({ snapshotDetails.state !== SNAPSHOT_STATE.SUCCESS && snapshotDetails.state !== SNAPSHOT_STATE.PARTIAL } + data-test-subj="restoreSnapshotButton" > { const security = getService('security'); describe('Snapshot restore', function () { + const REPOSITORY = 'my-repository'; before(async () => { await security.testUser.setRoles(['snapshot_restore_user'], { skipBrowserRefresh: true }); await pageObjects.common.navigateToApp('snapshotRestore'); // Create a repository await es.snapshot.createRepository({ - name: 'my-repository', + name: REPOSITORY, verify: true, repository: { type: 'fs', @@ -32,50 +33,165 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }, }, }); + }); + + describe('Snapshot', () => { + before(async () => { + // Create a snapshot + await es.snapshot.create({ + snapshot: 'my-snapshot', + repository: REPOSITORY, + }); - // Create a snapshot - await es.snapshot.create({ - snapshot: 'my-snapshot', - repository: 'my-repository', + // Wait for snapshot to be ready + await pageObjects.common.sleep(2000); + + // Refresh page so that the snapshot shows up in the snapshots table + await browser.refresh(); }); - // Wait for snapshot to be ready - await pageObjects.common.sleep(2000); + after(async () => { + await es.snapshot.delete({ + snapshot: 'my-snapshot', + repository: REPOSITORY, + }); + }); + + it('Renders the Snapshot restore form', async () => { + const snapshots = await pageObjects.snapshotRestore.getSnapshotList(); + const snapshotRestoreButton = await snapshots[0].snapshotRestore; + // Open the Snapshot restore form + await snapshotRestoreButton.click(); + + // Go to second step (Index settings) + await testSubjects.click('nextButton'); + + // Verify that the Index Settings editor is rendered (uses CodeEditor) + await testSubjects.click('modifyIndexSettingsSwitch'); + expect(await testSubjects.exists('indexSettingsEditor')).to.be(true); - // Refresh page so that the snapshot shows up in the snapshots table - await browser.refresh(); + // Close Index Settings editor + await testSubjects.click('modifyIndexSettingsSwitch'); + + // Go to final step (Review) + await testSubjects.click('nextButton'); + + // Verify that Restore button exists + expect(await testSubjects.exists('restoreButton')).to.be(true); + }); }); - it('Renders the Snapshot restore form', async () => { - const snapshots = await pageObjects.snapshotRestore.getSnapshotList(); - const snapshotRestoreButton = await snapshots[0].snapshotRestore; - // Open the Snapshot restore form - await snapshotRestoreButton.click(); + describe('Allows to create and restore a snapshot from a Logsdb index', () => { + const logsDbIndex = 'logsdb-index'; + const policyId = 'testPolicy'; + const snapshotPrefx = 'logsdb-snap'; - // Go to second step (Index settings) - await testSubjects.click('nextButton'); + before(async () => { + await es.indices.create({ + index: logsDbIndex, + settings: { + mode: 'logsdb', + }, + }); + await pageObjects.common.navigateToApp('snapshotRestore'); + // Create a policy + await pageObjects.snapshotRestore.navToPolicies(); + await pageObjects.snapshotRestore.fillCreateNewPolicyPageOne( + policyId, + `<${snapshotPrefx}-{now/d}>` + ); + await pageObjects.snapshotRestore.fillCreateNewPolicyPageTwo(); + await pageObjects.snapshotRestore.fillCreateNewPolicyPageThree(); + await pageObjects.snapshotRestore.submitNewPolicy(); + await pageObjects.snapshotRestore.closeFlyout(); + }); - // Verify that the Index Settings editor is rendered (uses CodeEditor) - await testSubjects.click('modifyIndexSettingsSwitch'); - expect(await testSubjects.exists('indexSettingsEditor')).to.be(true); + after(async () => { + // Delete the logdb index + await es.indices.delete({ + index: logsDbIndex, + }); + // Delete policy + await es.slm.deleteLifecycle({ + policy_id: policyId, + }); + await es.snapshot.delete({ + snapshot: `${snapshotPrefx}-*`, + repository: REPOSITORY, + }); + await es.indices.delete({ + index: `restored_${logsDbIndex}`, + }); + }); - // Close Index Settings editor - await testSubjects.click('modifyIndexSettingsSwitch'); + it('create snapshot', async () => { + // Verify there are no snapshots + await pageObjects.snapshotRestore.navToSnapshots(); + expect(await testSubjects.exists('emptyPrompt')).to.be(true); - // Go to final step (Review) - await testSubjects.click('nextButton'); + // Run policy snapshot + await pageObjects.snapshotRestore.navToPolicies(); - // Verify that Restore button exists - expect(await testSubjects.exists('restoreButton')).to.be(true); + await pageObjects.snapshotRestore.clickPolicyNameLink(policyId); + await pageObjects.snapshotRestore.clickPolicyActionButton(); + await pageObjects.snapshotRestore.clickRunPolicy(); + await pageObjects.snapshotRestore.clickConfirmationModal(); + await pageObjects.snapshotRestore.closeFlyout(); + + // Wait for snapshot to be ready + await pageObjects.common.sleep(2000); + + // Open snapshot info flyout + await pageObjects.snapshotRestore.navToSnapshots(); + await pageObjects.header.waitUntilLoadingHasFinished(); + expect(await testSubjects.exists('snapshotList')).to.be(true); + + // Reload page to make sure snapshot is complete + await testSubjects.click('reloadButton'); + await pageObjects.header.waitUntilLoadingHasFinished(); + + // Verify that one snapshot has been created + const snapshots = await pageObjects.snapshotRestore.getSnapshotList(); + expect(snapshots.length).to.be(1); + + // Verify that snaphot has been created + const snapshotLink = snapshots[0].snapshotLink; + await snapshotLink.click(); + + // Verify snapshot exists and contains the logsdb index + expect(await testSubjects.exists('detailTitle')).to.be(true); + expect(await testSubjects.getVisibleText('detailTitle')).to.contain(snapshotPrefx); + await pageObjects.snapshotRestore.clickShowCollapsedIndicesIfPresent(); + expect(await testSubjects.getVisibleText('indices')).to.contain(logsDbIndex); + await pageObjects.snapshotRestore.closeSnaphsotFlyout(); + }); + + it('restore snapshot', async () => { + // Verify there are not restore snapshots + await pageObjects.snapshotRestore.navToRestoreStatus(); + await pageObjects.header.waitUntilLoadingHasFinished(); + expect(await testSubjects.exists('noRestoredSnapshotsHeader')).to.be(true); + + // restore snapshot + await pageObjects.snapshotRestore.navToSnapshots(); + await pageObjects.header.waitUntilLoadingHasFinished(); + + const snapshots = await pageObjects.snapshotRestore.getSnapshotList(); + const snapshotLink = snapshots[0].snapshotLink; + await snapshotLink.click(); + await pageObjects.snapshotRestore.restoreSnapshot(logsDbIndex, true); + + // Verify snapshot has been restored + await pageObjects.snapshotRestore.navToRestoreStatus(false); + expect(await testSubjects.getVisibleText('restoreList')).to.contain( + `restored_${logsDbIndex}` + ); + }); }); after(async () => { - await es.snapshot.delete({ - snapshot: 'my-snapshot', - repository: 'my-repository', - }); await es.snapshot.deleteRepository({ - name: 'my-repository', + name: REPOSITORY, }); await security.testUser.restoreDefaults(); }); diff --git a/x-pack/test/functional/config.base.js b/x-pack/test/functional/config.base.js index 24a4a63d53dda..962cea76f367e 100644 --- a/x-pack/test/functional/config.base.js +++ b/x-pack/test/functional/config.base.js @@ -625,6 +625,13 @@ export default async function ({ readConfigFile }) { 'manage_slm', 'cluster:admin/snapshot', 'cluster:admin/repository', + 'manage_index_templates', + ], + indices: [ + { + names: ['*'], + privileges: ['all'], + }, ], }, kibana: [ diff --git a/x-pack/test/functional/page_objects/snapshot_restore_page.ts b/x-pack/test/functional/page_objects/snapshot_restore_page.ts index 8c41f4a15c7f2..f4e699e928182 100644 --- a/x-pack/test/functional/page_objects/snapshot_restore_page.ts +++ b/x-pack/test/functional/page_objects/snapshot_restore_page.ts @@ -10,6 +10,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; export function SnapshotRestorePageProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const retry = getService('retry'); + const find = getService('find'); return { async appTitleText() { @@ -44,28 +45,43 @@ export function SnapshotRestorePageProvider({ getService }: FtrProviderContext) 'Wait for register repository button to be on page', 10000, async () => { - return await testSubjects.isDisplayed('noRestoredSnapshotsHeader'); + return await testSubjects.isDisplayed( + emptyList ? 'noRestoredSnapshotsHeader' : 'restoreList' + ); } ); }, async navToSnapshots(emptyList: boolean = true) { await testSubjects.click('snapshots_tab'); await retry.waitForWithTimeout('Wait for snapshot list to be on page', 10000, async () => { - return await testSubjects.isDisplayed(emptyList ? 'snapshotListEmpty' : 'snapshotList'); + return await testSubjects.isDisplayed(emptyList ? 'emptyPrompt' : 'snapshotList'); }); }, - async fillCreateNewPolicyPageOne(policyName: string, snapshotName: string) { + async fillCreateNewPolicyPageOne( + policyName: string, + snapshotName: string, + repositoryName?: string + ) { await testSubjects.click('createPolicyButton'); await testSubjects.setValue('nameInput', policyName); await testSubjects.setValue('snapshotNameInput', snapshotName); + if (repositoryName) { + await testSubjects.selectValue('repositorySelect', repositoryName); + } await testSubjects.click('nextButton'); await retry.waitFor('all indices to be visible', async () => { return await testSubjects.isDisplayed('allIndicesToggle'); }); }, - async fillCreateNewPolicyPageTwo() { + async fillCreateNewPolicyPageTwo(singleIndexToSelect?: string) { + if (singleIndexToSelect) { + await testSubjects.click('allIndicesToggle'); + await testSubjects.click('useIndexPatternsButton'); + await testSubjects.setValue('comboBoxSearchInput', singleIndexToSelect); + await testSubjects.pressEnter('comboBoxSearchInput'); + } await testSubjects.click('nextButton'); await retry.waitFor('expire after value input to be visible', async () => { return await testSubjects.isDisplayed('expireAfterValueInput'); @@ -92,6 +108,18 @@ export function SnapshotRestorePageProvider({ getService }: FtrProviderContext) return await testSubjects.isDisplayed('policyLink'); }); }, + async closeSnaphsotFlyout() { + await testSubjects.click('euiFlyoutCloseButton'); + await retry.waitFor('snapshot table to be visible', async () => { + return await testSubjects.isDisplayed('snapshotLink'); + }); + }, + async closeRepositoriesFlyout() { + await testSubjects.click('srRepositoryDetailsFlyoutCloseButton'); + await retry.waitFor('repositories table to be visible', async () => { + return await testSubjects.isDisplayed('repositoryLink'); + }); + }, async getSnapshotList() { const table = await testSubjects.find('snapshotTable'); const rows = await table.findAllByTestSubject('row'); @@ -137,5 +165,60 @@ export function SnapshotRestorePageProvider({ getService }: FtrProviderContext) }); return await testSubjects.getVisibleText('cleanupCodeBlock'); }, + + async clickPolicyNameLink(name: string): Promise { + await find.clickByLinkText(name); + }, + + async clickPolicyActionButton() { + await testSubjects.click('policyActionMenuButton'); + await retry.waitFor('run button to be visible', async () => { + return await testSubjects.isDisplayed('policyActionMenuRunPolicy'); + }); + }, + + async clickRunPolicy() { + await testSubjects.click('policyActionMenuRunPolicy'); + await retry.waitFor('confirm modal to be visible', async () => { + return await testSubjects.isDisplayed('confirmModalConfirmButton'); + }); + }, + + async clickConfirmationModal() { + await testSubjects.click('confirmModalConfirmButton'); + }, + + async clickShowCollapsedIndicesIfPresent() { + if (await testSubjects.exists('collapsibleIndicesArrow')) { + await testSubjects.click('collapsibleIndicesArrow'); + } + }, + + async restoreSnapshot(indexName: string, rename: boolean = false) { + await testSubjects.click('restoreSnapshotButton'); + await retry.waitFor('restore form to be visible', async () => { + return await testSubjects.isDisplayed('snapshotRestoreApp'); + }); + + await testSubjects.click('allDsAndIndicesToggle'); + await testSubjects.click('restoreIndexPatternsButton'); + await testSubjects.setValue('comboBoxSearchInput', indexName); + await testSubjects.pressEnter('comboBoxSearchInput'); + + if (rename) { + await testSubjects.click('restoreRenameToggle'); + await testSubjects.setValue('capturePattern', `${indexName}(.*)`); + await testSubjects.setValue('replacementPattern', `restored_${indexName}$1`); + } + await testSubjects.click('nextButton'); + await retry.waitFor('index settings to be visible', async () => { + return await testSubjects.isDisplayed('indexSettingsTitle'); + }); + await testSubjects.click('nextButton'); + await retry.waitFor('review step to be visible', async () => { + return await testSubjects.isDisplayed('reviewSnapshotTitle'); + }); + await testSubjects.click('restoreButton'); + }, }; }