From dde2d112d09769ef864250c80bcd8a137a423809 Mon Sep 17 00:00:00 2001 From: Pete Harverson Date: Fri, 13 Nov 2020 09:39:33 +0000 Subject: [PATCH] [ML] Adds functional tests for the index data visualizer card contents (#83174) * [ML] Adds functional tests for the index data visualizer card contents * [ML] Fix translations * [ML] Fix type errors in permissions tests * [ML] Address comments from review --- .../content_types/date_content.tsx | 6 +- .../content_types/keyword_content.tsx | 4 +- .../content_types/number_content.tsx | 24 +- .../document_count_chart.tsx | 2 +- .../examples_list/examples_list.tsx | 2 +- .../field_data_card/field_data_card.tsx | 2 +- .../metric_distribution_chart.tsx | 2 +- .../field_data_card/top_values/top_values.tsx | 6 +- .../components/search_panel/search_panel.tsx | 36 ++- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../data_visualizer/index_data_visualizer.ts | 148 +++++++++---- .../apps/ml/permissions/full_ml_access.ts | 2 +- .../apps/ml/permissions/read_ml_access.ts | 2 +- .../ml/data_visualizer_index_based.ts | 206 +++++++++++++++++- x-pack/test/functional/services/ml/index.ts | 5 +- .../apps/ml/permissions/full_ml_access.ts | 2 +- .../apps/ml/permissions/read_ml_access.ts | 2 +- 18 files changed, 365 insertions(+), 88 deletions(-) diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/date_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/date_content.tsx index 76d05539c0c82..61addb4689f4e 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/date_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/content_types/date_content.tsx @@ -25,7 +25,7 @@ export const DateContent: FC = ({ config }) => { return (
- +   = ({ config }) => { -
+
= ({ config }) => { -
+
= ({ config }) => { return (
- +   = ({ config }) => {
- +   = ({ config }) => { return (
- +   = ({ config }) => {
- +   = ({ config }) => { - + - + - + @@ -145,14 +147,14 @@ export const NumberContent: FC = ({ config }) => { setDetailsMode(optionId as DETAILS_MODE)} + onChange={(optionId) => setDetailsMode(optionId as DetailsModeType)} legend={i18n.translate( 'xpack.ml.fieldDataCard.cardNumber.selectMetricDetailsDisplayAriaLabel', { defaultMessage: 'Select display option for metric details', } )} - data-test-subj="mlFieldDataCardNumberDetailsSelect" + data-test-subj="mlFieldDataCardDetailsSelect" isFullWidth={true} buttonSize="compressed" /> diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/document_count_chart/document_count_chart.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/document_count_chart/document_count_chart.tsx index a2cc59bb38939..7e2671884b101 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/document_count_chart/document_count_chart.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/document_count_chart/document_count_chart.tsx @@ -61,7 +61,7 @@ export const DocumentCountChart: FC = ({ const EVENT_RATE_COLOR = themeName.euiColorVis2; return ( -
+
= ({ examples }) => { }); return ( -
+
= ({ config }) => { hasShadow={false} > -
+
{loading === true ? : getCardContent()}
diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/metric_distribution_chart/metric_distribution_chart.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/metric_distribution_chart/metric_distribution_chart.tsx index 4189308a3bc99..9ff6e99dbc5c2 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/metric_distribution_chart/metric_distribution_chart.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/metric_distribution_chart/metric_distribution_chart.tsx @@ -72,7 +72,7 @@ export const MetricDistributionChart: FC = ({ width, height, chartData, f }; return ( -
+
= ({ stats, fieldFormat, barColor }) => { const progressBarMax = isTopValuesSampled === true ? topValuesSampleSize : count; return ( - +
{topValues.map((value: any) => ( @@ -55,7 +55,7 @@ export const TopValues: FC = ({ stats, fieldFormat, barColor }) => { - + @@ -79,6 +79,6 @@ export const TopValues: FC = ({ stats, fieldFormat, barColor }) => { )} - +
); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/search_panel.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/search_panel.tsx index b93ae9e67ef72..9e14e4044a297 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/search_panel.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/search_panel/search_panel.tsx @@ -48,16 +48,20 @@ const searchSizeOptions = [1000, 5000, 10000, 100000, -1].map((v) => { value: String(v), inputDisplay: v > 0 ? ( - {v} }} - /> + + {v} }} + /> + ) : ( - + + + ), }; }); @@ -174,10 +178,18 @@ export const SearchPanel: FC = ({ {totalCount}, + strongTotalCount: ( + + + + ), }} /> diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 6a5be2e99dc3f..ba6ac32f2e3d0 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12270,7 +12270,6 @@ "xpack.ml.datavisualizer.page.fieldsPanelTitle": "フィールド", "xpack.ml.datavisualizer.page.metricsPanelTitle": "メトリック", "xpack.ml.datavisualizer.searchPanel.allOptionLabel": "すべて検索", - "xpack.ml.datavisualizer.searchPanel.documentsPerShardLabel": "合計ドキュメント数: {wrappedTotalCount}", "xpack.ml.datavisualizer.searchPanel.invalidKuerySyntaxErrorMessageQueryBar": "無効なクエリ", "xpack.ml.datavisualizer.searchPanel.queryBarPlaceholder": "小さいサンプルサイズを選択することで、クエリの実行時間を短縮しクラスターへの負荷を軽減できます。", "xpack.ml.datavisualizer.searchPanel.queryBarPlaceholderText": "検索… (例: status:200 AND extension:\"PHP\")", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index b01d0db9d244a..822ccf5cc8409 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12284,7 +12284,6 @@ "xpack.ml.datavisualizer.page.fieldsPanelTitle": "字段", "xpack.ml.datavisualizer.page.metricsPanelTitle": "指标", "xpack.ml.datavisualizer.searchPanel.allOptionLabel": "搜索全部", - "xpack.ml.datavisualizer.searchPanel.documentsPerShardLabel": "文档总数:{wrappedTotalCount}", "xpack.ml.datavisualizer.searchPanel.invalidKuerySyntaxErrorMessageQueryBar": "无效查询", "xpack.ml.datavisualizer.searchPanel.queryBarPlaceholder": "选择较小的样例大小将减少查询运行时间和集群上的负载。", "xpack.ml.datavisualizer.searchPanel.queryBarPlaceholderText": "搜索……(例如,status:200 AND extension:\"PHP\")", diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts index 1c1afd86fb7d7..a7660e68e93e1 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts @@ -8,6 +8,17 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; import { ML_JOB_FIELD_TYPES } from '../../../../../plugins/ml/common/constants/field_types'; import { FieldVisConfig } from '../../../../../plugins/ml/public/application/datavisualizer/index_based/common'; +interface MetricFieldVisConfig extends FieldVisConfig { + statsMaxDecimalPlaces: number; + docCountFormatted: string; + selectedDetailsMode: 'distribution' | 'top_values'; + topValuesCount: number; +} + +interface NonMetricFieldVisConfig extends FieldVisConfig { + exampleCount?: number; +} + interface TestData { suiteTitle: string; sourceIndexOrSavedSearch: string; @@ -15,10 +26,11 @@ interface TestData { nonMetricFieldsFilter: string; nonMetricFieldsTypeFilter: string; expected: { - totalDocCount: number; + totalDocCountFormatted: string; fieldsPanelCount: number; - metricCards?: FieldVisConfig[]; - nonMetricCards?: FieldVisConfig[]; + documentCountCard: FieldVisConfig; + metricCards?: MetricFieldVisConfig[]; + nonMetricCards?: NonMetricFieldVisConfig[]; nonMetricFieldsTypeFilterCardCount: number; metricFieldsFilterCardCount: number; nonMetricFieldsFilterCardCount: number; @@ -48,21 +60,25 @@ export default function ({ getService }: FtrProviderContext) { nonMetricFieldsFilter: 'airline', nonMetricFieldsTypeFilter: 'keyword', expected: { - totalDocCount: 86274, + totalDocCountFormatted: '86,274', fieldsPanelCount: 2, // Metrics panel and Fields panel + documentCountCard: { + type: ML_JOB_FIELD_TYPES.NUMBER, + existsInDocs: true, + aggregatable: true, + loading: false, + }, metricCards: [ - { - type: ML_JOB_FIELD_TYPES.NUMBER, // document count card - existsInDocs: true, - aggregatable: true, - loading: false, - }, { fieldName: 'responsetime', type: ML_JOB_FIELD_TYPES.NUMBER, existsInDocs: true, aggregatable: true, loading: false, + docCountFormatted: '5,000', + statsMaxDecimalPlaces: 3, + selectedDetailsMode: 'distribution', + topValuesCount: 10, }, ], nonMetricCards: [ @@ -79,6 +95,7 @@ export default function ({ getService }: FtrProviderContext) { existsInDocs: true, aggregatable: false, loading: false, + exampleCount: 1, }, { fieldName: '@version.keyword', @@ -86,6 +103,7 @@ export default function ({ getService }: FtrProviderContext) { existsInDocs: true, aggregatable: true, loading: false, + exampleCount: 1, }, { fieldName: 'airline', @@ -93,6 +111,7 @@ export default function ({ getService }: FtrProviderContext) { existsInDocs: true, aggregatable: true, loading: false, + exampleCount: 10, }, { fieldName: 'type', @@ -100,6 +119,7 @@ export default function ({ getService }: FtrProviderContext) { existsInDocs: true, aggregatable: false, loading: false, + exampleCount: 1, }, { fieldName: 'type.keyword', @@ -107,6 +127,7 @@ export default function ({ getService }: FtrProviderContext) { existsInDocs: true, aggregatable: true, loading: false, + exampleCount: 1, }, ], nonMetricFieldsTypeFilterCardCount: 3, @@ -122,21 +143,25 @@ export default function ({ getService }: FtrProviderContext) { nonMetricFieldsFilter: 'airline', nonMetricFieldsTypeFilter: 'keyword', expected: { - totalDocCount: 34415, + totalDocCountFormatted: '34,415', fieldsPanelCount: 2, // Metrics panel and Fields panel + documentCountCard: { + type: ML_JOB_FIELD_TYPES.NUMBER, + existsInDocs: true, + aggregatable: true, + loading: false, + }, metricCards: [ - { - type: ML_JOB_FIELD_TYPES.NUMBER, // document count card - existsInDocs: true, - aggregatable: true, - loading: false, - }, { fieldName: 'responsetime', type: ML_JOB_FIELD_TYPES.NUMBER, existsInDocs: true, aggregatable: true, loading: false, + docCountFormatted: '5,000', + statsMaxDecimalPlaces: 3, + selectedDetailsMode: 'distribution', + topValuesCount: 10, }, ], nonMetricCards: [ @@ -153,6 +178,7 @@ export default function ({ getService }: FtrProviderContext) { existsInDocs: true, aggregatable: false, loading: false, + exampleCount: 1, }, { fieldName: '@version.keyword', @@ -160,6 +186,7 @@ export default function ({ getService }: FtrProviderContext) { existsInDocs: true, aggregatable: true, loading: false, + exampleCount: 1, }, { fieldName: 'airline', @@ -167,6 +194,7 @@ export default function ({ getService }: FtrProviderContext) { existsInDocs: true, aggregatable: true, loading: false, + exampleCount: 5, }, { fieldName: 'type', @@ -174,6 +202,7 @@ export default function ({ getService }: FtrProviderContext) { existsInDocs: true, aggregatable: false, loading: false, + exampleCount: 1, }, { fieldName: 'type.keyword', @@ -181,6 +210,7 @@ export default function ({ getService }: FtrProviderContext) { existsInDocs: true, aggregatable: true, loading: false, + exampleCount: 1, }, ], nonMetricFieldsTypeFilterCardCount: 3, @@ -196,21 +226,25 @@ export default function ({ getService }: FtrProviderContext) { nonMetricFieldsFilter: 'version', nonMetricFieldsTypeFilter: 'keyword', expected: { - totalDocCount: 34416, + totalDocCountFormatted: '34,416', fieldsPanelCount: 2, // Metrics panel and Fields panel + documentCountCard: { + type: ML_JOB_FIELD_TYPES.NUMBER, // document count card + existsInDocs: true, + aggregatable: true, + loading: false, + }, metricCards: [ - { - type: ML_JOB_FIELD_TYPES.NUMBER, // document count card - existsInDocs: true, - aggregatable: true, - loading: false, - }, { fieldName: 'responsetime', type: ML_JOB_FIELD_TYPES.NUMBER, existsInDocs: true, aggregatable: true, loading: false, + docCountFormatted: '5,000', + statsMaxDecimalPlaces: 3, + selectedDetailsMode: 'distribution', + topValuesCount: 10, }, ], nonMetricCards: [ @@ -227,6 +261,7 @@ export default function ({ getService }: FtrProviderContext) { existsInDocs: true, aggregatable: false, loading: false, + exampleCount: 1, }, { fieldName: '@version.keyword', @@ -234,6 +269,7 @@ export default function ({ getService }: FtrProviderContext) { existsInDocs: true, aggregatable: true, loading: false, + exampleCount: 1, }, { fieldName: 'airline', @@ -241,6 +277,7 @@ export default function ({ getService }: FtrProviderContext) { existsInDocs: true, aggregatable: true, loading: false, + exampleCount: 5, }, { fieldName: 'type', @@ -248,6 +285,7 @@ export default function ({ getService }: FtrProviderContext) { existsInDocs: true, aggregatable: false, loading: false, + exampleCount: 1, }, { fieldName: 'type.keyword', @@ -255,6 +293,7 @@ export default function ({ getService }: FtrProviderContext) { existsInDocs: true, aggregatable: true, loading: false, + exampleCount: 1, }, ], nonMetricFieldsTypeFilterCardCount: 3, @@ -283,22 +322,42 @@ export default function ({ getService }: FtrProviderContext) { await ml.dataVisualizerIndexBased.assertTimeRangeSelectorSectionExists(); await ml.testExecution.logTestStep(`${testData.suiteTitle} loads data for full time range`); - await ml.dataVisualizerIndexBased.clickUseFullDataButton(testData.expected.totalDocCount); + await ml.dataVisualizerIndexBased.clickUseFullDataButton( + testData.expected.totalDocCountFormatted + ); await ml.testExecution.logTestStep(`${testData.suiteTitle} displays the panels of fields`); await ml.dataVisualizerIndexBased.assertFieldsPanelsExist(testData.expected.fieldsPanelCount); - if (testData.expected.metricCards !== undefined && testData.expected.metricCards.length > 0) { - await ml.testExecution.logTestStep(`${testData.suiteTitle} displays the Metrics panel`); - await ml.dataVisualizerIndexBased.assertFieldsPanelForTypesExist([ - ML_JOB_FIELD_TYPES.NUMBER, - ]); // document_count not exposed as a type in the panel + await ml.testExecution.logTestStep(`${testData.suiteTitle} displays the Metrics panel`); + await ml.dataVisualizerIndexBased.assertFieldsPanelForTypesExist([ML_JOB_FIELD_TYPES.NUMBER]); + await ml.testExecution.logTestStep( + `${testData.suiteTitle} displays the expected document count card` + ); + await ml.dataVisualizerIndexBased.assertCardExists( + testData.expected.documentCountCard.type, + testData.expected.documentCountCard.fieldName + ); + await ml.dataVisualizerIndexBased.assertDocumentCountCardContents(); + + await ml.testExecution.logTestStep( + `${testData.suiteTitle} displays the expected metric field cards and contents` + ); + + if (testData.expected.metricCards !== undefined && testData.expected.metricCards.length > 0) { await ml.testExecution.logTestStep( - `${testData.suiteTitle} displays the expected metric field cards` + `${testData.suiteTitle} displays the expected metric field cards and contents` ); - for (const fieldCard of testData.expected.metricCards as FieldVisConfig[]) { + for (const fieldCard of testData.expected.metricCards as MetricFieldVisConfig[]) { await ml.dataVisualizerIndexBased.assertCardExists(fieldCard.type, fieldCard.fieldName); + await ml.dataVisualizerIndexBased.assertNumberCardContents( + fieldCard.fieldName!, + fieldCard.docCountFormatted, + fieldCard.statsMaxDecimalPlaces, + fieldCard.selectedDetailsMode, + fieldCard.topValuesCount + ); } await ml.testExecution.logTestStep( @@ -323,10 +382,15 @@ export default function ({ getService }: FtrProviderContext) { ); await ml.testExecution.logTestStep( - `${testData.suiteTitle} displays the expected non-metric field cards` + `${testData.suiteTitle} displays the expected non-metric field cards and contents` ); for (const fieldCard of testData.expected.nonMetricCards!) { await ml.dataVisualizerIndexBased.assertCardExists(fieldCard.type, fieldCard.fieldName); + await ml.dataVisualizerIndexBased.assertNonMetricCardContents( + fieldCard.type, + fieldCard.fieldName!, + fieldCard.exampleCount + ); } await ml.testExecution.logTestStep( @@ -346,10 +410,22 @@ export default function ({ getService }: FtrProviderContext) { `${testData.suiteTitle} filters non-metric fields cards with search` ); await ml.dataVisualizerIndexBased.filterFieldsPanelWithSearchString( - getFieldTypes(testData.expected.nonMetricCards as FieldVisConfig[]), + fieldTypes, testData.nonMetricFieldsFilter, testData.expected.nonMetricFieldsFilterCardCount ); + + await ml.testExecution.logTestStep( + `${testData.suiteTitle} sample size control changes non-metric field cards doc count` + ); + await ml.dataVisualizerIndexBased.clearFieldsPanelSearchInput(fieldTypes); + await ml.dataVisualizerIndexBased.assertSampleSizeInputExists(); + await ml.dataVisualizerIndexBased.setSampleSizeInputValue( + 1000, + ML_JOB_FIELD_TYPES.KEYWORD, + 'airline', + '1,000' + ); } }); } @@ -366,10 +442,6 @@ export default function ({ getService }: FtrProviderContext) { await ml.securityUI.loginAsMlPowerUser(); }); - // TODO - add tests for - // - validating metrics displayed inside the cards - // - selecting a document sample size - describe('with farequote', function () { // Run tests on full farequote index. it(`${farequoteIndexPatternTestData.suiteTitle} loads the data visualizer selector page`, async () => { diff --git a/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts b/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts index 4abf4dabf7939..285ea49419bbd 100644 --- a/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts +++ b/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts @@ -96,7 +96,7 @@ export default function ({ getService }: FtrProviderContext) { const filterItems = ['filter_item_permission']; const ecIndexPattern = 'ft_module_sample_ecommerce'; - const ecExpectedTotalCount = 287; + const ecExpectedTotalCount = '287'; const ecExpectedFieldPanelCount = 2; const ecExpectedModuleId = 'sample_data_ecommerce'; diff --git a/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts b/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts index 22dcc945acd5c..1557d2b4ec2fb 100644 --- a/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts +++ b/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts @@ -97,7 +97,7 @@ export default function ({ getService }: FtrProviderContext) { const filterItems = ['filter_item_permission']; const ecIndexPattern = 'ft_module_sample_ecommerce'; - const ecExpectedTotalCount = 287; + const ecExpectedTotalCount = '287'; const ecExpectedFieldPanelCount = 2; const uploadFilePath = path.join( diff --git a/x-pack/test/functional/services/ml/data_visualizer_index_based.ts b/x-pack/test/functional/services/ml/data_visualizer_index_based.ts index 31cd17e4df826..60677423a2aa1 100644 --- a/x-pack/test/functional/services/ml/data_visualizer_index_based.ts +++ b/x-pack/test/functional/services/ml/data_visualizer_index_based.ts @@ -7,10 +7,12 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; import { ML_JOB_FIELD_TYPES } from '../../../../plugins/ml/common/constants/field_types'; +import { MlCommonUI } from './common_ui'; -export function MachineLearningDataVisualizerIndexBasedProvider({ - getService, -}: FtrProviderContext) { +export function MachineLearningDataVisualizerIndexBasedProvider( + { getService }: FtrProviderContext, + mlCommonUI: MlCommonUI +) { const testSubjects = getService('testSubjects'); const retry = getService('retry'); const browser = getService('browser'); @@ -20,19 +22,19 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ await testSubjects.existOrFail('mlDataVisualizerTimeRangeSelectorSection'); }, - async assertTotalDocumentCount(expectedTotalDocCount: number) { + async assertTotalDocumentCount(expectedFormattedTotalDocCount: string) { await retry.tryForTime(5000, async () => { const docCount = await testSubjects.getVisibleText('mlDataVisualizerTotalDocCount'); expect(docCount).to.eql( - expectedTotalDocCount, - `Expected total document count to be '${expectedTotalDocCount}' (got '${docCount}')` + expectedFormattedTotalDocCount, + `Expected total document count to be '${expectedFormattedTotalDocCount}' (got '${docCount}')` ); }); }, - async clickUseFullDataButton(expectedTotalDocCount: number) { + async clickUseFullDataButton(expectedFormattedTotalDocCount: string) { await testSubjects.clickWhenNotDisabled('mlButtonUseFullData'); - await this.assertTotalDocumentCount(expectedTotalDocCount); + await this.assertTotalDocumentCount(expectedFormattedTotalDocCount); }, async assertFieldsPanelsExist(expectedPanelCount: number) { @@ -51,6 +53,166 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ await testSubjects.existOrFail(`mlFieldDataCard ${fieldName} ${cardType}`); }, + async assertCardContentsExists(cardType: string, fieldName?: string) { + await testSubjects.existOrFail( + `mlFieldDataCard ${fieldName} ${cardType} > mlFieldDataCardContent` + ); + }, + + async assertNonMetricCardContents(cardType: string, fieldName: string, exampleCount?: number) { + await this.assertCardContentsExists(cardType, fieldName); + + // Currently the data used in the data visualizer tests only contains these field types. + if (cardType === ML_JOB_FIELD_TYPES.DATE) { + await this.assertDateCardContents(fieldName); + } else if (cardType === ML_JOB_FIELD_TYPES.KEYWORD) { + await this.assertKeywordCardContents(fieldName, exampleCount!); + } else if (cardType === ML_JOB_FIELD_TYPES.TEXT) { + await this.assertTextCardContents(fieldName, exampleCount!); + } + }, + + async assertDocumentCountCardContents() { + await this.assertCardContentsExists('number', undefined); + await testSubjects.existOrFail( + 'mlFieldDataCard undefined number > mlFieldDataCardDocumentCountChart' + ); + }, + + async assertNumberCardContents( + fieldName: string, + docCountFormatted: string, + statsMaxDecimalPlaces: number, + selectedDetailsMode: 'distribution' | 'top_values', + topValuesCount: number + ) { + await this.assertCardContentsExists('number', fieldName); + await this.assertFieldDocCountExists('number', fieldName); + await this.assertFieldDocCountContents('number', fieldName, docCountFormatted); + await this.assertFieldCardinalityExists('number', fieldName); + + await this.assertNumberStatsContents(fieldName, 'Min', statsMaxDecimalPlaces); + await this.assertNumberStatsContents(fieldName, 'Median', statsMaxDecimalPlaces); + await this.assertNumberStatsContents(fieldName, 'Max', statsMaxDecimalPlaces); + + await testSubjects.existOrFail( + `mlFieldDataCard ${fieldName} number > mlFieldDataCardDetailsSelect` + ); + + if (selectedDetailsMode === 'distribution') { + await mlCommonUI.assertRadioGroupValue( + `mlFieldDataCard ${fieldName} number > mlFieldDataCardDetailsSelect`, + 'distribution' + ); + await testSubjects.existOrFail( + `mlFieldDataCard ${fieldName} number > mlFieldDataCardMetricDistributionChart` + ); + + await mlCommonUI.selectRadioGroupValue( + `mlFieldDataCard ${fieldName} number > mlFieldDataCardDetailsSelect`, + 'top_values' + ); + await this.assertTopValuesContents('number', fieldName, topValuesCount); + } else { + await mlCommonUI.assertRadioGroupValue( + `mlFieldDataCard ${fieldName} number > mlFieldDataCardDetailsSelect`, + 'top_values' + ); + await this.assertTopValuesContents('number', fieldName, topValuesCount); + + await mlCommonUI.selectRadioGroupValue( + `mlFieldDataCard ${fieldName} number > mlFieldDataCardDetailsSelect`, + 'distribution' + ); + await testSubjects.existOrFail( + `mlFieldDataCard ${fieldName} number > mlFieldDataCardMetricDistributionChart` + ); + } + }, + + async assertDateCardContents(fieldName: string) { + await this.assertFieldDocCountExists('date', fieldName); + await testSubjects.existOrFail(`mlFieldDataCard ${fieldName} date > mlFieldDataCardEarliest`); + await testSubjects.existOrFail(`mlFieldDataCard ${fieldName} date > mlFieldDataCardLatest`); + }, + + async assertKeywordCardContents(fieldName: string, expectedTopValuesCount: number) { + await this.assertFieldDocCountExists('keyword', fieldName); + await this.assertFieldCardinalityExists('keyword', fieldName); + await this.assertTopValuesContents('keyword', fieldName, expectedTopValuesCount); + }, + + async assertTextCardContents(fieldName: string, expectedExamplesCount: number) { + const examplesList = await testSubjects.find( + `mlFieldDataCard ${fieldName} text > mlFieldDataCardExamplesList` + ); + const examplesListItems = await examplesList.findAllByTagName('li'); + expect(examplesListItems).to.have.length( + expectedExamplesCount, + `Expected example list item count for field '${fieldName}' to be '${expectedExamplesCount}' (got '${examplesListItems.length}')` + ); + }, + + async assertFieldDocCountExists(cardType: string, fieldName: string) { + await testSubjects.existOrFail( + `mlFieldDataCard ${fieldName} ${cardType} > mlFieldDataCardDocCount` + ); + }, + + async assertFieldDocCountContents( + cardType: string, + fieldName: string, + docCountFormatted: string + ) { + const docCountText = await testSubjects.getVisibleText( + `mlFieldDataCard ${fieldName} ${cardType} > mlFieldDataCardDocCount` + ); + expect(docCountText).to.contain( + docCountFormatted, + `Expected doc count for '${fieldName}' to be '${docCountFormatted}' (got contents '${docCountText}')` + ); + }, + + async assertFieldCardinalityExists(cardType: string, fieldName: string) { + await testSubjects.existOrFail( + `mlFieldDataCard ${fieldName} ${cardType} > mlFieldDataCardCardinality` + ); + }, + + async assertNumberStatsContents( + fieldName: string, + stat: 'Min' | 'Median' | 'Max', + maxDecimalPlaces: number + ) { + const statElement = await testSubjects.find( + `mlFieldDataCard ${fieldName} number > mlFieldDataCard${stat}` + ); + const statValue = await statElement.getVisibleText(); + const dotIdx = statValue.indexOf('.'); + const numDecimalPlaces = dotIdx === -1 ? 0 : statValue.length - dotIdx - 1; + expect(numDecimalPlaces).to.be.lessThan( + maxDecimalPlaces + 1, + `Expected number of decimal places for '${fieldName}' '${stat}' to be less than or equal to '${maxDecimalPlaces}' (got '${numDecimalPlaces}')` + ); + }, + + async assertTopValuesContents( + cardType: string, + fieldName: string, + expectedTopValuesCount: number + ) { + const topValuesElement = await testSubjects.find( + `mlFieldDataCard ${fieldName} ${cardType} > mlFieldDataCardTopValues` + ); + const topValuesBars = await topValuesElement.findAllByTestSubject( + 'mlFieldDataCardTopValueBar' + ); + expect(topValuesBars).to.have.length( + expectedTopValuesCount, + `Expected top values count for field '${fieldName}' to be '${expectedTopValuesCount}' (got '${topValuesBars.length}')` + ); + }, + async assertFieldsPanelCardCount(panelFieldTypes: string[], expectedCardCount: number) { await retry.tryForTime(5000, async () => { const filteredCards = await testSubjects.findAll( @@ -75,6 +237,15 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ ); }, + async clearFieldsPanelSearchInput(fieldTypes: string[]) { + const searchBar = await testSubjects.find( + `mlDataVisualizerFieldsPanel ${fieldTypes} > mlDataVisualizerFieldsSearchBarDiv` + ); + const searchBarInput = await searchBar.findByTagName('input'); + await searchBarInput.clearValueWithKeyboard(); + await searchBarInput.pressKeys(browser.keys.ENTER); + }, + async filterFieldsPanelWithSearchString( fieldTypes: string[], filter: string, @@ -119,6 +290,25 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ await this.assertFieldsPanelCardCount(panelFieldTypes, expectedCardCount); }, + async assertSampleSizeInputExists() { + await testSubjects.existOrFail('mlDataVisualizerShardSizeSelect'); + }, + + async setSampleSizeInputValue( + sampleSize: number, + cardType: string, + fieldName: string, + docCountFormatted: string + ) { + await testSubjects.clickWhenNotDisabled('mlDataVisualizerShardSizeSelect'); + await testSubjects.existOrFail(`mlDataVisualizerShardSizeOption ${sampleSize}`); + await testSubjects.click(`mlDataVisualizerShardSizeOption ${sampleSize}`); + + await retry.tryForTime(5000, async () => { + await this.assertFieldDocCountContents(cardType, fieldName, docCountFormatted); + }); + }, + async assertActionsPanelExists() { await testSubjects.existOrFail('mlDataVisualizerActionsPanel'); }, diff --git a/x-pack/test/functional/services/ml/index.ts b/x-pack/test/functional/services/ml/index.ts index b9ff0692cb737..3744e3cad6471 100644 --- a/x-pack/test/functional/services/ml/index.ts +++ b/x-pack/test/functional/services/ml/index.ts @@ -62,7 +62,10 @@ export function MachineLearningProvider(context: FtrProviderContext) { const dataFrameAnalyticsTable = MachineLearningDataFrameAnalyticsTableProvider(context); const dataVisualizer = MachineLearningDataVisualizerProvider(context); const dataVisualizerFileBased = MachineLearningDataVisualizerFileBasedProvider(context, commonUI); - const dataVisualizerIndexBased = MachineLearningDataVisualizerIndexBasedProvider(context); + const dataVisualizerIndexBased = MachineLearningDataVisualizerIndexBasedProvider( + context, + commonUI + ); const jobManagement = MachineLearningJobManagementProvider(context, api); const jobSelection = MachineLearningJobSelectionProvider(context); const jobSourceSelection = MachineLearningJobSourceSelectionProvider(context); diff --git a/x-pack/test/functional_basic/apps/ml/permissions/full_ml_access.ts b/x-pack/test/functional_basic/apps/ml/permissions/full_ml_access.ts index 7a8a58a359b4e..8140b471fc6c8 100644 --- a/x-pack/test/functional_basic/apps/ml/permissions/full_ml_access.ts +++ b/x-pack/test/functional_basic/apps/ml/permissions/full_ml_access.ts @@ -20,7 +20,7 @@ export default function ({ getService }: FtrProviderContext) { for (const user of testUsers) { describe(`(${user})`, function () { const ecIndexPattern = 'ft_module_sample_ecommerce'; - const ecExpectedTotalCount = 287; + const ecExpectedTotalCount = '287'; const ecExpectedFieldPanelCount = 2; const ecExpectedModuleId = 'sample_data_ecommerce'; diff --git a/x-pack/test/functional_basic/apps/ml/permissions/read_ml_access.ts b/x-pack/test/functional_basic/apps/ml/permissions/read_ml_access.ts index 331f2c83639fc..522ca15934e22 100644 --- a/x-pack/test/functional_basic/apps/ml/permissions/read_ml_access.ts +++ b/x-pack/test/functional_basic/apps/ml/permissions/read_ml_access.ts @@ -20,7 +20,7 @@ export default function ({ getService }: FtrProviderContext) { for (const user of testUsers) { describe(`(${user})`, function () { const ecIndexPattern = 'ft_module_sample_ecommerce'; - const ecExpectedTotalCount = 287; + const ecExpectedTotalCount = '287'; const ecExpectedFieldPanelCount = 2; const ecExpectedModuleId = 'sample_data_ecommerce';