[ML] Data Frame Analytics: Fix color assertion with risk of percentage being rounded to 0. (#95797)

- Fix color assertion with risk of percentage being rounded to 0.
- Better naming of attributes of expected values.
- Adds assertions to use the sample size dropdown and randomize query switch.
This commit is contained in:
Walter Rafelsberger 2021-04-06 15:21:23 +02:00 committed by GitHub
parent d10935cf07
commit d9a475258b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 279 additions and 86 deletions

View file

@ -331,6 +331,7 @@ export const ScatterplotMatrix: FC<ScatterplotMatrixProps> = ({
fullWidth fullWidth
> >
<EuiSelect <EuiSelect
data-test-subj="mlScatterplotMatrixSampleSizeSelect"
compressed compressed
options={sampleSizeOptions} options={sampleSizeOptions}
value={fetchSize} value={fetchSize}
@ -355,6 +356,7 @@ export const ScatterplotMatrix: FC<ScatterplotMatrixProps> = ({
fullWidth fullWidth
> >
<EuiSwitch <EuiSwitch
data-test-subj="mlScatterplotMatrixRandomizeQuerySwitch"
name="mlScatterplotMatrixRandomizeQuery" name="mlScatterplotMatrixRandomizeQuery"
label={randomizeQuery ? TOGGLE_ON : TOGGLE_OFF} label={randomizeQuery ? TOGGLE_ON : TOGGLE_OFF}
checked={randomizeQuery} checked={randomizeQuery}

View file

@ -12,8 +12,7 @@ export default function ({ getService }: FtrProviderContext) {
const ml = getService('ml'); const ml = getService('ml');
const editedDescription = 'Edited description'; const editedDescription = 'Edited description';
// FLAKY: https://github.com/elastic/kibana/issues/91450 describe('classification creation', function () {
describe.skip('classification creation', function () {
before(async () => { before(async () => {
await esArchiver.loadIfNeeded('ml/bm_classification'); await esArchiver.loadIfNeeded('ml/bm_classification');
await ml.testResources.createIndexPatternIfNeeded('ft_bank_marketing', '@timestamp'); await ml.testResources.createIndexPatternIfNeeded('ft_bank_marketing', '@timestamp');
@ -44,18 +43,18 @@ export default function ({ getService }: FtrProviderContext) {
expected: { expected: {
rocCurveColorState: [ rocCurveColorState: [
// tick/grid/axis // tick/grid/axis
{ key: '#DDDDDD', value: 50 }, { color: '#DDDDDD', percentage: 50 },
// line // line
{ key: '#98A2B3', value: 30 }, { color: '#98A2B3', percentage: 30 },
], ],
scatterplotMatrixColorStats: [ scatterplotMatrixColorStats: [
// marker colors // marker colors
{ key: '#7FC6B3', value: 1 }, { color: '#7FC6B3', percentage: 1 },
{ key: '#88ADD0', value: 0.03 }, { color: '#88ADD0', percentage: 0.03 },
// tick/grid/axis // tick/grid/axis
{ key: '#DDDDDD', value: 8 }, { color: '#DDDDDD', percentage: 8 },
{ key: '#D3DAE6', value: 8 }, { color: '#D3DAE6', percentage: 8 },
{ key: '#F5F7FA', value: 20 }, { color: '#F5F7FA', percentage: 15 },
], ],
row: { row: {
type: 'classification', type: 'classification',
@ -78,6 +77,10 @@ export default function ({ getService }: FtrProviderContext) {
await ml.navigation.navigateToDataFrameAnalytics(); await ml.navigation.navigateToDataFrameAnalytics();
await ml.testExecution.logTestStep('loads the source selection modal'); await ml.testExecution.logTestStep('loads the source selection modal');
// Disable anti-aliasing to stabilize canvas image rendering assertions
await ml.commonUI.disableAntiAliasing();
await ml.dataFrameAnalytics.startAnalyticsCreation(); await ml.dataFrameAnalytics.startAnalyticsCreation();
await ml.testExecution.logTestStep( await ml.testExecution.logTestStep(
@ -106,6 +109,16 @@ export default function ({ getService }: FtrProviderContext) {
await ml.testExecution.logTestStep('displays the include fields selection'); await ml.testExecution.logTestStep('displays the include fields selection');
await ml.dataFrameAnalyticsCreation.assertIncludeFieldsSelectionExists(); await ml.dataFrameAnalyticsCreation.assertIncludeFieldsSelectionExists();
await ml.testExecution.logTestStep(
'sets the sample size to 10000 for the scatterplot matrix'
);
await ml.dataFrameAnalyticsCreation.setScatterplotMatrixSampleSizeSelectValue('10000');
await ml.testExecution.logTestStep(
'sets the randomize query switch to true for the scatterplot matrix'
);
await ml.dataFrameAnalyticsCreation.setScatterplotMatrixRandomizeQueryCheckState(true);
await ml.testExecution.logTestStep('displays the scatterplot matrix'); await ml.testExecution.logTestStep('displays the scatterplot matrix');
await ml.dataFrameAnalyticsCreation.assertScatterplotMatrix( await ml.dataFrameAnalyticsCreation.assertScatterplotMatrix(
testData.expected.scatterplotMatrixColorStats testData.expected.scatterplotMatrixColorStats
@ -237,6 +250,12 @@ export default function ({ getService }: FtrProviderContext) {
await ml.testExecution.logTestStep('displays the results view for created job'); await ml.testExecution.logTestStep('displays the results view for created job');
await ml.dataFrameAnalyticsTable.openResultsView(testData.jobId); await ml.dataFrameAnalyticsTable.openResultsView(testData.jobId);
await ml.dataFrameAnalyticsResults.assertClassificationEvaluatePanelElementsExists(); await ml.dataFrameAnalyticsResults.assertClassificationEvaluatePanelElementsExists();
await ml.dataFrameAnalyticsResults.assertClassificationTablePanelExists();
await ml.dataFrameAnalyticsResults.assertResultsTableExists();
await ml.dataFrameAnalyticsResults.assertResultsTableTrainingFiltersExist();
await ml.dataFrameAnalyticsResults.assertResultsTableNotEmpty();
await ml.testExecution.logTestStep('displays the ROC curve chart');
await ml.commonUI.assertColorsInCanvasElement( await ml.commonUI.assertColorsInCanvasElement(
'mlDFAnalyticsClassificationExplorationRocCurveChart', 'mlDFAnalyticsClassificationExplorationRocCurveChart',
testData.expected.rocCurveColorState, testData.expected.rocCurveColorState,
@ -247,13 +266,23 @@ export default function ({ getService }: FtrProviderContext) {
// since the returned colors vary quite a bit on each run. // since the returned colors vary quite a bit on each run.
20 20
); );
await ml.dataFrameAnalyticsResults.assertClassificationTablePanelExists();
await ml.dataFrameAnalyticsResults.assertResultsTableExists(); await ml.testExecution.logTestStep(
await ml.dataFrameAnalyticsResults.assertResultsTableTrainingFiltersExist(); 'sets the sample size to 10000 for the scatterplot matrix'
await ml.dataFrameAnalyticsResults.assertResultsTableNotEmpty(); );
await ml.dataFrameAnalyticsResults.setScatterplotMatrixSampleSizeSelectValue('10000');
await ml.testExecution.logTestStep(
'sets the randomize query switch to true for the scatterplot matrix'
);
await ml.dataFrameAnalyticsResults.setScatterplotMatrixRandomizeQueryCheckState(true);
await ml.testExecution.logTestStep('displays the scatterplot matrix');
await ml.dataFrameAnalyticsResults.assertScatterplotMatrix( await ml.dataFrameAnalyticsResults.assertScatterplotMatrix(
testData.expected.scatterplotMatrixColorStats testData.expected.scatterplotMatrixColorStats
); );
await ml.commonUI.resetAntiAliasing();
}); });
it('displays the analytics job in the map view', async () => { it('displays the analytics job in the map view', async () => {

View file

@ -52,19 +52,19 @@ export default function ({ getService }: FtrProviderContext) {
], ],
scatterplotMatrixColorsWizard: [ scatterplotMatrixColorsWizard: [
// markers // markers
{ key: '#52B398', value: 25 }, { color: '#52B398', percentage: 15 },
// grey boilerplate // grey boilerplate
{ key: '#6A717D', value: 30 }, { color: '#6A717D', percentage: 33 },
], ],
scatterplotMatrixColorStatsResults: [ scatterplotMatrixColorStatsResults: [
// red markers // red markers
{ key: '#D98071', value: 1 }, { color: '#D98071', percentage: 1 },
// tick/grid/axis, grey markers // tick/grid/axis, grey markers
{ key: '#6A717D', value: 30 }, { color: '#6A717D', percentage: 33 },
{ key: '#D3DAE6', value: 8 }, { color: '#D3DAE6', percentage: 8 },
{ key: '#98A1B3', value: 25 }, { color: '#98A1B3', percentage: 12 },
// anti-aliasing // anti-aliasing
{ key: '#F5F7FA', value: 27 }, { color: '#F5F7FA', percentage: 30 },
], ],
row: { row: {
type: 'outlier_detection', type: 'outlier_detection',
@ -126,6 +126,16 @@ export default function ({ getService }: FtrProviderContext) {
await ml.testExecution.logTestStep('displays the include fields selection'); await ml.testExecution.logTestStep('displays the include fields selection');
await ml.dataFrameAnalyticsCreation.assertIncludeFieldsSelectionExists(); await ml.dataFrameAnalyticsCreation.assertIncludeFieldsSelectionExists();
await ml.testExecution.logTestStep(
'sets the sample size to 10000 for the scatterplot matrix'
);
await ml.dataFrameAnalyticsCreation.setScatterplotMatrixSampleSizeSelectValue('10000');
await ml.testExecution.logTestStep(
'sets the randomize query switch to true for the scatterplot matrix'
);
await ml.dataFrameAnalyticsCreation.setScatterplotMatrixRandomizeQueryCheckState(true);
await ml.testExecution.logTestStep('displays the scatterplot matrix'); await ml.testExecution.logTestStep('displays the scatterplot matrix');
await ml.dataFrameAnalyticsCreation.assertScatterplotMatrix( await ml.dataFrameAnalyticsCreation.assertScatterplotMatrix(
testData.expected.scatterplotMatrixColorsWizard testData.expected.scatterplotMatrixColorsWizard
@ -255,9 +265,23 @@ export default function ({ getService }: FtrProviderContext) {
await ml.dataFrameAnalyticsResults.assertResultsTableExists(); await ml.dataFrameAnalyticsResults.assertResultsTableExists();
await ml.dataFrameAnalyticsResults.assertResultsTableNotEmpty(); await ml.dataFrameAnalyticsResults.assertResultsTableNotEmpty();
await ml.dataFrameAnalyticsResults.assertFeatureInfluenceCellNotEmpty(); await ml.dataFrameAnalyticsResults.assertFeatureInfluenceCellNotEmpty();
await ml.testExecution.logTestStep(
'sets the sample size to 10000 for the scatterplot matrix'
);
await ml.dataFrameAnalyticsResults.setScatterplotMatrixSampleSizeSelectValue('10000');
await ml.testExecution.logTestStep(
'sets the randomize query switch to true for the scatterplot matrix'
);
await ml.dataFrameAnalyticsResults.setScatterplotMatrixRandomizeQueryCheckState(true);
await ml.testExecution.logTestStep('displays the scatterplot matrix');
await ml.dataFrameAnalyticsResults.assertScatterplotMatrix( await ml.dataFrameAnalyticsResults.assertScatterplotMatrix(
testData.expected.scatterplotMatrixColorStatsResults testData.expected.scatterplotMatrixColorStatsResults
); );
await ml.commonUI.resetAntiAliasing();
}); });
it('displays the analytics job in the map view', async () => { it('displays the analytics job in the map view', async () => {

View file

@ -42,12 +42,12 @@ export default function ({ getService }: FtrProviderContext) {
expected: { expected: {
scatterplotMatrixColorStats: [ scatterplotMatrixColorStats: [
// some marker colors of the continuous color scale // some marker colors of the continuous color scale
{ key: '#61AFA3', value: 2 }, { color: '#61AFA3', percentage: 2 },
{ key: '#D1E5E0', value: 2 }, { color: '#D1E5E0', percentage: 2 },
// tick/grid/axis // tick/grid/axis
{ key: '#6A717D', value: 10 }, { color: '#6A717D', percentage: 10 },
{ key: '#F5F7FA', value: 12 }, { color: '#F5F7FA', percentage: 10 },
{ key: '#D3DAE6', value: 3 }, { color: '#D3DAE6', percentage: 3 },
], ],
row: { row: {
type: 'regression', type: 'regression',
@ -71,6 +71,10 @@ export default function ({ getService }: FtrProviderContext) {
await ml.navigation.navigateToDataFrameAnalytics(); await ml.navigation.navigateToDataFrameAnalytics();
await ml.testExecution.logTestStep('loads the source selection modal'); await ml.testExecution.logTestStep('loads the source selection modal');
// Disable anti-aliasing to stabilize canvas image rendering assertions
await ml.commonUI.disableAntiAliasing();
await ml.dataFrameAnalytics.startAnalyticsCreation(); await ml.dataFrameAnalytics.startAnalyticsCreation();
await ml.testExecution.logTestStep( await ml.testExecution.logTestStep(
@ -99,6 +103,16 @@ export default function ({ getService }: FtrProviderContext) {
await ml.testExecution.logTestStep('displays the include fields selection'); await ml.testExecution.logTestStep('displays the include fields selection');
await ml.dataFrameAnalyticsCreation.assertIncludeFieldsSelectionExists(); await ml.dataFrameAnalyticsCreation.assertIncludeFieldsSelectionExists();
await ml.testExecution.logTestStep(
'sets the sample size to 10000 for the scatterplot matrix'
);
await ml.dataFrameAnalyticsCreation.setScatterplotMatrixSampleSizeSelectValue('10000');
await ml.testExecution.logTestStep(
'sets the randomize query switch to true for the scatterplot matrix'
);
await ml.dataFrameAnalyticsCreation.setScatterplotMatrixRandomizeQueryCheckState(true);
await ml.testExecution.logTestStep('displays the scatterplot matrix'); await ml.testExecution.logTestStep('displays the scatterplot matrix');
await ml.dataFrameAnalyticsCreation.assertScatterplotMatrix( await ml.dataFrameAnalyticsCreation.assertScatterplotMatrix(
testData.expected.scatterplotMatrixColorStats testData.expected.scatterplotMatrixColorStats
@ -229,9 +243,23 @@ export default function ({ getService }: FtrProviderContext) {
await ml.dataFrameAnalyticsResults.assertResultsTableExists(); await ml.dataFrameAnalyticsResults.assertResultsTableExists();
await ml.dataFrameAnalyticsResults.assertResultsTableTrainingFiltersExist(); await ml.dataFrameAnalyticsResults.assertResultsTableTrainingFiltersExist();
await ml.dataFrameAnalyticsResults.assertResultsTableNotEmpty(); await ml.dataFrameAnalyticsResults.assertResultsTableNotEmpty();
await ml.testExecution.logTestStep(
'sets the sample size to 10000 for the scatterplot matrix'
);
await ml.dataFrameAnalyticsResults.setScatterplotMatrixSampleSizeSelectValue('10000');
await ml.testExecution.logTestStep(
'sets the randomize query switch to true for the scatterplot matrix'
);
await ml.dataFrameAnalyticsResults.setScatterplotMatrixRandomizeQueryCheckState(true);
await ml.testExecution.logTestStep('displays the scatterplot matrix');
await ml.dataFrameAnalyticsResults.assertScatterplotMatrix( await ml.dataFrameAnalyticsResults.assertScatterplotMatrix(
testData.expected.scatterplotMatrixColorStats testData.expected.scatterplotMatrixColorStats
); );
await ml.commonUI.resetAntiAliasing();
}); });
it('displays the analytics job in the map view', async () => { it('displays the analytics job in the map view', async () => {

View file

@ -161,8 +161,8 @@ export default function ({ getService }: FtrProviderContext) {
id: 'currency', id: 'currency',
legend: '1 category', legend: '1 category',
colorStats: [ colorStats: [
{ key: '#000000', value: 10 }, { color: '#000000', percentage: 10 },
{ key: '#54B399', value: 90 }, { color: '#54B399', percentage: 90 },
], ],
}, },
{ {
@ -177,8 +177,8 @@ export default function ({ getService }: FtrProviderContext) {
id: 'customer_gender', id: 'customer_gender',
legend: '2 categories', legend: '2 categories',
colorStats: [ colorStats: [
{ key: '#000000', value: 15 }, { color: '#000000', percentage: 15 },
{ key: '#54B399', value: 85 }, { color: '#54B399', percentage: 85 },
], ],
}, },
{ {
@ -186,8 +186,8 @@ export default function ({ getService }: FtrProviderContext) {
id: 'customer_id', id: 'customer_id',
legend: 'top 20 of 46 categories', legend: 'top 20 of 46 categories',
colorStats: [ colorStats: [
{ key: '#54B399', value: 35 }, { color: '#54B399', percentage: 35 },
{ key: '#000000', value: 60 }, { color: '#000000', percentage: 60 },
], ],
}, },
{ chartAvailable: false, id: 'customer_last_name', legend: 'Chart not supported.' }, { chartAvailable: false, id: 'customer_last_name', legend: 'Chart not supported.' },
@ -196,8 +196,8 @@ export default function ({ getService }: FtrProviderContext) {
id: 'customer_phone', id: 'customer_phone',
legend: '1 category', legend: '1 category',
colorStats: [ colorStats: [
{ key: '#000000', value: 10 }, { color: '#000000', percentage: 10 },
{ key: '#54B399', value: 90 }, { color: '#54B399', percentage: 90 },
], ],
}, },
{ {
@ -205,8 +205,8 @@ export default function ({ getService }: FtrProviderContext) {
id: 'day_of_week', id: 'day_of_week',
legend: '7 categories', legend: '7 categories',
colorStats: [ colorStats: [
{ key: '#000000', value: 20 }, { color: '#000000', percentage: 20 },
{ key: '#54B399', value: 75 }, { color: '#54B399', percentage: 75 },
], ],
}, },
], ],

View file

@ -8,6 +8,9 @@
import { TRANSFORM_STATE } from '../../../../plugins/transform/common/constants'; import { TRANSFORM_STATE } from '../../../../plugins/transform/common/constants';
import { FtrProviderContext } from '../../ftr_provider_context'; import { FtrProviderContext } from '../../ftr_provider_context';
import type { HistogramCharts } from '../../services/transform/wizard';
import { import {
GroupByEntry, GroupByEntry,
isLatestTransformTestData, isLatestTransformTestData,
@ -44,22 +47,22 @@ export default function ({ getService }: FtrProviderContext) {
}); });
// Only testing that histogram charts are available for runtime fields here // Only testing that histogram charts are available for runtime fields here
const histogramCharts = [ const histogramCharts: HistogramCharts = [
{ {
chartAvailable: true, chartAvailable: true,
id: 'rt_airline_lower', id: 'rt_airline_lower',
legend: '19 categories', legend: '19 categories',
colorStats: [ colorStats: [
{ key: '#000000', value: 48 }, { color: '#000000', percentage: 48 },
{ key: '#54B399', value: 41 }, { color: '#54B399', percentage: 41 },
], ],
}, },
{ {
chartAvailable: true, chartAvailable: true,
id: 'rt_responsetime_x_2', id: 'rt_responsetime_x_2',
colorStats: [ colorStats: [
{ key: '#54B399', value: 5 }, { color: '#54B399', percentage: 5 },
{ key: '#000000', value: 95 }, { color: '#000000', percentage: 95 },
], ],
}, },
]; ];

View file

@ -8,12 +8,13 @@
import { rgb, nest } from 'd3'; import { rgb, nest } from 'd3';
interface ColorStat { interface ColorStat {
key: string; color: string;
value: number; percentage: number;
pixels?: number;
withinTolerance?: boolean; withinTolerance?: boolean;
} }
type ColorStats = ColorStat[]; export type CanvasElementColorStats = ColorStat[];
import { FtrProviderContext } from '../ftr_provider_context'; import { FtrProviderContext } from '../ftr_provider_context';
@ -75,12 +76,12 @@ export async function CanvasElementProvider({ getService }: FtrProviderContext)
*/ */
public async getColorStats( public async getColorStats(
selector: string, selector: string,
expectedColorStats?: ColorStats, expectedColorStats?: CanvasElementColorStats,
exclude?: string[], exclude?: string[],
percentageThreshold = 5, percentageThreshold = 5,
channelTolerance = 10, channelTolerance = 10,
valueTolerance = 10 valueTolerance = 10
): Promise<ColorStats> { ): Promise<CanvasElementColorStats> {
const imageData = await this.getImageData(selector); const imageData = await this.getImageData(selector);
// transform the array of RGBA numbers to an array of hex values // transform the array of RGBA numbers to an array of hex values
const colors: string[] = []; const colors: string[] = [];
@ -109,21 +110,24 @@ export async function CanvasElementProvider({ getService }: FtrProviderContext)
.filter((s) => getPixelPercentage(s.values.length) >= percentageThreshold) .filter((s) => getPixelPercentage(s.values.length) >= percentageThreshold)
.sort((a, b) => a.key.localeCompare(b.key)) .sort((a, b) => a.key.localeCompare(b.key))
.map((s, i) => { .map((s, i) => {
const value = getPixelPercentage(s.values.length); const percentage = getPixelPercentage(s.values.length);
const pixels = s.values.length;
return { return {
key: s.key, color: s.key,
value, percentage,
pixels,
...(expectedColorStats !== undefined ...(expectedColorStats !== undefined
? { ? {
withinTolerance: withinTolerance:
this.isValueWithinTolerance( this.isValueWithinTolerance(
value, percentage,
expectedColorStats[i]?.value, pixels,
expectedColorStats[i]?.percentage,
valueTolerance valueTolerance
) && ) &&
this.isColorWithinTolerance( this.isColorWithinTolerance(
s.key, s.key,
expectedColorStats[i]?.key, expectedColorStats[i]?.color,
channelTolerance channelTolerance
), ),
} }
@ -138,12 +142,12 @@ export async function CanvasElementProvider({ getService }: FtrProviderContext)
*/ */
public async getColorStatsWithColorTolerance( public async getColorStatsWithColorTolerance(
selector: string, selector: string,
expectedColorStats: ColorStats, expectedColorStats: CanvasElementColorStats,
exclude?: string[], exclude?: string[],
percentageThreshold = 0, percentageThreshold = 0,
channelTolerance = 10, channelTolerance = 10,
valueTolerance = 10 valueTolerance = 10
) { ): Promise<CanvasElementColorStats> {
const actualColorStats = await this.getColorStats( const actualColorStats = await this.getColorStats(
selector, selector,
undefined, undefined,
@ -154,16 +158,26 @@ export async function CanvasElementProvider({ getService }: FtrProviderContext)
); );
return expectedColorStats.map((expectedColor) => { return expectedColorStats.map((expectedColor) => {
const colorPercentageWithinTolerance = actualColorStats const colorsWithinTolerance = actualColorStats.filter((d) =>
.filter((d) => this.isColorWithinTolerance(d.key, expectedColor.key, channelTolerance)) this.isColorWithinTolerance(d.color, expectedColor.color, channelTolerance)
.reduce((sum, x) => sum + x.value, 0); );
const colorPercentageWithinTolerance = colorsWithinTolerance.reduce(
(sum, x) => sum + x.percentage,
0
);
const pixelsWithinTolerance = colorsWithinTolerance.reduce(
(sum, x) => sum + (x.pixels || 0),
0
);
return { return {
key: expectedColor.key, color: expectedColor.color,
value: colorPercentageWithinTolerance, percentage: colorPercentageWithinTolerance,
pixels: pixelsWithinTolerance,
withinTolerance: this.isValueWithinTolerance( withinTolerance: this.isValueWithinTolerance(
colorPercentageWithinTolerance, colorPercentageWithinTolerance,
expectedColor.value, pixelsWithinTolerance,
expectedColor.percentage,
valueTolerance valueTolerance
), ),
}; };
@ -202,15 +216,24 @@ export async function CanvasElementProvider({ getService }: FtrProviderContext)
/** /**
* Returns if a given value is within the tolerated range of an expected value * Returns if a given value is within the tolerated range of an expected value
* *
* @param actualValue * @param actualPercentage
* @param expectedValue * @param actualPixels
* @param expectedPercentage
* @param toleranceRange * @param toleranceRange
* @returns if actualValue is within the tolerance of expectedValue * @returns if actualValue is within the tolerance of expectedValue
*/ */
public isValueWithinTolerance(actualValue: number, expectedValue: number, toleranceRange = 10) { public isValueWithinTolerance(
const lower = expectedValue - toleranceRange / 2; actualPercentage: number,
const upper = expectedValue + toleranceRange / 2; actualPixels: number,
return actualValue > 0 && lower <= actualValue && upper >= actualValue; expectedPercentage: number,
toleranceRange = 10
) {
const lower = expectedPercentage - toleranceRange / 2;
const upper = expectedPercentage + toleranceRange / 2;
return (
// actualPercentage could be rounded to 0 so we check against actualPixels if they are above 0.
actualPixels > 0 && lower <= actualPercentage && upper >= actualPercentage
);
} }
})(); })();
} }

View file

@ -10,18 +10,13 @@ import { ProvidedType } from '@kbn/test/types/ftr';
import { FtrProviderContext } from '../../ftr_provider_context'; import { FtrProviderContext } from '../../ftr_provider_context';
import type { CanvasElementColorStats } from '../canvas_element';
interface SetValueOptions { interface SetValueOptions {
clearWithKeyboard?: boolean; clearWithKeyboard?: boolean;
typeCharByChar?: boolean; typeCharByChar?: boolean;
} }
// key: color hex code, e.g. #FF3344
// value: the expected percentage of the color to be present in the canvas element
export type CanvasElementColorStats = Array<{
key: string;
value: number;
}>;
export type MlCommonUI = ProvidedType<typeof MachineLearningCommonUIProvider>; export type MlCommonUI = ProvidedType<typeof MachineLearningCommonUIProvider>;
export function MachineLearningCommonUIProvider({ getService }: FtrProviderContext) { export function MachineLearningCommonUIProvider({ getService }: FtrProviderContext) {

View file

@ -9,7 +9,8 @@ import expect from '@kbn/expect';
import { DataFrameAnalyticsConfig } from '../../../../plugins/ml/public/application/data_frame_analytics/common'; import { DataFrameAnalyticsConfig } from '../../../../plugins/ml/public/application/data_frame_analytics/common';
import { FtrProviderContext } from '../../ftr_provider_context'; import { FtrProviderContext } from '../../ftr_provider_context';
import type { CanvasElementColorStats, MlCommonUI } from './common_ui'; import type { CanvasElementColorStats } from '../canvas_element';
import type { MlCommonUI } from './common_ui';
import { MlApi } from './api'; import { MlApi } from './api';
import { import {
isRegressionAnalysis, isRegressionAnalysis,
@ -273,6 +274,45 @@ export function MachineLearningDataFrameAnalyticsCreationProvider(
); );
}, },
async setScatterplotMatrixSampleSizeSelectValue(selectValue: string) {
await testSubjects.selectValue('mlScatterplotMatrixSampleSizeSelect', selectValue);
const actualSelectState = await testSubjects.getAttribute(
'mlScatterplotMatrixSampleSizeSelect',
'value'
);
expect(actualSelectState).to.eql(
selectValue,
`Sample size should be '${selectValue}' (got '${actualSelectState}')`
);
},
async getScatterplotMatrixRandomizeQuerySwitchCheckState(): Promise<boolean> {
const state = await testSubjects.getAttribute(
'mlScatterplotMatrixRandomizeQuerySwitch',
'aria-checked'
);
return state === 'true';
},
async assertScatterplotMatrixRandomizeQueryCheckState(expectedCheckState: boolean) {
const actualCheckState = await this.getScatterplotMatrixRandomizeQuerySwitchCheckState();
expect(actualCheckState).to.eql(
expectedCheckState,
`Randomize query check state should be '${expectedCheckState}' (got '${actualCheckState}')`
);
},
async setScatterplotMatrixRandomizeQueryCheckState(checkState: boolean) {
await retry.tryForTime(30000, async () => {
if ((await this.getScatterplotMatrixRandomizeQuerySwitchCheckState()) !== checkState) {
await testSubjects.click('mlScatterplotMatrixRandomizeQuerySwitch');
}
await this.assertScatterplotMatrixRandomizeQueryCheckState(checkState);
});
},
async assertTrainingPercentInputExists() { async assertTrainingPercentInputExists() {
await testSubjects.existOrFail('mlAnalyticsCreateJobWizardTrainingPercentSlider'); await testSubjects.existOrFail('mlAnalyticsCreateJobWizardTrainingPercentSlider');
}, },

View file

@ -10,7 +10,8 @@ import { WebElementWrapper } from 'test/functional/services/lib/web_element_wrap
import { FtrProviderContext } from '../../ftr_provider_context'; import { FtrProviderContext } from '../../ftr_provider_context';
import type { CanvasElementColorStats, MlCommonUI } from './common_ui'; import type { CanvasElementColorStats } from '../canvas_element';
import type { MlCommonUI } from './common_ui';
export function MachineLearningDataFrameAnalyticsResultsProvider( export function MachineLearningDataFrameAnalyticsResultsProvider(
{ getService }: FtrProviderContext, { getService }: FtrProviderContext,
@ -84,14 +85,60 @@ export function MachineLearningDataFrameAnalyticsResultsProvider(
}); });
}, },
async setScatterplotMatrixSampleSizeSelectValue(selectValue: string) {
await testSubjects.selectValue('mlScatterplotMatrixSampleSizeSelect', selectValue);
const actualSelectState = await testSubjects.getAttribute(
'mlScatterplotMatrixSampleSizeSelect',
'value'
);
expect(actualSelectState).to.eql(
selectValue,
`Sample size should be '${selectValue}' (got '${actualSelectState}')`
);
},
async getScatterplotMatrixRandomizeQuerySwitchCheckState(): Promise<boolean> {
const state = await testSubjects.getAttribute(
'mlScatterplotMatrixRandomizeQuerySwitch',
'aria-checked'
);
return state === 'true';
},
async assertScatterplotMatrixRandomizeQueryCheckState(expectedCheckState: boolean) {
const actualCheckState = await this.getScatterplotMatrixRandomizeQuerySwitchCheckState();
expect(actualCheckState).to.eql(
expectedCheckState,
`Randomize query check state should be '${expectedCheckState}' (got '${actualCheckState}')`
);
},
async setScatterplotMatrixRandomizeQueryCheckState(checkState: boolean) {
await retry.tryForTime(30000, async () => {
if ((await this.getScatterplotMatrixRandomizeQuerySwitchCheckState()) !== checkState) {
await testSubjects.click('mlScatterplotMatrixRandomizeQuerySwitch');
}
await this.assertScatterplotMatrixRandomizeQueryCheckState(checkState);
});
},
async assertScatterplotMatrix(expectedValue: CanvasElementColorStats) { async assertScatterplotMatrix(expectedValue: CanvasElementColorStats) {
await testSubjects.existOrFail('mlDFExpandableSection-splom > mlScatterplotMatrix loaded', { await testSubjects.existOrFail('mlDFExpandableSection-splom > mlScatterplotMatrix loaded', {
timeout: 5000, timeout: 5000,
}); });
await testSubjects.scrollIntoView('mlDFExpandableSection-splom > mlScatterplotMatrix loaded'); await testSubjects.scrollIntoView('mlDFExpandableSection-splom > mlScatterplotMatrix loaded');
await mlCommonUI.assertColorsInCanvasElement('mlDFExpandableSection-splom', expectedValue, [ await mlCommonUI.assertColorsInCanvasElement(
'#000000', 'mlDFExpandableSection-splom',
]); expectedValue,
['#000000'],
undefined,
undefined,
// increased tolerance up from 10 to 20
// since the returned randomized colors vary quite a bit on each run.
20
);
}, },
async assertFeatureImportanceDecisionPathChartElementsExists() { async assertFeatureImportanceDecisionPathChartElementsExists() {

View file

@ -10,6 +10,15 @@ import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context'; import { FtrProviderContext } from '../../ftr_provider_context';
import type { CanvasElementColorStats } from '../canvas_element';
export type HistogramCharts = Array<{
chartAvailable: boolean;
id: string;
legend?: string;
colorStats?: CanvasElementColorStats;
}>;
export function TransformWizardProvider({ getService, getPageObjects }: FtrProviderContext) { export function TransformWizardProvider({ getService, getPageObjects }: FtrProviderContext) {
const aceEditor = getService('aceEditor'); const aceEditor = getService('aceEditor');
const canvasElement = getService('canvasElement'); const canvasElement = getService('canvasElement');
@ -224,14 +233,7 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi
); );
}, },
async assertIndexPreviewHistogramCharts( async assertIndexPreviewHistogramCharts(expectedHistogramCharts: HistogramCharts) {
expectedHistogramCharts: Array<{
chartAvailable: boolean;
id: string;
legend?: string;
colorStats?: any[];
}>
) {
// For each chart, get the content of each header cell and assert // For each chart, get the content of each header cell and assert
// the legend text and column id and if the chart should be present or not. // the legend text and column id and if the chart should be present or not.
await retry.tryForTime(5000, async () => { await retry.tryForTime(5000, async () => {
@ -244,7 +246,7 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi
if (expected.colorStats !== undefined) { if (expected.colorStats !== undefined) {
const sortedExpectedColorStats = [...expected.colorStats].sort((a, b) => const sortedExpectedColorStats = [...expected.colorStats].sort((a, b) =>
a.key.localeCompare(b.key) a.color.localeCompare(b.color)
); );
const actualColorStats = await canvasElement.getColorStats( const actualColorStats = await canvasElement.getColorStats(