[ML] Transforms/Data Frame Analytics: Fix data grid height. (#98923)

Adds a short term fix/override for the height of data grid in transform and data frame analytics wizards.
EUI itself sets the height style of euiDataGrid__virtualized. For some reason it's not able to adjust that height within the wizards when you toggle histogram visibility or change rows shown per page.
This PR fixes it by wrapping the data grid with EuiMutationObserver to listen for updates within the data grid and setting the height of euiDataGrid__virtualized to auto on every update. For now this should be a safe short term fix since we don't use the full screen option.
This commit is contained in:
Walter Rafelsberger 2021-05-03 16:55:00 +02:00 committed by GitHub
parent 38a8989a9c
commit 763e08a1dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 125 additions and 84 deletions

View file

@ -6,8 +6,9 @@
*/
import { isEqual } from 'lodash';
import React, { memo, useEffect, FC, useMemo } from 'react';
import React, { memo, useEffect, useMemo, useRef, FC } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiButtonEmpty,
@ -18,6 +19,7 @@ import {
EuiDataGrid,
EuiFlexGroup,
EuiFlexItem,
EuiMutationObserver,
EuiSpacer,
EuiTitle,
EuiToolTip,
@ -41,6 +43,7 @@ import {
FeatureImportance,
TopClasses,
} from '../../../../common/types/feature_importance';
import { isPopulatedObject } from '../../../../common/util/object_utils';
import { DEFAULT_RESULTS_FIELD } from '../../../../common/constants/data_frame_analytics';
import { DataFrameAnalysisConfigType } from '../../../../common/types/data_frame_analytics';
@ -190,6 +193,8 @@ export const DataGrid: FC<Props> = memo(
}
}, [invalidSortingColumnns, toastNotifications]);
const wrapperEl = useRef<HTMLDivElement>(null);
if (status === INDEX_STATUS.LOADED && data.length === 0) {
return (
<div data-test-subj={`${dataTestSubj} empty`}>
@ -261,8 +266,22 @@ export const DataGrid: FC<Props> = memo(
}
}
const onMutation = () => {
if (wrapperEl.current !== null) {
const els = wrapperEl.current.querySelectorAll('.euiDataGrid__virtualized');
for (const el of Array.from(els)) {
if (isPopulatedObject(el) && isPopulatedObject(el.style) && el.style.height !== 'auto') {
el.style.height = 'auto';
}
}
}
};
return (
<div data-test-subj={`${dataTestSubj} ${status === INDEX_STATUS.ERROR ? 'error' : 'loaded'}`}>
<div
data-test-subj={`${dataTestSubj} ${status === INDEX_STATUS.ERROR ? 'error' : 'loaded'}`}
ref={wrapperEl}
>
{isWithHeader(props) && (
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
<EuiFlexItem>
@ -310,62 +329,73 @@ export const DataGrid: FC<Props> = memo(
<EuiSpacer size="m" />
</div>
)}
<div className="mlDataGrid">
<EuiDataGrid
aria-label={isWithHeader(props) ? props.title : ''}
columns={columnsWithChartsActionized.map((c) => {
c.initialWidth = 165;
return c;
})}
columnVisibility={{ visibleColumns, setVisibleColumns }}
gridStyle={euiDataGridStyle}
rowCount={rowCount}
renderCellValue={renderCellValue}
sorting={{ columns: sortingColumns, onSort }}
toolbarVisibility={{
...euiDataGridToolbarSettings,
...(chartsButtonVisible
? {
additionalControls: (
<EuiToolTip
content={i18n.translate('xpack.ml.dataGrid.histogramButtonToolTipContent', {
defaultMessage:
'Queries run to fetch histogram chart data will use a sample size per shard of {samplerShardSize} documents.',
values: {
samplerShardSize: DEFAULT_SAMPLER_SHARD_SIZE,
},
})}
>
<EuiButtonEmpty
aria-pressed={chartsVisible === true}
className={`euiDataGrid__controlBtn${
chartsVisible === true ? ' euiDataGrid__controlBtn--active' : ''
}`}
data-test-subj={`${dataTestSubj}HistogramButton`}
size="xs"
iconType="visBarVertical"
color="text"
onClick={toggleChartVisibility}
disabled={chartsVisible === undefined}
>
{i18n.translate('xpack.ml.dataGrid.histogramButtonText', {
defaultMessage: 'Histogram charts',
})}
</EuiButtonEmpty>
</EuiToolTip>
),
}
: {}),
}}
popoverContents={popOverContent}
pagination={{
...pagination,
pageSizeOptions: [5, 10, 25],
onChangeItemsPerPage,
onChangePage,
}}
/>
</div>
<EuiMutationObserver
observerOptions={{ subtree: true, attributes: true, childList: true }}
onMutation={onMutation}
>
{(mutationRef) => (
<div className="mlDataGrid" ref={mutationRef}>
<EuiDataGrid
aria-label={isWithHeader(props) ? props.title : ''}
columns={columnsWithChartsActionized.map((c) => {
c.initialWidth = 165;
return c;
})}
columnVisibility={{ visibleColumns, setVisibleColumns }}
gridStyle={euiDataGridStyle}
rowCount={rowCount}
renderCellValue={renderCellValue}
sorting={{ columns: sortingColumns, onSort }}
toolbarVisibility={{
...euiDataGridToolbarSettings,
...(chartsButtonVisible
? {
additionalControls: (
<EuiToolTip
content={i18n.translate(
'xpack.ml.dataGrid.histogramButtonToolTipContent',
{
defaultMessage:
'Queries run to fetch histogram chart data will use a sample size per shard of {samplerShardSize} documents.',
values: {
samplerShardSize: DEFAULT_SAMPLER_SHARD_SIZE,
},
}
)}
>
<EuiButtonEmpty
aria-pressed={chartsVisible === true}
className={`euiDataGrid__controlBtn${
chartsVisible === true ? ' euiDataGrid__controlBtn--active' : ''
}`}
data-test-subj={`${dataTestSubj}HistogramButton`}
size="xs"
iconType="visBarVertical"
color="text"
onClick={toggleChartVisibility}
disabled={chartsVisible === undefined}
>
<FormattedMessage
id="xpack.ml.dataGrid.histogramButtonText"
defaultMessage="Histogram charts"
/>
</EuiButtonEmpty>
</EuiToolTip>
),
}
: {}),
}}
popoverContents={popOverContent}
pagination={{
...pagination,
pageSizeOptions: [5, 10, 25],
onChangeItemsPerPage,
onChangePage,
}}
/>
</div>
)}
</EuiMutationObserver>
</div>
);
},

View file

@ -6,9 +6,10 @@
*/
import React, { FC } from 'react';
import { IntlProvider } from 'react-intl';
import '@testing-library/jest-dom/extend-expect';
import { render, waitFor } from '@testing-library/react';
import { render, screen, waitFor } from '@testing-library/react';
import { renderHook } from '@testing-library/react-hooks';
import { CoreSetup } from 'src/core/public';
@ -48,7 +49,9 @@ describe('Transform: useIndexData()', () => {
test('indexPattern set triggers loading', async () => {
const mlShared = await getMlSharedImports();
const wrapper: FC = ({ children }) => (
<MlSharedContext.Provider value={mlShared}>{children}</MlSharedContext.Provider>
<IntlProvider locale="en">
<MlSharedContext.Provider value={mlShared}>{children}</MlSharedContext.Provider>
</IntlProvider>
);
const { result, waitForNextUpdate } = renderHook(
@ -101,17 +104,21 @@ describe('Transform: <DataGrid /> with useIndexData()', () => {
return <DataGrid {...props} />;
};
const { queryByText } = render(
<MlSharedContext.Provider value={mlSharedImports}>
<Wrapper />
</MlSharedContext.Provider>
render(
<IntlProvider locale="en">
<MlSharedContext.Provider value={mlSharedImports}>
<Wrapper />
</MlSharedContext.Provider>
</IntlProvider>
);
// Act
// Assert
await waitFor(() => {
expect(queryByText('the-index-preview-title')).toBeInTheDocument();
expect(queryByText('Cross-cluster search returned no fields data.')).not.toBeInTheDocument();
expect(screen.queryByText('the-index-preview-title')).toBeInTheDocument();
expect(
screen.queryByText('Cross-cluster search returned no fields data.')
).not.toBeInTheDocument();
});
});
@ -140,17 +147,21 @@ describe('Transform: <DataGrid /> with useIndexData()', () => {
return <DataGrid {...props} />;
};
const { queryByText } = render(
<MlSharedContext.Provider value={mlSharedImports}>
<Wrapper />
</MlSharedContext.Provider>
render(
<IntlProvider locale="en">
<MlSharedContext.Provider value={mlSharedImports}>
<Wrapper />
</MlSharedContext.Provider>
</IntlProvider>
);
// Act
// Assert
await waitFor(() => {
expect(queryByText('the-index-preview-title')).toBeInTheDocument();
expect(queryByText('Cross-cluster search returned no fields data.')).toBeInTheDocument();
expect(screen.queryByText('the-index-preview-title')).toBeInTheDocument();
expect(
screen.queryByText('Cross-cluster search returned no fields data.')
).toBeInTheDocument();
});
});
});

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { render, fireEvent, waitFor } from '@testing-library/react';
import { render, fireEvent, screen, waitFor } from '@testing-library/react';
import React from 'react';
import moment from 'moment-timezone';
import { TransformListRow } from '../../../../common';
@ -35,31 +35,31 @@ describe('Transform: Transform List <ExpandedRow />', () => {
// @ts-expect-error mock data is too loosely typed
const item: TransformListRow = transformListRow;
const { getByText, getByTestId } = render(
render(
<MlSharedContext.Provider value={mlShared}>
<ExpandedRow item={item} />
</MlSharedContext.Provider>
);
await waitFor(() => {
expect(getByText('Details')).toBeInTheDocument();
expect(getByText('Stats')).toBeInTheDocument();
expect(getByText('JSON')).toBeInTheDocument();
expect(getByText('Messages')).toBeInTheDocument();
expect(getByText('Preview')).toBeInTheDocument();
expect(screen.getByText('Details')).toBeInTheDocument();
expect(screen.getByText('Stats')).toBeInTheDocument();
expect(screen.getByText('JSON')).toBeInTheDocument();
expect(screen.getByText('Messages')).toBeInTheDocument();
expect(screen.getByText('Preview')).toBeInTheDocument();
const tabContent = getByTestId('transformDetailsTabContent');
const tabContent = screen.getByTestId('transformDetailsTabContent');
expect(tabContent).toBeInTheDocument();
expect(getByTestId('transformDetailsTab')).toHaveAttribute('aria-selected', 'true');
expect(screen.getByTestId('transformDetailsTab')).toHaveAttribute('aria-selected', 'true');
expect(within(tabContent).getByText('General')).toBeInTheDocument();
});
fireEvent.click(getByTestId('transformStatsTab'));
fireEvent.click(screen.getByTestId('transformStatsTab'));
await waitFor(() => {
expect(getByTestId('transformStatsTab')).toHaveAttribute('aria-selected', 'true');
const tabContent = getByTestId('transformDetailsTabContent');
expect(screen.getByTestId('transformStatsTab')).toHaveAttribute('aria-selected', 'true');
const tabContent = screen.getByTestId('transformDetailsTabContent');
expect(within(tabContent).getByText('Stats')).toBeInTheDocument();
});
});