diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index 599289b7c6eacc..2960327b9087e9 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -225,7 +225,7 @@ enabled: - x-pack/test/functional_basic/apps/ml/data_visualizer/group2/config.ts - x-pack/test/functional_basic/apps/ml/data_visualizer/group3/config.ts - x-pack/test/functional_basic/apps/transform/creation/index_pattern/config.ts - - x-pack/test/functional_basic/apps/transform/start_reset_delete/config.ts + - x-pack/test/functional_basic/apps/transform/actions/config.ts - x-pack/test/functional_basic/apps/transform/edit_clone/config.ts - x-pack/test/functional_basic/apps/transform/creation/runtime_mappings_saved_search/config.ts - x-pack/test/functional_basic/apps/transform/permissions/config.ts @@ -291,7 +291,7 @@ enabled: - x-pack/test/functional/apps/status_page/config.ts - x-pack/test/functional/apps/transform/creation/index_pattern/config.ts - x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/config.ts - - x-pack/test/functional/apps/transform/start_reset_delete/config.ts + - x-pack/test/functional/apps/transform/actions/config.ts - x-pack/test/functional/apps/transform/edit_clone/config.ts - x-pack/test/functional/apps/transform/permissions/config.ts - x-pack/test/functional/apps/transform/feature_controls/config.ts diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx index fd79e8eb9e2e9e..3c74cf450b4b94 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx @@ -114,6 +114,7 @@ export const TransformManagement: FC = () => { diff --git a/x-pack/plugins/transform/readme.md b/x-pack/plugins/transform/readme.md index 7eb1f59250be9d..8c25f2ddd8ac46 100644 --- a/x-pack/plugins/transform/readme.md +++ b/x-pack/plugins/transform/readme.md @@ -122,7 +122,7 @@ With PATH_TO_CONFIG and other options as follows. edit, clone | `test/functional/apps/transform/edit_clone/config.ts` feature controls | `test/functional/apps/transform/feature_controls/config.ts` permissions | `test/functional/apps/transform/permissions/config.ts` - start, reset, delete | `test/functional/apps/transform/start_reset_delete/config.ts` + actions | `test/functional/apps/transform/actions/config.ts` 1. Functional UI tests with `Basic` license: @@ -133,7 +133,7 @@ With PATH_TO_CONFIG and other options as follows. edit, clone | `test/functional_basic/apps/transform/edit_clone/config.ts` feature controls | `test/functional_basic/apps/transform/feature_controls/config.ts` permissions | `test/functional_basic/apps/transform/permissions/config.ts` - start, reset, delete | `test/functional_basic/apps/transform/start_reset_delete/config.ts` + actions | `test/functional_basic/apps/transform/actions/config.ts` 1. API integration tests with `Trial` license: diff --git a/x-pack/test/api_integration/apis/transform/reauthorize_transforms.ts b/x-pack/test/api_integration/apis/transform/reauthorize_transforms.ts index 274c1b42a4d07a..a33ef09063d37a 100644 --- a/x-pack/test/api_integration/apis/transform/reauthorize_transforms.ts +++ b/x-pack/test/api_integration/apis/transform/reauthorize_transforms.ts @@ -21,9 +21,6 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertestWithoutAuth'); const transform = getService('transform'); - // If transform was created with sufficient -> should still authorize and start - // If transform was created with insufficient -> should still authorize and start - function getTransformIdByUser(username: USER) { return `transform-by-${username}`; } @@ -51,6 +48,8 @@ export default ({ getService }: FtrProviderContext) => { await transform.api.deleteIndices(destinationIndex); } + // If transform was created with sufficient permissions -> should create and start + // If transform was created with insufficient permissions -> should create but not start describe('/api/transform/reauthorize_transforms', function () { const apiKeysForTransformUsers = new Map(); diff --git a/x-pack/test/functional/apps/transform/start_reset_delete/config.ts b/x-pack/test/functional/apps/transform/actions/config.ts similarity index 96% rename from x-pack/test/functional/apps/transform/start_reset_delete/config.ts rename to x-pack/test/functional/apps/transform/actions/config.ts index edf34d16785c4c..3b7948e9c66bc5 100644 --- a/x-pack/test/functional/apps/transform/start_reset_delete/config.ts +++ b/x-pack/test/functional/apps/transform/actions/config.ts @@ -14,7 +14,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.getAll(), testFiles: [require.resolve('.')], junit: { - reportName: 'Chrome X-Pack UI Functional Tests - transform - start reset & delete', + reportName: 'Chrome X-Pack UI Functional Tests - transform - actions', }, }; } diff --git a/x-pack/test/functional/apps/transform/start_reset_delete/deleting.ts b/x-pack/test/functional/apps/transform/actions/deleting.ts similarity index 100% rename from x-pack/test/functional/apps/transform/start_reset_delete/deleting.ts rename to x-pack/test/functional/apps/transform/actions/deleting.ts diff --git a/x-pack/test/functional/apps/transform/start_reset_delete/index.ts b/x-pack/test/functional/apps/transform/actions/index.ts similarity index 92% rename from x-pack/test/functional/apps/transform/start_reset_delete/index.ts rename to x-pack/test/functional/apps/transform/actions/index.ts index 1a606339eb82a9..8a156b4bba84d4 100644 --- a/x-pack/test/functional/apps/transform/start_reset_delete/index.ts +++ b/x-pack/test/functional/apps/transform/actions/index.ts @@ -11,7 +11,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const transform = getService('transform'); - describe('transform - start reset & delete', function () { + describe('transform - actions', function () { this.tags('transform'); before(async () => { @@ -33,6 +33,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { }); loadTestFile(require.resolve('./deleting')); + loadTestFile(require.resolve('./reauthorizing')); loadTestFile(require.resolve('./resetting')); loadTestFile(require.resolve('./starting')); }); diff --git a/x-pack/test/functional/apps/transform/actions/reauthorizing.ts b/x-pack/test/functional/apps/transform/actions/reauthorizing.ts new file mode 100644 index 00000000000000..89acef449c2e7c --- /dev/null +++ b/x-pack/test/functional/apps/transform/actions/reauthorizing.ts @@ -0,0 +1,221 @@ +/* + * 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 { TRANSFORM_HEALTH_LABEL, TRANSFORM_STATE } from '@kbn/transform-plugin/common/constants'; +import type { + TransformLatestConfig, + TransformPivotConfig, +} from '@kbn/transform-plugin/common/types/transform'; +import type { SecurityCreateApiKeyResponse } from '@elastic/elasticsearch/lib/api/types'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; +import { getLatestTransformConfig, getPivotTransformConfig } from '../helpers'; +import { USER } from '../../../services/transform/security_common'; +import { COMMON_REQUEST_HEADERS } from '../../../services/ml/common_api'; + +interface TestDataPivot { + suiteTitle: string; + originalConfig: TransformPivotConfig; + mode: 'batch' | 'continuous'; + type: 'pivot'; + expected: { + originalState: object; + reauthorizeEnabled: boolean; + reauthorizedState: object; + }; + created_by_user: USER; + current_user: USER; +} + +interface TestDataLatest { + suiteTitle: string; + originalConfig: TransformLatestConfig; + mode: 'batch' | 'continuous'; + type: 'latest'; + expected: { + originalState: object; + reauthorizeEnabled: boolean; + reauthorizedState: object; + }; + created_by_user: USER; + current_user: USER; +} + +type TestData = TestDataPivot | TestDataLatest; + +function generateHeaders(apiKey: SecurityCreateApiKeyResponse) { + return { + ...COMMON_REQUEST_HEADERS, + 'es-secondary-authorization': `ApiKey ${apiKey.encoded}`, + }; +} + +export default function ({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const transform = getService('transform'); + + const apiKeysForTransformUsers = new Map(); + + describe('reauthorizing', function () { + const PREFIX = 'reauthorizing'; + const testDataList: TestData[] = [ + { + suiteTitle: 'continuous pivot transform (created by viewer, viewed by viewer)', + originalConfig: getPivotTransformConfig(`${PREFIX}-${USER.TRANSFORM_VIEWER}-1`, true), + mode: 'continuous', + type: 'pivot', + expected: { + originalState: { status: TRANSFORM_STATE.STOPPED, health: TRANSFORM_HEALTH_LABEL.red }, + reauthorizeEnabled: false, + reauthorizedState: { + status: TRANSFORM_STATE.STARTED, + health: TRANSFORM_HEALTH_LABEL.green, + }, + }, + created_by_user: USER.TRANSFORM_VIEWER, + current_user: USER.TRANSFORM_VIEWER, + }, + { + suiteTitle: 'batch pivot transform (created by viewer, viewed by poweruser)', + originalConfig: getPivotTransformConfig(PREFIX, false), + mode: 'batch', + type: 'pivot', + expected: { + originalState: { status: TRANSFORM_STATE.STOPPED, health: TRANSFORM_HEALTH_LABEL.red }, + reauthorizeEnabled: true, + reauthorizedState: { + status: TRANSFORM_STATE.STOPPED, + progress: '100', + health: TRANSFORM_HEALTH_LABEL.green, + }, + }, + created_by_user: USER.TRANSFORM_VIEWER, + current_user: USER.TRANSFORM_POWERUSER, + }, + { + suiteTitle: 'continuous pivot transform (created by viewer, authorized by poweruser)', + originalConfig: getPivotTransformConfig(`${PREFIX}-${USER.TRANSFORM_VIEWER}-2`, true), + mode: 'continuous', + type: 'pivot', + expected: { + originalState: { status: TRANSFORM_STATE.STOPPED, health: TRANSFORM_HEALTH_LABEL.red }, + reauthorizeEnabled: true, + reauthorizedState: { + status: TRANSFORM_STATE.STARTED, + health: TRANSFORM_HEALTH_LABEL.green, + }, + }, + created_by_user: USER.TRANSFORM_VIEWER, + current_user: USER.TRANSFORM_POWERUSER, + }, + { + suiteTitle: 'continuous latest transform (created by poweruser, viewed by viewer)', + originalConfig: getLatestTransformConfig(`${PREFIX}-${USER.TRANSFORM_POWERUSER}-1`, true), + mode: 'continuous', + type: 'latest', + expected: { + originalState: { status: TRANSFORM_STATE.STARTED, health: TRANSFORM_HEALTH_LABEL.green }, + reauthorizeEnabled: false, + reauthorizedState: { + status: TRANSFORM_STATE.STARTED, + health: TRANSFORM_HEALTH_LABEL.green, + }, + }, + created_by_user: USER.TRANSFORM_POWERUSER, + current_user: USER.TRANSFORM_VIEWER, + }, + ]; + + before(async () => { + const apiKeyForTransformUsers = + await transform.securityCommon.createApiKeyForTransformUsers(); + + apiKeyForTransformUsers.forEach(({ user, apiKey }) => + apiKeysForTransformUsers.set(user.name as USER, apiKey) + ); + + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/ecommerce'); + await transform.testResources.createIndexPatternIfNeeded('ft_ecommerce', 'order_date'); + + for (const testData of testDataList) { + await transform.api.createTransform(testData.originalConfig.id, testData.originalConfig, { + deferValidation: true, + // Create transforms with secondary authorization headers + headers: generateHeaders(apiKeysForTransformUsers.get(testData.created_by_user)!), + }); + // For transforms created with insufficient permissions, they can be created but not started + // so we should not assert that the api call is successful here + await transform.api.startTransform(testData.originalConfig.id, false); + } + await transform.testResources.setKibanaTimeZoneToUTC(); + }); + + after(async () => { + await transform.securityCommon.clearAllTransformApiKeys(); + + for (const testData of testDataList) { + await transform.testResources.deleteIndexPatternByTitle(testData.originalConfig.dest.index); + await transform.api.deleteIndices(testData.originalConfig.dest.index); + } + + await transform.api.cleanTransformIndices(); + await transform.testResources.deleteIndexPatternByTitle('ft_ecommerce'); + }); + + for (const testData of testDataList) { + const transformId = testData.originalConfig.id; + + describe(`${testData.suiteTitle}`, function () { + it('reauthorize transform', async () => { + await transform.securityUI.loginAs(testData.current_user); + + await transform.testExecution.logTestStep('should load the home page'); + await transform.navigation.navigateTo(); + await transform.management.assertTransformListPageExists(); + + await transform.testExecution.logTestStep('should display the transforms table'); + await transform.management.assertTransformsTableExists(); + + await transform.testExecution.logTestStep( + 'should display the transforms reauthorize callout' + ); + await transform.management.assertTransformsReauthorizeCalloutExists(); + + await transform.testExecution.logTestStep( + 'should display the original transform in the transform list' + ); + await transform.table.filterWithSearchString(transformId, 1); + await transform.table.assertTransformRowFields( + transformId, + testData.expected.originalState + ); + + if (testData.expected.reauthorizeEnabled) { + await transform.testExecution.logTestStep('should reauthorize the transform'); + await transform.table.assertTransformRowActionEnabled( + transformId, + 'Reauthorize', + testData.expected.reauthorizeEnabled + ); + await transform.table.clickTransformRowAction(transformId, 'Reauthorize'); + await transform.table.confirmReauthorizeTransform(); + + await transform.table.assertTransformRowFields( + transformId, + testData.expected.reauthorizedState + ); + await transform.testExecution.logTestStep('should not show Reauthorize action anymore'); + await transform.table.assertTransformRowActionMissing(transformId, 'Reauthorize'); + } else { + await transform.testExecution.logTestStep('should show disabled action menu button'); + await transform.table.assertTransformRowActionsButtonEnabled(transformId, false); + } + await transform.table.clearSearchString(testDataList.length); + }); + }); + } + }); +} diff --git a/x-pack/test/functional/apps/transform/start_reset_delete/resetting.ts b/x-pack/test/functional/apps/transform/actions/resetting.ts similarity index 100% rename from x-pack/test/functional/apps/transform/start_reset_delete/resetting.ts rename to x-pack/test/functional/apps/transform/actions/resetting.ts diff --git a/x-pack/test/functional/apps/transform/start_reset_delete/starting.ts b/x-pack/test/functional/apps/transform/actions/starting.ts similarity index 100% rename from x-pack/test/functional/apps/transform/start_reset_delete/starting.ts rename to x-pack/test/functional/apps/transform/actions/starting.ts diff --git a/x-pack/test/functional/services/transform/api.ts b/x-pack/test/functional/services/transform/api.ts index 4c6944b64a91d6..8fb984e72e0c5b 100644 --- a/x-pack/test/functional/services/transform/api.ts +++ b/x-pack/test/functional/services/transform/api.ts @@ -272,10 +272,13 @@ export function TransformAPIProvider({ getService }: FtrProviderContext) { }); }, - async startTransform(transformId: string) { + async startTransform(transformId: string, assertSuccess = true) { log.debug(`Starting transform '${transformId}' ...`); const { body, status } = await esSupertest.post(`/_transform/${transformId}/_start`); - this.assertResponseStatusCode(200, status, body); + + if (assertSuccess) { + this.assertResponseStatusCode(200, status, body); + } }, async stopTransform(transformId: string) { diff --git a/x-pack/test/functional/services/transform/management.ts b/x-pack/test/functional/services/transform/management.ts index 1855159f478b61..4d5a54f38c7f86 100644 --- a/x-pack/test/functional/services/transform/management.ts +++ b/x-pack/test/functional/services/transform/management.ts @@ -32,6 +32,10 @@ export function TransformManagementProvider({ getService }: FtrProviderContext) await testSubjects.existOrFail('transformCreateFirstButton'); }, + async assertTransformsReauthorizeCalloutExists() { + await testSubjects.existOrFail('transformPageReauthorizeCallout'); + }, + async assertCreateFirstTransformButtonEnabled(expectedValue: boolean) { const isEnabled = await testSubjects.isEnabled('transformCreateFirstButton'); expect(isEnabled).to.eql( diff --git a/x-pack/test/functional/services/transform/transform_table.ts b/x-pack/test/functional/services/transform/transform_table.ts index 135aa18af9b409..2af0d129ed51c9 100644 --- a/x-pack/test/functional/services/transform/transform_table.ts +++ b/x-pack/test/functional/services/transform/transform_table.ts @@ -9,7 +9,15 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; -type TransformRowActionName = 'Clone' | 'Delete' | 'Discover' | 'Edit' | 'Reset' | 'Start' | 'Stop'; +type TransformRowActionName = + | 'Clone' + | 'Delete' + | 'Discover' + | 'Edit' + | 'Reset' + | 'Start' + | 'Stop' + | 'Reauthorize'; export function TransformTableProvider({ getService }: FtrProviderContext) { const find = getService('find'); @@ -464,6 +472,21 @@ export function TransformTableProvider({ getService }: FtrProviderContext) { }); } + public async assertTransformRowActionMissing( + transformId: string, + action: TransformRowActionName + ) { + const selector = `transformAction${action}`; + await retry.tryForTime(60 * 1000, async () => { + await this.refreshTransformList(); + + await this.ensureTransformActionsMenuOpen(transformId); + + await testSubjects.missingOrFail(selector, { timeout: 1000 }); + await this.ensureTransformActionsMenuClosed(); + }); + } + public async assertTransformRowActionEnabled( transformId: string, action: TransformRowActionName, @@ -516,6 +539,14 @@ export function TransformTableProvider({ getService }: FtrProviderContext) { await testSubjects.missingOrFail('transformDeleteModal', { timeout: 60 * 1000 }); } + public async assertTransformReauthorizeModalExists() { + await testSubjects.existOrFail('transformReauthorizeModal', { timeout: 60 * 1000 }); + } + + public async assertTransformReauthorizeModalNotExists() { + await testSubjects.missingOrFail('transformReauthorizeModal', { timeout: 60 * 1000 }); + } + public async assertTransformResetModalExists() { await testSubjects.existOrFail('transformResetModal', { timeout: 60 * 1000 }); } @@ -564,6 +595,14 @@ export function TransformTableProvider({ getService }: FtrProviderContext) { }); } + public async confirmReauthorizeTransform() { + await retry.tryForTime(30 * 1000, async () => { + await this.assertTransformReauthorizeModalExists(); + await testSubjects.click('transformReauthorizeModal > confirmModalConfirmButton'); + await this.assertTransformReauthorizeModalNotExists(); + }); + } + public async confirmStartTransform() { await retry.tryForTime(30 * 1000, async () => { await this.assertTransformStartModalExists(); diff --git a/x-pack/test/functional_basic/apps/transform/start_reset_delete/config.ts b/x-pack/test/functional_basic/apps/transform/actions/config.ts similarity index 85% rename from x-pack/test/functional_basic/apps/transform/start_reset_delete/config.ts rename to x-pack/test/functional_basic/apps/transform/actions/config.ts index 6922e0f70c5a59..34c1359750082f 100644 --- a/x-pack/test/functional_basic/apps/transform/start_reset_delete/config.ts +++ b/x-pack/test/functional_basic/apps/transform/actions/config.ts @@ -16,8 +16,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { testFiles: [require.resolve('.')], junit: { ...transformConfig.get('junit'), - reportName: - 'Chrome X-Pack UI Functional Tests Basic License - transform - start reset & delete', + reportName: 'Chrome X-Pack UI Functional Tests Basic License - transform - actions', }, }; } diff --git a/x-pack/test/functional_basic/apps/transform/start_reset_delete/index.ts b/x-pack/test/functional_basic/apps/transform/actions/index.ts similarity index 95% rename from x-pack/test/functional_basic/apps/transform/start_reset_delete/index.ts rename to x-pack/test/functional_basic/apps/transform/actions/index.ts index 14a9bcbc099c8b..589e6536ecc1b5 100644 --- a/x-pack/test/functional_basic/apps/transform/start_reset_delete/index.ts +++ b/x-pack/test/functional_basic/apps/transform/actions/index.ts @@ -12,6 +12,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { this.tags(['skipFirefox', 'transform']); // The transform UI should work the same as with a trial license - loadTestFile(require.resolve('../../../../functional/apps/transform/start_reset_delete')); + loadTestFile(require.resolve('../../../../functional/apps/transform/actions')); }); }