Skip to content

Commit

Permalink
[Lens][Unified Field list] Add functional tests to fields lists and s…
Browse files Browse the repository at this point in the history
…ummary popover (#143747)

* ♻️ Add testId handlers

* ✅ Add functional tests

* 💄 Wrap unsupported messages with testId

* 🔧 Enable creation of dataViews without timefield

* ✅ Extends tests for other dataview types + runtime fields

* ✅ Add more checks on top values charts

* 👌 Integrated feedback

* 🐛 Fix testIds and added some logging
  • Loading branch information
dej611 authored Oct 27, 2022
1 parent 7bc63e0 commit 2efad9d
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export interface FieldStatsProps {
'data-test-subj'?: string;
overrideMissingContent?: (params: {
element: JSX.Element;
noDataFound?: boolean;
reason: 'no-data' | 'unsupported';
}) => JSX.Element | null;
overrideFooter?: (params: {
element: JSX.Element;
Expand Down Expand Up @@ -304,7 +304,7 @@ const FieldStatsComponent: React.FC<FieldStatsProps> = ({

return overrideMissingContent
? overrideMissingContent({
noDataFound: false,
reason: 'unsupported',
element: messageNoAnalysis,
})
: messageNoAnalysis;
Expand Down Expand Up @@ -338,7 +338,7 @@ const FieldStatsComponent: React.FC<FieldStatsProps> = ({

return overrideMissingContent
? overrideMissingContent({
noDataFound: true,
reason: 'no-data',
element: messageNoData,
})
: messageNoData;
Expand All @@ -358,12 +358,14 @@ const FieldStatsComponent: React.FC<FieldStatsProps> = ({
defaultMessage: 'Top values',
}),
id: 'topValues',
'data-test-subj': `${dataTestSubject}-buttonGroup-topValuesButton`,
},
{
label: i18n.translate('unifiedFieldList.fieldStats.fieldDistributionLabel', {
defaultMessage: 'Distribution',
}),
id: 'histogram',
'data-test-subj': `${dataTestSubject}-buttonGroup-distributionButton`,
},
]}
onChange={(optionId: string) => {
Expand Down
37 changes: 22 additions & 15 deletions x-pack/plugins/lens/public/datasources/form_based/field_item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) {
initialFocus=".lnsFieldItem__fieldPanel"
className="lnsFieldItem__popoverAnchor"
data-test-subj="lnsFieldListPanelField"
panelProps={{
'data-test-subj': 'lnsFieldListPanelFieldContent',
}}
container={document.querySelector<HTMLElement>('.application') || undefined}
button={
<DragDrop
Expand Down Expand Up @@ -331,26 +334,30 @@ function FieldItemPopoverContents(
field={dataViewField}
data-test-subj="lnsFieldListPanel"
overrideMissingContent={(params) => {
if (params?.noDataFound) {
if (params.reason === 'no-data') {
// TODO: should we replace this with a default message "Analysis is not available for this field?"
const isUsingSampling = core.uiSettings.get('lens:useFieldExistenceSampling');
return (
<>
<EuiText size="s">
{isUsingSampling
? i18n.translate('xpack.lens.indexPattern.fieldStatsSamplingNoData', {
defaultMessage:
'Lens is unable to create visualizations with this field because it does not contain data in the first 500 documents that match your filters. To create a visualization, drag and drop a different field.',
})
: i18n.translate('xpack.lens.indexPattern.fieldStatsNoData', {
defaultMessage:
'Lens is unable to create visualizations with this field because it does not contain data. To create a visualization, drag and drop a different field.',
})}
</EuiText>
</>
<EuiText size="s" data-test-subj="lnsFieldListPanel-missingFieldStats">
{isUsingSampling
? i18n.translate('xpack.lens.indexPattern.fieldStatsSamplingNoData', {
defaultMessage:
'Lens is unable to create visualizations with this field because it does not contain data in the first 500 documents that match your filters. To create a visualization, drag and drop a different field.',
})
: i18n.translate('xpack.lens.indexPattern.fieldStatsNoData', {
defaultMessage:
'Lens is unable to create visualizations with this field because it does not contain data. To create a visualization, drag and drop a different field.',
})}
</EuiText>
);
}
if (params.reason === 'unsupported') {
return (
<EuiText data-test-subj="lnsFieldListPanel-missingFieldStats">
{params.element}
</EuiText>
);
}

return params.element;
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,18 @@ export const FieldsAccordion = memo(function InnerFieldsAccordion({
}
if (hasLoaded) {
return (
<EuiNotificationBadge size="m" color={isFiltered ? 'accent' : 'subdued'}>
<EuiNotificationBadge
size="m"
color={isFiltered ? 'accent' : 'subdued'}
data-test-subj={`${id}-count`}
>
{fieldsCount}
</EuiNotificationBadge>
);
}

return <EuiLoadingSpinner size="m" />;
}, [showExistenceFetchError, showExistenceFetchTimeout, hasLoaded, isFiltered, fieldsCount]);
}, [showExistenceFetchError, showExistenceFetchTimeout, hasLoaded, isFiltered, id, fieldsCount]);

return (
<EuiAccordion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,18 @@ export const FieldsAccordion = memo(function InnerFieldsAccordion({
const extraAction = useMemo(() => {
if (hasLoaded) {
return (
<EuiNotificationBadge size="m" color={isFiltered ? 'accent' : 'subdued'}>
<EuiNotificationBadge
size="m"
color={isFiltered ? 'accent' : 'subdued'}
data-test-subj={`${id}-count`}
>
{fields.length}
</EuiNotificationBadge>
);
}

return <EuiLoadingSpinner size="m" />;
}, [fields.length, hasLoaded, isFiltered]);
}, [fields.length, hasLoaded, id, isFiltered]);

return (
<>
Expand Down
233 changes: 233 additions & 0 deletions x-pack/test/functional/apps/lens/group1/fields_list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../../ftr_provider_context';

export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['visualize', 'lens', 'common', 'header']);
const find = getService('find');
const log = getService('log');
const testSubjects = getService('testSubjects');
const filterBar = getService('filterBar');
const fieldEditor = getService('fieldEditor');
const retry = getService('retry');

describe('lens fields list tests', () => {
for (const datasourceType of ['form-based', 'ad-hoc', 'ad-hoc-no-timefield']) {
describe(`${datasourceType} datasource`, () => {
before(async () => {
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickVisType('lens');

if (datasourceType !== 'form-based') {
await PageObjects.lens.createAdHocDataView(
'*stash*',
datasourceType !== 'ad-hoc-no-timefield'
);
retry.try(async () => {
const selectedPattern = await PageObjects.lens.getDataPanelIndexPattern();
expect(selectedPattern).to.eql('*stash*');
});
}

if (datasourceType !== 'ad-hoc-no-timefield') {
await PageObjects.lens.goToTimeRange();
}

await retry.try(async () => {
await PageObjects.lens.clickAddField();
await fieldEditor.setName('runtime_string');
await fieldEditor.enableValue();
await fieldEditor.typeScript("emit('abc')");
await fieldEditor.save();
await PageObjects.header.waitUntilLoadingHasFinished();
});
});

it('should show all fields as available', async () => {
expect(
await (await testSubjects.find('lnsIndexPatternAvailableFields-count')).getVisibleText()
).to.eql(53);
});

it('should show a histogram and top values popover for numeric field', async () => {
const [fieldId] = await PageObjects.lens.findFieldIdsByType('number');
await log.debug(`Opening field stats for ${fieldId}`);
await testSubjects.click(fieldId);
// check for popover
await testSubjects.exists('lnsFieldListPanel-title');
// check for top values chart
await testSubjects.existOrFail('lnsFieldListPanel-topValues');
const topValuesRows = await testSubjects.findAll('lnsFieldListPanel-topValues-bucket');
expect(topValuesRows.length).to.eql(11);
// check for the Other entry
expect(await topValuesRows[10].getVisibleText()).to.eql('Other\n96.7%');
// switch to date histogram
await testSubjects.click('lnsFieldListPanel-buttonGroup-distributionButton');
// check for date histogram chart
expect(
await find.existsByCssSelector(
'[data-test-subj="lnsFieldListPanelFieldContent"] .echChart'
)
).to.eql(true);
});

it('should show a top values popover for a keyword field', async () => {
const [fieldId] = await PageObjects.lens.findFieldIdsByType('string');
await log.debug(`Opening field stats for ${fieldId}`);
await testSubjects.click(fieldId);
// check for popover
await testSubjects.exists('lnsFieldListPanel-title');
// check for top values chart
await testSubjects.existOrFail('lnsFieldListPanel-topValues');
const topValuesRows = await testSubjects.findAll('lnsFieldListPanel-topValues-bucket');
expect(topValuesRows.length).to.eql(11);
// check for the Other entry
expect(await topValuesRows[10].getVisibleText()).to.eql('Other\n99.9%');
// check no date histogram
expect(
await find.existsByCssSelector(
'[data-test-subj="lnsFieldListPanelFieldContent"] .echChart'
)
).to.eql(false);
});

it('should show a date histogram popover for a date field', async () => {
const [fieldId] = await PageObjects.lens.findFieldIdsByType('date');
await log.debug(`Opening field stats for ${fieldId}`);
await testSubjects.click(fieldId);
// check for popover
await testSubjects.exists('lnsFieldListPanel-title');
// check for date histogram chart
expect(
await find.existsByCssSelector(
'[data-test-subj="lnsFieldListPanelFieldContent"] .echChart'
)
).to.eql(true);
// check no top values chart
await testSubjects.missingOrFail('lnsFieldListPanel-buttonGroup-topValuesButton');
});

it('should show a placeholder message about geo points field', async () => {
const [fieldId] = await PageObjects.lens.findFieldIdsByType('geo_point');
await log.debug(`Opening field stats for ${fieldId}`);
await testSubjects.click(fieldId);
const message = await testSubjects.getVisibleText('lnsFieldListPanel-missingFieldStats');
expect(message).to.eql('Analysis is not available for this field.');
});

it('should show stats for a numeric runtime field', async () => {
await PageObjects.lens.searchField('runtime');
await PageObjects.lens.waitForField('runtime_number');
const [fieldId] = await PageObjects.lens.findFieldIdsByType('number');
await log.debug(`Opening field stats for ${fieldId}`);
await testSubjects.click(fieldId);
// check for popover
await testSubjects.exists('lnsFieldListPanel-title');
// check for top values chart
await testSubjects.existOrFail('lnsFieldListPanel-topValues');
// check values
const topValuesRows = await testSubjects.findAll('lnsFieldListPanel-topValues-bucket');
expect(topValuesRows.length).to.eql(11);
// check for the Other entry
expect(await topValuesRows[10].getVisibleText()).to.eql('Other\n96.7%');
// switch to date histogram
await testSubjects.click('lnsFieldListPanel-buttonGroup-distributionButton');
// check for date histogram chart
expect(
await find.existsByCssSelector(
'[data-test-subj="lnsFieldListPanelFieldContent"] .echChart'
)
).to.eql(true);
});

it('should show stats for a keyword runtime field', async () => {
await PageObjects.lens.searchField('runtime');
await PageObjects.lens.waitForField('runtime_string');
const [fieldId] = await PageObjects.lens.findFieldIdsByType('string');
await log.debug(`Opening field stats for ${fieldId}`);
await testSubjects.click(fieldId);
// check for popover
await testSubjects.exists('lnsFieldListPanel-title');
// check for top values chart
await testSubjects.existOrFail('lnsFieldListPanel-topValues');
// check no date histogram
expect(
await find.existsByCssSelector(
'[data-test-subj="lnsFieldListPanelFieldContent"] .echChart'
)
).to.eql(false);
await PageObjects.lens.searchField('');
});

it('should change popover content if user defines a filter that affects field values', async () => {
// check the current records count for stats
const [fieldId] = await PageObjects.lens.findFieldIdsByType('string');
await log.debug(`Opening field stats for ${fieldId}`);
await testSubjects.click(fieldId);
const valuesCount = parseInt(
(await testSubjects.getVisibleText('lnsFieldListPanel-statsFooter'))
.replaceAll(/(Calculated from | records\.)/g, '')
.replace(',', ''),
10
);
// define a filter
await filterBar.addFilter('geo.src', 'is', 'CN');
await retry.waitFor('Wait for the filter to take effect', async () => {
await testSubjects.click(fieldId);
// check for top values chart has changed compared to the previous test
const newValuesCount = parseInt(
(await testSubjects.getVisibleText('lnsFieldListPanel-statsFooter'))
.replaceAll(/(Calculated from | records\.)/g, '')
.replace(',', ''),
10
);
return newValuesCount < valuesCount;
});
});

// One Fields cap's limitation is to not know when an index has no fields based on filters
it('should detect fields have no data in popup if filter excludes them', async () => {
await filterBar.removeAllFilters();
await filterBar.addFilter('bytes', 'is', '-1');
// check via popup fields have no data
const [fieldId] = await PageObjects.lens.findFieldIdsByType('string');
await log.debug(`Opening field stats for ${fieldId}`);
await retry.try(async () => {
await testSubjects.click(fieldId);
expect(await testSubjects.find('lnsFieldListPanel-missingFieldStats')).to.be.ok();
// close the popover
await testSubjects.click(fieldId);
});
});

if (datasourceType !== 'ad-hoc-no-timefield') {
it('should move some fields as empty when the time range excludes them', async () => {
// remove the filter
await filterBar.removeAllFilters();
// tweak the time range to 17 Sept 2015 to 18 Sept 2015
await PageObjects.lens.goToTimeRange(
'Sep 17, 2015 @ 06:31:44.000',
'Sep 18, 2015 @ 06:31:44.000'
);
// check all fields are empty now
expect(
await (await testSubjects.find('lnsIndexPatternEmptyFields-count')).getVisibleText()
).to.eql(52);
// check avaialble count is 0
expect(
await (
await testSubjects.find('lnsIndexPatternAvailableFields-count')
).getVisibleText()
).to.eql(1);
});
}
});
}
});
}
1 change: 1 addition & 0 deletions x-pack/test/functional/apps/lens/group1/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext
loadTestFile(require.resolve('./table_dashboard'));
loadTestFile(require.resolve('./table'));
loadTestFile(require.resolve('./text_based_languages'));
loadTestFile(require.resolve('./fields_list'));
loadTestFile(require.resolve('./layer_actions'));
}
});
Expand Down
Loading

0 comments on commit 2efad9d

Please sign in to comment.