From a6fb0bf3b3e34030061d062b4a386b4bd9f0c5cd Mon Sep 17 00:00:00 2001 From: "Quynh Nguyen (Quinn)" <43350163+qn895@users.noreply.github.com> Date: Wed, 3 May 2023 12:36:38 -0500 Subject: [PATCH] [ML] Add functional testing for continuous transforms (creation, start, stop, reset) (#156128) ## Summary Part of https://github.com/elastic/kibana/issues/152009. This PR adds functional test for continuous transforms related actions including creation, start, stop, and reset. It also stablizes tests skipped in https://github.com/elastic/kibana/issues/151889. Started [flaky test suite runner for continuous transform](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/2185)... Started [flaky test suite runner for creation index pattern (previously skipped)](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/2187)... ### Checklist Delete any items that are not applicable to this PR. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### Risk Matrix Delete this section if it is not applicable to this PR. Before closing this PR, invite QA, stakeholders, and other developers to identify risks that should be tested prior to the change/feature release. When forming the risk matrix, consider some of the following examples and how they may potentially impact the change: | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | Multiple Spaces—unexpected behavior in non-default Kibana Space. | Low | High | Integration tests will verify that all features are still supported in non-default Kibana Space and when user switches between spaces. | | Multiple nodes—Elasticsearch polling might have race conditions when multiple Kibana nodes are polling for the same tasks. | High | Low | Tasks are idempotent, so executing them multiple times will not result in logical error, but will degrade performance. To test for this case we add plenty of unit tests around this logic and document manual testing procedure. | | Code should gracefully handle cases when feature X or plugin Y are disabled. | Medium | High | Unit tests will verify that any feature flag or plugin combination still results in our service operational. | | [See more potential risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> (cherry picked from commit f61546629dc787c320582ff9b26777c493d0459d) --- .../step_details/step_details_form.tsx | 2 +- .../transform_list/expanded_row.test.tsx | 2 +- .../transform_list/expanded_row.tsx | 5 +- .../expanded_row_details_pane.tsx | 8 +- .../index_pattern/continuous_transform.ts | 511 ++++++++++++++++++ .../index_pattern/creation_index_pattern.ts | 4 +- .../transform/creation/index_pattern/index.ts | 1 + .../test/functional/apps/transform/helpers.ts | 1 + .../services/ml/field_stats_flyout.ts | 18 +- .../services/transform/date_picker.ts | 6 +- .../functional/services/transform/discover.ts | 11 + .../services/transform/transform_table.ts | 126 ++++- .../functional/services/transform/wizard.ts | 40 +- 13 files changed, 709 insertions(+), 26 deletions(-) create mode 100644 x-pack/test/functional/apps/transform/creation/index_pattern/continuous_transform.ts diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index 1c756b242c79f..2a7af6e5b4db6 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -627,7 +627,7 @@ export const StepDetailsForm: FC = React.memo( )} > ({ text }))} + options={dateFieldNames.map((text: string) => ({ text, value: text }))} value={continuousModeDateField} onChange={(e) => setContinuousModeDateField(e.target.value)} data-test-subj="transformContinuousDateFieldSelect" diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx index e77de4b668eca..f8b55dbbeda3c 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx @@ -60,7 +60,7 @@ describe('Transform: Transform List ', () => { await waitFor(() => { expect(screen.getByTestId('transformStatsTab')).toHaveAttribute('aria-selected', 'true'); - const tabContent = screen.getByTestId('transformDetailsTabContent'); + const tabContent = screen.getByTestId('transformStatsTabContent'); expect(within(tabContent).getByText('Stats')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx index 43e20106ee3ab..209af498cb5f6 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx @@ -240,6 +240,7 @@ export const ExpandedRow: FC = ({ item, onAlertEdit }) => { checkpointing, ...(alertingRules.items ? [alertingRules] : []), ]} + dataTestSubj={'transformDetailsTabContent'} /> ), }, @@ -252,7 +253,9 @@ export const ExpandedRow: FC = ({ item, onAlertEdit }) => { defaultMessage: 'Stats', } ), - content: , + content: ( + + ), }, { id: `transform-json-tab-${tabId}`, diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_details_pane.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_details_pane.tsx index 1b2dde0a2e576..ab79db82ba007 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_details_pane.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_details_pane.tsx @@ -48,11 +48,15 @@ export const Section: FC = ({ section }) => { interface ExpandedRowDetailsPaneProps { sections: SectionConfig[]; + dataTestSubj?: string; } -export const ExpandedRowDetailsPane: FC = ({ sections }) => { +export const ExpandedRowDetailsPane: FC = ({ + sections, + dataTestSubj, +}) => { return ( -
+
{sections diff --git a/x-pack/test/functional/apps/transform/creation/index_pattern/continuous_transform.ts b/x-pack/test/functional/apps/transform/creation/index_pattern/continuous_transform.ts new file mode 100644 index 0000000000000..9e3538d036cd9 --- /dev/null +++ b/x-pack/test/functional/apps/transform/creation/index_pattern/continuous_transform.ts @@ -0,0 +1,511 @@ +/* + * 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_STATE } from '@kbn/transform-plugin/common/constants'; + +import type { FtrProviderContext } from '../../../../ftr_provider_context'; +import { + GroupByEntry, + isLatestTransformTestData, + isPivotTransformTestData, + LatestTransformTestData, + PivotTransformTestData, +} from '../../helpers'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const canvasElement = getService('canvasElement'); + const esArchiver = getService('esArchiver'); + const transform = getService('transform'); + const sampleData = getService('sampleData'); + const security = getService('security'); + const pageObjects = getPageObjects(['discover']); + + describe('creation_continuous_transform', function () { + before(async () => { + // installing the sample data with test user with super user role and then switching roles with limited privileges + await security.testUser.setRoles(['superuser'], { skipBrowserRefresh: true }); + await esArchiver.emptyKibanaIndex(); + await sampleData.testResources.installKibanaSampleData('ecommerce'); + await transform.securityUI.loginAsTransformPowerUser(); + }); + + after(async () => { + await transform.api.cleanTransformIndices(); + await transform.testResources.deleteIndexPatternByTitle('ft_ecommerce'); + }); + + const DEFAULT_NUM_FAILURE_RETRIES = '5'; + const testDataList: Array = [ + { + type: 'pivot', + suiteTitle: 'continuous with terms+date_histogram groups and avg agg', + source: 'kibana_sample_data_ecommerce', + groupByEntries: [ + { + identifier: 'terms(category)', + label: 'category', + } as GroupByEntry, + { + identifier: 'date_histogram(order_date)', + label: 'order_date', + intervalLabel: '1m', + } as GroupByEntry, + ], + aggregationEntries: [ + { + identifier: 'avg(products.base_price)', + label: 'products.base_price.avg', + }, + { + identifier: 'filter(geoip.city_name)', + label: 'geoip.city_name.filter', + form: { + transformFilterAggTypeSelector: 'term', + transformFilterTermValueSelector: 'New York', + }, + subAggs: [ + { + identifier: 'max(products.base_price)', + label: 'products.base_price.max', + }, + { + identifier: 'filter(customer_gender)', + label: 'customer_gender.filter', + form: { + transformFilterAggTypeSelector: 'term', + transformFilterTermValueSelector: 'FEMALE', + }, + subAggs: [ + { + identifier: 'avg(taxful_total_price)', + label: 'taxful_total_price.avg', + }, + ], + }, + ], + }, + ], + transformId: `ec_pivot_${Date.now()}`, + transformDescription: 'kibana_ecommerce continuous pivot transform', + get destinationIndex(): string { + return `user-${this.transformId}`; + }, + continuousModeDateField: 'order_date', + numFailureRetries: '7', + discoverAdjustSuperDatePicker: true, + expected: { + continuousModeDateField: 'order_date', + pivotAdvancedEditorValueArr: ['{', ' "group_by": {', ' "category": {'], + pivotAdvancedEditorValue: { + group_by: { + category: { + terms: { + field: 'category.keyword', + }, + }, + order_date: { + date_histogram: { + field: 'order_date', + calendar_interval: '1m', + }, + }, + }, + aggregations: { + 'products.base_price.avg': { + avg: { + field: 'products.base_price', + }, + }, + 'New York': { + filter: { + term: { + 'geoip.city_name': 'New York', + }, + }, + aggs: { + 'products.base_price.max': { + max: { + field: 'products.base_price', + }, + }, + FEMALE: { + filter: { + term: { + customer_gender: 'FEMALE', + }, + }, + aggs: { + 'taxful_total_price.avg': { + avg: { + field: 'taxful_total_price', + }, + }, + }, + }, + }, + }, + }, + }, + transformPreview: { + column: 0, + values: [`Men's Accessories`], + }, + row: { + status: TRANSFORM_STATE.STARTED, + mode: 'continuous', + progress: '0', + health: 'Healthy', + }, + indexPreview: { + columns: 10, + rows: 5, + }, + discoverQueryHits: '3,418', + numFailureRetries: '7', + }, + } as PivotTransformTestData, + { + type: 'latest', + suiteTitle: 'continuous with the latest function', + source: 'kibana_sample_data_ecommerce', + uniqueKeys: [ + { + identifier: 'geoip.country_iso_code', + label: 'geoip.country_iso_code', + }, + ], + sortField: { + identifier: 'order_date', + label: 'order_date', + }, + transformId: `ec_latest_${Date.now()}`, + transformDescription: 'kibana_ecommerce continuous with the latest function', + continuousModeDateField: 'order_date', + + get destinationIndex(): string { + return `user-${this.transformId}`; + }, + destinationDataViewTimeField: 'order_date', + numFailureRetries: '101', + discoverAdjustSuperDatePicker: false, + expected: { + latestPreview: { + column: 0, + values: [], + }, + row: { + status: TRANSFORM_STATE.STARTED, + type: 'latest', + mode: 'continuous', + health: 'Healthy', + }, + indexPreview: { + columns: 10, + rows: 5, + }, + transformPreview: { + column: 0, + values: [ + 'July 12th 2019, 22:16:19', + 'July 12th 2019, 22:50:53', + 'July 12th 2019, 23:06:43', + 'July 12th 2019, 23:15:22', + 'July 12th 2019, 23:31:12', + ], + }, + discoverQueryHits: '10', + numFailureRetries: 'error', + }, + } as LatestTransformTestData, + ]; + + for (const testData of testDataList) { + describe(`${testData.suiteTitle}`, function () { + after(async () => { + await transform.api.deleteIndices(testData.destinationIndex); + await transform.testResources.deleteIndexPatternByTitle(testData.destinationIndex); + }); + + it('loads the wizard for the source data', async () => { + await transform.testExecution.logTestStep('loads the home page'); + await transform.navigation.navigateTo(); + await transform.management.assertTransformListPageExists(); + + await transform.testExecution.logTestStep('displays the stats bar'); + await transform.management.assertTransformStatsBarExists(); + + await transform.testExecution.logTestStep('loads the source selection modal'); + await transform.management.startTransformCreation(); + + await transform.testExecution.logTestStep('selects the source data'); + await transform.sourceSelection.selectSource(testData.source); + }); + + it('navigates through the wizard and sets all needed fields', async () => { + await transform.testExecution.logTestStep('displays the define step'); + await transform.wizard.assertDefineStepActive(); + + await transform.testExecution.logTestStep('has correct transform function selected'); + await transform.wizard.assertSelectedTransformFunction('pivot'); + + await transform.testExecution.logTestStep(`sets the date picker to '15 days ago to now'`); + await transform.datePicker.quickSelect(15, 'y'); + await transform.wizard.assertIndexPreviewLoaded(); + + await transform.testExecution.logTestStep('shows the index preview'); + await transform.wizard.assertIndexPreview( + testData.expected.indexPreview.columns, + testData.expected.indexPreview.rows + ); + + await transform.testExecution.logTestStep('displays the query input'); + await transform.wizard.assertQueryInputExists(); + await transform.wizard.assertQueryValue(''); + + await transform.testExecution.logTestStep('displays the advanced query editor switch'); + await transform.wizard.assertAdvancedQueryEditorSwitchExists(); + await transform.wizard.assertAdvancedQueryEditorSwitchCheckState(false); + + // Disable anti-aliasing to stabilize canvas image rendering assertions + await canvasElement.disableAntiAliasing(); + + await transform.testExecution.logTestStep('enables the index preview histogram charts'); + await transform.wizard.enableIndexPreviewHistogramCharts(true); + + if (isPivotTransformTestData(testData)) { + for (const [index, entry] of testData.groupByEntries.entries()) { + await transform.wizard.assertGroupByInputExists(); + await transform.wizard.assertGroupByInputValue([]); + await transform.wizard.addGroupByEntry( + index, + entry.identifier, + entry.label, + entry.intervalLabel + ); + } + + await transform.testExecution.logTestStep('adds the aggregation entries'); + await transform.wizard.addAggregationEntries(testData.aggregationEntries); + + await transform.testExecution.logTestStep('displays the advanced pivot editor switch'); + await transform.wizard.assertAdvancedPivotEditorSwitchExists(); + await transform.wizard.assertAdvancedPivotEditorSwitchCheckState(false); + + await transform.testExecution.logTestStep('displays the advanced configuration'); + await transform.wizard.enableAdvancedPivotEditor(); + await transform.wizard.assertAdvancedPivotEditorContent( + testData.expected.pivotAdvancedEditorValueArr + ); + } + + if (isLatestTransformTestData(testData)) { + await transform.testExecution.logTestStep('sets latest transform method'); + await transform.wizard.selectTransformFunction('latest'); + + await transform.testExecution.logTestStep('adds unique keys'); + for (const { identifier, label } of testData.uniqueKeys) { + await transform.wizard.assertUniqueKeysInputExists(); + await transform.wizard.assertUniqueKeysInputValue([]); + await transform.wizard.addUniqueKeyEntry(identifier, label); + } + + await transform.testExecution.logTestStep('sets the sort field'); + await transform.wizard.assertSortFieldInputExists(); + await transform.wizard.assertSortFieldInputValue(''); + await transform.wizard.setSortFieldValue( + testData.sortField.identifier, + testData.sortField.label + ); + } + + await transform.testExecution.logTestStep('loads the transform preview'); + await transform.wizard.assertPivotPreviewLoaded(); + + await transform.testExecution.logTestStep('shows the transform preview'); + await transform.wizard.assertPivotPreviewChartHistogramButtonMissing(); + + await transform.testExecution.logTestStep('loads the details step'); + await transform.wizard.advanceToDetailsStep(); + + await transform.testExecution.logTestStep('inputs the transform id'); + await transform.wizard.assertTransformIdInputExists(); + await transform.wizard.assertTransformIdValue(''); + await transform.wizard.setTransformId(testData.transformId); + + await transform.testExecution.logTestStep('inputs the transform description'); + await transform.wizard.assertTransformDescriptionInputExists(); + await transform.wizard.assertTransformDescriptionValue(''); + await transform.wizard.setTransformDescription(testData.transformDescription); + + await transform.testExecution.logTestStep('inputs the destination index'); + await transform.wizard.assertDestinationIndexInputExists(); + await transform.wizard.assertDestinationIndexValue(''); + await transform.wizard.setDestinationIndex(testData.destinationIndex); + + await transform.testExecution.logTestStep('displays the create data view switch'); + await transform.wizard.assertCreateDataViewSwitchExists(); + await transform.wizard.assertCreateDataViewSwitchCheckState(true); + + if (testData.destinationDataViewTimeField) { + await transform.testExecution.logTestStep('sets the data view time field'); + await transform.wizard.assertDataViewTimeFieldInputExists(); + await transform.wizard.setDataViewTimeField(testData.destinationDataViewTimeField); + } + + await transform.testExecution.logTestStep( + 'displays the continuous mode switch and enables it' + ); + await transform.wizard.assertContinuousModeSwitchExists(); + await transform.wizard.setContinuousModeSwitchCheckState(true); + await transform.wizard.assertContinuousModeDateFieldSelectExists(); + + if (testData.continuousModeDateField) { + await transform.testExecution.logTestStep('sets the date field'); + await transform.wizard.selectContinuousModeDateField(testData.continuousModeDateField); + } + + await transform.testExecution.logTestStep( + 'should display the advanced settings and show pre-filled configuration' + ); + await transform.wizard.openTransformAdvancedSettingsAccordion(); + if ( + testData.numFailureRetries !== undefined && + testData.expected.numFailureRetries !== undefined + ) { + await transform.wizard.assertNumFailureRetriesValue(''); + await transform.wizard.setTransformNumFailureRetriesValue( + testData.numFailureRetries.toString(), + testData.expected.numFailureRetries + ); + // If num failure input is expected to give an error, sets it back to a valid + // so that we can continue creating the transform + if (testData.expected.numFailureRetries === 'error') { + await transform.wizard.setTransformNumFailureRetriesValue( + DEFAULT_NUM_FAILURE_RETRIES, + DEFAULT_NUM_FAILURE_RETRIES + ); + } + } + + await transform.testExecution.logTestStep('loads the create step'); + await transform.wizard.advanceToCreateStep(); + + await transform.testExecution.logTestStep('displays the summary details'); + await transform.wizard.openTransformAdvancedSettingsSummaryAccordion(); + await transform.wizard.assertTransformNumFailureRetriesSummaryValue( + testData.expected.numFailureRetries === 'error' + ? DEFAULT_NUM_FAILURE_RETRIES + : testData.expected.numFailureRetries + ); + + await transform.testExecution.logTestStep('displays the create and start button'); + await transform.wizard.assertCreateAndStartButtonExists(); + await transform.wizard.assertCreateAndStartButtonEnabled(true); + + await transform.testExecution.logTestStep('displays the create button'); + await transform.wizard.assertCreateButtonExists(); + await transform.wizard.assertCreateButtonEnabled(true); + + await transform.testExecution.logTestStep('displays the copy to clipboard button'); + await transform.wizard.assertCopyToClipboardButtonExists(); + await transform.wizard.assertCopyToClipboardButtonEnabled(true); + }); + + it('runs the transform and displays it correctly in the job list', async () => { + await transform.testExecution.logTestStep('creates the transform'); + await transform.wizard.createTransform(); + + await transform.testExecution.logTestStep('starts the transform and finishes processing'); + await transform.wizard.startTransform({ expectProgressbarExists: false }); + await transform.wizard.assertErrorToastsNotExist(); + + await transform.testExecution.logTestStep('returns to the management page'); + await transform.wizard.returnToManagement(); + + await transform.testExecution.logTestStep('displays the transforms table'); + await transform.management.assertTransformsTableExists(); + + await transform.testExecution.logTestStep( + 'displays the created transform in the transform list' + ); + await transform.table.refreshTransformList(); + await transform.table.filterWithSearchString(testData.transformId, 1); + + await transform.testExecution.logTestStep( + 'transform creation displays details for the created transform in the transform list' + ); + await transform.table.assertTransformRowFields(testData.transformId, { + id: testData.transformId, + description: testData.transformDescription, + type: testData.type, + status: testData.expected.row.status, + mode: testData.expected.row.mode, + health: testData.expected.row.health, + }); + }); + + it('stops transform', async () => { + await transform.testExecution.logTestStep('should show the actions popover'); + await transform.table.assertTransformRowActions(testData.transformId, true); + await transform.table.assertTransformRowActionEnabled( + testData.transformId, + 'Reset', + false + ); + await transform.table.assertTransformRowActionEnabled( + testData.transformId, + 'Delete', + false + ); + + await transform.testExecution.logTestStep('should stop transform'); + await transform.table.stopTransform(testData.transformId); + }); + + it('navigates to discover and displays results of the destination index', async () => { + await transform.testExecution.logTestStep('should navigate to discover'); + await transform.table.clickTransformRowAction(testData.transformId, 'Discover'); + await pageObjects.discover.waitUntilSearchingHasFinished(); + + if (testData.discoverAdjustSuperDatePicker) { + await transform.datePicker.quickSelect(15, 'y'); + } + await transform.discover.assertDiscoverQueryHitsMoreThanZero(); + }); + + it('resets and starts previously stopped transform', async () => { + await transform.testExecution.logTestStep( + 'should navigate to transform management list page' + ); + await transform.navigation.navigateTo(); + await transform.management.assertTransformListPageExists(); + + await transform.testExecution.logTestStep( + 'should show the actions popover for continuous transform' + ); + await transform.table.assertTransformRowActions(testData.transformId, false); + await transform.table.assertTransformRowActionEnabled( + testData.transformId, + 'Reset', + true + ); + await transform.table.assertTransformRowActionEnabled( + testData.transformId, + 'Start', + true + ); + + await transform.testExecution.logTestStep('should reset transform'); + await transform.table.resetTransform(testData.transformId); + + await transform.testExecution.logTestStep('should start previously stopped transform'); + await transform.table.startTransform(testData.transformId); + }); + }); + } + }); +} diff --git a/x-pack/test/functional/apps/transform/creation/index_pattern/creation_index_pattern.ts b/x-pack/test/functional/apps/transform/creation/index_pattern/creation_index_pattern.ts index e5319cb4d60a4..69406c5830421 100644 --- a/x-pack/test/functional/apps/transform/creation/index_pattern/creation_index_pattern.ts +++ b/x-pack/test/functional/apps/transform/creation/index_pattern/creation_index_pattern.ts @@ -22,8 +22,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const transform = getService('transform'); const pageObjects = getPageObjects(['discover']); - // Failing: See https://github.com/elastic/kibana/issues/151889 - describe.skip('creation_index_pattern', function () { + describe('creation_index_pattern', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/ecommerce'); await transform.testResources.createIndexPatternIfNeeded('ft_ecommerce', 'order_date'); @@ -35,6 +34,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { after(async () => { await transform.api.cleanTransformIndices(); await transform.testResources.deleteIndexPatternByTitle('ft_ecommerce'); + await transform.securityUI.logout(); }); const fieldStatsEntries = [ diff --git a/x-pack/test/functional/apps/transform/creation/index_pattern/index.ts b/x-pack/test/functional/apps/transform/creation/index_pattern/index.ts index 9e09c4e1c51fa..f1cf20997029b 100644 --- a/x-pack/test/functional/apps/transform/creation/index_pattern/index.ts +++ b/x-pack/test/functional/apps/transform/creation/index_pattern/index.ts @@ -33,5 +33,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { }); loadTestFile(require.resolve('./creation_index_pattern')); + loadTestFile(require.resolve('./continuous_transform')); }); } diff --git a/x-pack/test/functional/apps/transform/helpers.ts b/x-pack/test/functional/apps/transform/helpers.ts index 831cefce6afad..51803fe47b8e0 100644 --- a/x-pack/test/functional/apps/transform/helpers.ts +++ b/x-pack/test/functional/apps/transform/helpers.ts @@ -29,6 +29,7 @@ export interface BaseTransformTestData { destinationIndex: string; destinationDataViewTimeField?: string; discoverAdjustSuperDatePicker: boolean; + continuousModeDateField?: string; numFailureRetries?: string; fieldStatsEntries?: Array<{ fieldName: string; diff --git a/x-pack/test/functional/services/ml/field_stats_flyout.ts b/x-pack/test/functional/services/ml/field_stats_flyout.ts index cca2db70b5914..f7f35ce5353b5 100644 --- a/x-pack/test/functional/services/ml/field_stats_flyout.ts +++ b/x-pack/test/functional/services/ml/field_stats_flyout.ts @@ -70,11 +70,13 @@ export function MachineLearningFieldStatsFlyoutProvider({ getService }: FtrProvi ) { const selector = `~${testSubj} > ~mlInspectFieldStatsButton-${fieldName}`; - await retry.tryForTime(2000, async () => { + await retry.tryForTime(10 * 1000, async () => { await testSubjects.existOrFail(selector); await testSubjects.click(selector); - await testSubjects.existOrFail('mlFieldStatsFlyout'); - await testSubjects.existOrFail(`mlFieldStatsFlyoutContent ${fieldName}-title`); + await testSubjects.existOrFail('mlFieldStatsFlyout', { timeout: 500 }); + await testSubjects.existOrFail(`mlFieldStatsFlyoutContent ${fieldName}-title`, { + timeout: 500, + }); }); await this.assertFieldStatContentByType(testSubj, fieldName, fieldType); if (Array.isArray(expectedTopValuesContent)) { @@ -90,16 +92,16 @@ export function MachineLearningFieldStatsFlyoutProvider({ getService }: FtrProvi expectedTopValuesContent?: string[] ) { const selector = `mlInspectFieldStatsButton-${fieldName}`; - - await retry.tryForTime(2000, async () => { + await retry.tryForTime(30 * 1000, async () => { const fieldTarget = await testSubjects.find(parentComboBoxSelector); await comboBox.openOptionsList(fieldTarget); await testSubjects.existOrFail(selector); await testSubjects.click(selector); - await testSubjects.existOrFail('mlFieldStatsFlyout'); - - await testSubjects.existOrFail(`mlFieldStatsFlyoutContent ${fieldName}-title`); + await testSubjects.existOrFail('mlFieldStatsFlyout', { timeout: 500 }); + await testSubjects.existOrFail(`mlFieldStatsFlyoutContent ${fieldName}-title`, { + timeout: 500, + }); await this.assertFieldStatContentByType(parentComboBoxSelector, fieldName, fieldType); diff --git a/x-pack/test/functional/services/transform/date_picker.ts b/x-pack/test/functional/services/transform/date_picker.ts index 9758e503ce329..c872c431a5d2b 100644 --- a/x-pack/test/functional/services/transform/date_picker.ts +++ b/x-pack/test/functional/services/transform/date_picker.ts @@ -22,8 +22,10 @@ export function TransformDatePickerProvider({ getService, getPageObjects }: FtrP async openSuperDatePicker() { await this.assertSuperDatePickerToggleQuickMenuButtonExists(); - await testSubjects.click('superDatePickerToggleQuickMenuButton'); - await testSubjects.existOrFail('superDatePickerQuickMenu'); + await retry.tryForTime(30 * 1000, async () => { + await testSubjects.click('superDatePickerToggleQuickMenuButton'); + await testSubjects.existOrFail('superDatePickerQuickMenu'); + }); }, async quickSelect(timeValue: number = 15, timeUnit: string = 'y') { diff --git a/x-pack/test/functional/services/transform/discover.ts b/x-pack/test/functional/services/transform/discover.ts index 303bc9b171f80..07bd66cce1780 100644 --- a/x-pack/test/functional/services/transform/discover.ts +++ b/x-pack/test/functional/services/transform/discover.ts @@ -26,6 +26,17 @@ export function TransformDiscoverProvider({ getService }: FtrProviderContext) { ); }, + async assertDiscoverQueryHitsMoreThanZero() { + await testSubjects.existOrFail('unifiedHistogramQueryHits'); + + const actualDiscoverQueryHits = await testSubjects.getVisibleText( + 'unifiedHistogramQueryHits' + ); + + const hits = parseInt(actualDiscoverQueryHits, 10); + expect(hits).to.greaterThan(0, `Discover query hits should be more than 0, got ${hits}`); + }, + async assertNoResults(expectedDestinationIndex: string) { await testSubjects.missingOrFail('unifiedHistogramQueryHits'); diff --git a/x-pack/test/functional/services/transform/transform_table.ts b/x-pack/test/functional/services/transform/transform_table.ts index 4ae7e4c95821d..135aa18af9b40 100644 --- a/x-pack/test/functional/services/transform/transform_table.ts +++ b/x-pack/test/functional/services/transform/transform_table.ts @@ -121,12 +121,17 @@ export function TransformTableProvider({ getService }: FtrProviderContext) { await this.refreshTransformList(); const rows = await this.parseTransformTable(); const transformRow = rows.filter((row) => row.id === transformId)[0]; - expect(transformRow).to.eql( - expectedRow, - `Expected transform row to be '${JSON.stringify(expectedRow)}' (got '${JSON.stringify( - transformRow - )}')` - ); + + for (const [key, value] of Object.entries(expectedRow)) { + expect(transformRow) + .to.have.property(key) + .eql( + value, + `Expected transform row ${transformId} to have '${key}' with value '${value}' (got ${JSON.stringify( + transformRow + )})` + ); + } }); } @@ -208,7 +213,6 @@ export function TransformTableProvider({ getService }: FtrProviderContext) { await this.switchToExpandedRowTab('transformMessagesTab', '~transformMessagesTabContent'); await this.switchToExpandedRowTab('transformPreviewTab', '~transformPivotPreview'); } - public async assertTransformExpandedRowJson(expectedText: string, expectedToContain = true) { await this.ensureDetailsOpen(); @@ -288,6 +292,73 @@ export function TransformTableProvider({ getService }: FtrProviderContext) { await this.switchToExpandedRowTab('transformDetailsTab', '~transformDetailsTabContent'); } + public async getTransformExpandedRowStats( + transformId: string + ): Promise> { + await this.filterWithSearchString(transformId, 1); + await this.ensureDetailsOpen(); + let stats: Record = {}; + + await retry.tryForTime(30 * 1000, async () => { + // The expanded row should show the details tab content by default + await testSubjects.existOrFail('transformDetailsTab'); + await testSubjects.existOrFail('~transformDetailsTabContent'); + + // Reset stats in case of retries + stats = {}; + // Click on the messages tab and assert the messages + await this.switchToExpandedRowTab('transformStatsTab', '~transformStatsTabContent'); + const actualText = await testSubjects.getVisibleText('~transformStatsTabContent'); + const parsedText = actualText.split('\n').slice(1); + if (Array.isArray(parsedText) && parsedText.length % 2 === 0) { + parsedText.forEach((key, idx) => { + if (idx % 2 === 0) { + const val = parsedText[idx + 1]; + stats[key] = + typeof val === 'string' && !isNaN(Number(val)) && !isNaN(parseFloat(val)) + ? parseFloat(val) + : val; + } + }); + } + // Switch back to details tab + await this.switchToExpandedRowTab('transformDetailsTab', '~transformDetailsTabContent'); + }); + return stats; + } + + public async assertTransformExpandedRowStats(transformId: string, expectedStats: object) { + const stats = await this.getTransformExpandedRowStats(transformId); + + await retry.tryForTime(60 * 1000, async () => { + for (const [key, value] of Object.entries(expectedStats)) { + expect(stats) + .to.have.property(key) + .eql( + value, + `Expected transform row stats to have '${key}' with value '${value}' (got ${JSON.stringify( + stats + )})` + ); + } + }); + } + + public async assertTransformExpandedRowStatsNotEql(transformId: string, expectedStats: object) { + const stats = await this.getTransformExpandedRowStats(transformId); + + await retry.tryForTime(60 * 1000, async () => { + for (const [key, val] of Object.entries(expectedStats)) { + expect(stats[key]).not.eql( + val, + `Expected transform row stats to have '${key}' with value not equal to ${val}' (got ${JSON.stringify( + stats + )})` + ); + } + }); + } + public rowSelector(transformId: string, subSelector?: string) { const row = `~transformListTable > ~row-${transformId}`; return !subSelector ? row : `${row} > ${subSelector}`; @@ -352,6 +423,47 @@ export function TransformTableProvider({ getService }: FtrProviderContext) { await this.ensureTransformActionsMenuClosed(); } + public async resetTransform(transformId: string) { + await this.assertTransformRowFields(transformId, { status: 'stopped' }); + // Assert that transform previously started and has processed documents + await this.assertTransformExpandedRowStatsNotEql(transformId, { + documents_indexed: 0, + documents_processed: 0, + exponential_avg_checkpoint_duration_ms: 0, + }); + await retry.tryForTime(180 * 1000, async () => { + await this.clickTransformRowAction(transformId, 'Reset'); + await this.confirmResetTransform(); + await this.assertTransformRowFields(transformId, { status: 'stopped' }); + // Assert that transform is reseted correctly and has 0 documents + await this.assertTransformExpandedRowStats(transformId, { + documents_indexed: 0, + documents_processed: 0, + exponential_avg_checkpoint_duration_ms: 0, + }); + }); + } + + public async stopTransform(transformId: string) { + await this.assertTransformRowFields(transformId, { status: 'started' }); + await this.assertTransformRowActionEnabled(transformId, 'Stop', true); + await retry.tryForTime(60 * 1000, async () => { + await this.clickTransformRowAction(transformId, 'Stop'); + await this.assertTransformRowFields(transformId, { status: 'stopped' }); + }); + } + + public async startTransform(transformId: string) { + await this.assertTransformRowFields(transformId, { status: 'stopped' }); + await this.assertTransformRowActionEnabled(transformId, 'Start', true); + + await retry.tryForTime(60 * 1000, async () => { + await this.clickTransformRowAction(transformId, 'Start'); + await this.confirmStartTransform(); + await this.assertTransformRowFields(transformId, { status: 'started' }); + }); + } + public async assertTransformRowActionEnabled( transformId: string, action: TransformRowActionName, diff --git a/x-pack/test/functional/services/transform/wizard.ts b/x-pack/test/functional/services/transform/wizard.ts index c48a097de9213..3d8acd6294aa2 100644 --- a/x-pack/test/functional/services/transform/wizard.ts +++ b/x-pack/test/functional/services/transform/wizard.ts @@ -771,6 +771,39 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi ); }, + async setContinuousModeSwitchCheckState(expectedCheckState: boolean) { + await retry.tryForTime(5000, async () => { + const currentCheckState = + (await testSubjects.getAttribute('transformContinuousModeSwitch', 'aria-checked')) === + 'true'; + if (currentCheckState !== expectedCheckState) { + await testSubjects.click('transformContinuousModeSwitch'); + await this.assertContinuousModeSwitchCheckState(expectedCheckState); + } + }); + }, + + async assertContinuousModeDateFieldSelectExists() { + await retry.tryForTime(1000, async () => { + await testSubjects.existOrFail(`transformContinuousDateFieldSelect`, { allowHidden: true }); + }); + }, + + async selectContinuousModeDateField(value: string) { + await retry.tryForTime(5000, async () => { + await testSubjects.selectValue('transformContinuousDateFieldSelect', value); + const actualSelectState = await testSubjects.getAttribute( + 'transformContinuousDateFieldSelect', + 'value' + ); + + expect(actualSelectState).to.eql( + value, + `Transform continuous date field should be '${value}' (got '${actualSelectState}')` + ); + }); + }, + async assertRetentionPolicySwitchExists() { await testSubjects.existOrFail(`transformRetentionPolicySwitch`, { allowHidden: true }); }, @@ -1085,12 +1118,15 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi }); }, - async startTransform() { + // The progress bar has to exist for batch transform, not for continuous transforms. + async startTransform({ expectProgressbarExists } = { expectProgressbarExists: true }) { await testSubjects.click('transformWizardStartButton'); await retry.tryForTime(5000, async () => { await this.assertDiscoverCardExists(); await this.assertStartButtonEnabled(false); - await this.assertProgressbarExists(); + if (expectProgressbarExists) { + await this.assertProgressbarExists(); + } }); },