Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EuiDataGrid] Provide row elements to wrap cells #5285

Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
3de3ce9
Provide rows for datagrid cells to be owned by
chandlerprall Sep 23, 2021
4594779
Merge branch 'master' into bug/4474-datagrid-row-accessibility
chandlerprall Sep 27, 2021
04d3ab5
changelog
chandlerprall Sep 27, 2021
9a6dc07
Merge branch 'master' into bug/4474-datagrid-row-accessibility
chandlerprall Oct 5, 2021
f786f5f
undoing things
chandlerprall Oct 5, 2021
06ac499
working except virtualized container
chandlerprall Oct 6, 2021
0129ea4
working row wrapper implementation
chandlerprall Oct 7, 2021
c05b457
Create datagrid row elements on-demand and render cells via portals
chandlerprall Oct 19, 2021
179da4a
Provide rows for datagrid cells to be owned by
chandlerprall Sep 23, 2021
24f5c53
changelog
chandlerprall Sep 27, 2021
a2ccb72
undoing things
chandlerprall Oct 5, 2021
eb4344c
working except virtualized container
chandlerprall Oct 6, 2021
535a2a9
working row wrapper implementation
chandlerprall Oct 7, 2021
9151350
Create datagrid row elements on-demand and render cells via portals
chandlerprall Oct 19, 2021
814feb1
Small style cleanup
chandlerprall Oct 19, 2021
95b7008
updated changelog
chandlerprall Oct 19, 2021
0ba3a02
updated changelog
chandlerprall Oct 19, 2021
1569aa0
fixing a bad changelog merge
chandlerprall Oct 19, 2021
9a6df52
oh that style was important
chandlerprall Oct 19, 2021
ae336f9
Update src/components/datagrid/body/data_grid_row_manager.ts
chandlerprall Oct 26, 2021
8dc8ad4
Update src/components/datagrid/data_grid_types.ts
chandlerprall Oct 26, 2021
7633c1b
Ensure unique ID in combobox with prepended or appended labels (#5229)
1Copenut Oct 19, 2021
c3e8b49
update i18ntokens
chandlerprall Oct 19, 2021
5ac481f
40.0.0
chandlerprall Oct 19, 2021
e0dd86e
Updated documentation.
chandlerprall Oct 19, 2021
ca721f2
Add combined Jest+Cypress code coverage reports (#5262)
constancecchen Oct 20, 2021
86af5ba
[EuiMarkdownEditor] Add `remark-breaks` and line break plugin (#5272)
i-a-n Oct 20, 2021
2553d89
[Docs] Separated out Borders to its own page & [EuiTableRowCell] fixe…
cchaos Oct 20, 2021
fdc387e
Fix CL from #5272
cchaos Oct 25, 2021
d468c85
[Docs] Update EuiDatePicker types (#5318)
thompsongl Oct 25, 2021
dea1eb9
[Cypress] Add flakey test retries + harden intermittent context menu …
constancecchen Oct 25, 2021
c995387
[Docs] Fix Colors guidelines (#5316)
thompsongl Oct 26, 2021
a60d734
Update dependency @elastic/charts to ^38.1.0 (#5321)
renovate[bot] Oct 26, 2021
ae515b6
PR feedback
chandlerprall Oct 26, 2021
f5b02ef
Added a cypress test for datagrid row rendering
chandlerprall Nov 1, 2021
82614cf
Merge branch 'master' into bug/4474-datagrid-row-accessibility-alternate
chandlerprall Nov 1, 2021
8336957
Merge branch 'master' into bug/4474-datagrid-row-accessibility-alternate
chandlerprall Nov 1, 2021
baa7b25
Revert changes(?) to docs/
chandlerprall Nov 1, 2021
937403e
revert changelog reformats
chandlerprall Nov 1, 2021
705edad
re-ignore a couple datagrid example pages when running a11y tests
chandlerprall Nov 2, 2021
55a5f95
Alternate way to know if the datagrid cells have rendered
chandlerprall Nov 3, 2021
420b37d
Merge branch 'main' into bug/4474-datagrid-row-accessibility-alternate
chandlerprall Nov 4, 2021
ad5c72b
PR feedback
chandlerprall Nov 4, 2021
6d10f54
changelog
chandlerprall Nov 5, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
**Bug fixes**

- Fixed styling of `align: center` for mobile version of `EuiTableRowCell` ([#5323](https://github.com/elastic/eui/pull/5323))
- Fixed an accessibility issue where `EuiDataGrid` cells weren't owned by `role=row` elements ([#5285](https://github.com/elastic/eui/pull/5285))
- Fixed `endDateControl` `className` in `EuiDatePickerRange` ([#5329](https://github.com/elastic/eui/pull/5329))

**Breaking changes**

Expand All @@ -21,10 +23,6 @@
- Removed `makeId` ([#5323](https://github.com/elastic/eui/pull/5323))
- Removed mobile-only props from `EuiTableRowCell` ([#5323](https://github.com/elastic/eui/pull/5323))

**Bug fixes**

- Fixed `endDateControl` `className` in `EuiDatePickerRange` ([#5329](https://github.com/elastic/eui/pull/5329))

## [`40.1.0`](https://github.com/elastic/eui/tree/v40.1.0)

- Added styling support for `valign` prop on `EuiTableRowCell` ([#5283](https://github.com/elastic/eui/pull/5283))
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"test-unit": "cross-env NODE_ENV=test jest --config ./scripts/jest/config.json",
"test-a11y": "node ./scripts/a11y-testing",
"test-staged": "yarn lint && node scripts/test-staged.js",
"test-cypress": "cross-env NODE_ENV=cypress_test cypress run-ct",
"test-cypress-dev": "cross-env NODE_ENV=cypress_test cypress open-ct",
"test-cypress": "cross-env BABEL_MODULES=false NODE_ENV=cypress_test cypress run-ct",
"test-cypress-dev": "cross-env BABEL_MODULES=false NODE_ENV=cypress_test cypress open-ct",
"combine-test-coverage": "sh ./scripts/combine-coverage.sh",
"start-test-server": "BABEL_MODULES=false NODE_ENV=puppeteer NODE_OPTIONS=--max-old-space-size=4096 webpack-dev-server --config src-docs/webpack.config.js --port 9999",
"yo-component": "yo ./generator-eui/app/component.js",
Expand Down
9 changes: 0 additions & 9 deletions scripts/a11y-testing.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,6 @@ const docsPages = async (root, page) => {
`${root}#/forms/color-selection`,
`${root}#/forms/date-picker`,
`${root}#/forms/super-date-picker`,
`${root}#/tabular-content/data-grid`,
`${root}#/tabular-content/data-grid-in-memory-settings`,
`${root}#/tabular-content/data-grid-schemas-and-popovers`,
`${root}#/tabular-content/data-grid-focus`,
`${root}#/tabular-content/data-grid-styling-and-control`,
`${root}#/tabular-content/data-grid-control-columns`,
`${root}#/tabular-content/data-grid-footer-row`,
`${root}#/tabular-content/data-grid-virtualization`,
`${root}#/tabular-content/data-grid-row-heights-options`,
];

return [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,8 @@ Array [
tabindex="0"
>
<div
data-itemsrendered=""
data-test-subj="euiDataGridBody"
style="width:100%;height:100%;overflow:hidden"
>
<div
Expand Down Expand Up @@ -1433,6 +1435,8 @@ Array [
tabindex="0"
>
<div
data-itemsrendered=""
data-test-subj="euiDataGridBody"
style="width:100%;height:100%;overflow:hidden"
>
<div
Expand Down Expand Up @@ -2132,6 +2136,8 @@ Array [
tabindex="0"
>
<div
data-itemsrendered=""
data-test-subj="euiDataGridBody"
style="width:100%;height:100%;overflow:hidden"
>
<div
Expand Down Expand Up @@ -2527,6 +2533,8 @@ Array [
tabindex="0"
>
<div
data-itemsrendered=""
data-test-subj="euiDataGridBody"
style="width:100%;height:100%;overflow:hidden"
>
<div
Expand Down
1 change: 1 addition & 0 deletions src/components/datagrid/_data_grid_data_row.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.euiDataGridRow {
// needed for footer cells to correctly position
display: flex;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

exports[`EuiDataGridBody renders 1`] = `
<div
data-itemsrendered=""
data-test-subj="euiDataGridBody"
style="width:100%;height:100%;overflow:hidden"
>
<div
Expand Down
32 changes: 32 additions & 0 deletions src/components/datagrid/body/data_grid_body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ import {
import {
EuiDataGridBodyProps,
EuiDataGridInMemoryValues,
EuiDataGridRowManager,
EuiDataGridSchemaDetector,
} from '../data_grid_types';
import { makeRowManager } from './data_grid_row_manager';
import { keysOf } from '../../common';

export const VIRTUALIZED_CONTAINER_CLASS = 'euiDataGrid__virtualized';

Expand All @@ -72,6 +75,7 @@ export const Cell: FunctionComponent<GridChildComponentProps> = ({
rowHeightsOptions,
getRowHeight,
rowHeightUtils,
rowManager,
} = data;

const { headerRowHeight } = useContext(DataGridWrapperRowsContext);
Expand Down Expand Up @@ -139,6 +143,7 @@ export const Cell: FunctionComponent<GridChildComponentProps> = ({
getRowHeight={getRowHeight}
rowHeightsOptions={rowHeightsOptions}
rowHeightUtils={rowHeightUtils}
rowManager={rowManager}
style={{
...style,
top: `${parseFloat(style.top as string) + headerRowHeight}px`,
Expand Down Expand Up @@ -166,6 +171,7 @@ export const Cell: FunctionComponent<GridChildComponentProps> = ({
rowHeightsOptions={rowHeightsOptions}
getRowHeight={getRowHeight}
rowHeightUtils={rowHeightUtils}
rowManager={rowManager}
style={{
...style,
top: `${parseFloat(style.top as string) + headerRowHeight}px`,
Expand Down Expand Up @@ -203,6 +209,7 @@ export const Cell: FunctionComponent<GridChildComponentProps> = ({
rowHeightsOptions={rowHeightsOptions}
getRowHeight={getRowHeight}
rowHeightUtils={rowHeightUtils}
rowManager={rowManager}
style={{
...style,
top: `${parseFloat(style.top as string) + headerRowHeight}px`,
Expand Down Expand Up @@ -616,6 +623,14 @@ export const EuiDataGridBody: FunctionComponent<EuiDataGridBodyProps> = (
const wrapperRef = useRef<HTMLDivElement | null>(null);
const wrapperDimensions = useResizeObserver(wrapperRef.current);

const innerGridRef = useRef<HTMLDivElement | null>(null);

// useState instead of useMemo as React reserves the right to drop memoized
// values in the future, and that would be very bad here
const [rowManager] = useState<EuiDataGridRowManager>(() =>
cee-chen marked this conversation as resolved.
Show resolved Hide resolved
makeRowManager(innerGridRef)
);

// reset height constraint when rowCount changes
useEffect(() => {
setHeight(undefined);
Expand Down Expand Up @@ -673,13 +688,27 @@ export const EuiDataGridBody: FunctionComponent<EuiDataGridBodyProps> = (
finalWidth = window.innerWidth;
}

const [dataItemsRendered, setDataItemsRendered] = useState('');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not totally clear on what this dataItemsRendered or the data-itemsrendered attribute is being used for - is it just for Cypress testing? TBH, I'd almost rather do visual snapshot testing over this approach. 1) it feels like it'd be more intuitive to compare outputs & state, and 2) it wouldn't impact realtime performance

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was to provide a way for the test to determine when the rendering is stable. The hooks + virtualization means the shell of the grid + header row is rendered at the initial pass, but then a second (maybe even a third) pass is needed to draw the cells. I couldn't think of a non-hacky way to do this and everything in Cypress's docs + googling said there should be some signal in the DOM to wait for, so I added the data attribute with the virtualization state to hook into.

Totally open to other ideas!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh gotcha, sorry! I was totally off on what this was doing in that case, apologies (I thought it was a way of determining cell content 🙈)

I think my worry is potential impact for production grids with lots of data / if this bit of code is overengineered for its purpose. Alternatives:

  • Can we opt for a simple true/false bool after the first time onItemsRendered gets called instead?
  • Can we put a data-test-subj on our individual cells and check that the expected number of cells exists instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was anticipating this full information would be useful in future tests against virtualization. Scroll & verify the row&column indices, set the overscanCount prop and verify, etc.

Maybe, to handle the perf impacts as this does update the attribute quite aggressively (though I didn't notice any lag when testing), we enable this with a debug flag on the component? This could open/enable other console logs or other ways of exposing info around the inner workings.

A true/false flag may work, but some tests may need that flag to toggle back to false until reaching stability again.

Can we put a data-test-subj on our individual cells and check that the expected number of cells exists instead?

I can't find a (non-horrible hacky) way to have Cypress wait for a number of elements. We can select for [role=gridcell], but from what I can tell trying to wait for N of them is an anti-pattern. We could add a data-test-subj with ${row},${col} and select/wait for that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add a data-test-subj with ${row},${col} and select/wait for that.

Oo, I like this option a lot! A debug/cypress flag would be fine, but between the two I think the row,col data-test-subj is elegant and could be helpful in general / potentially even in prod debugging.

trying to wait for N of them is an anti-pattern

Ha I have yolo'd too many Cypress anti-patterns in my day attempting to wait for the flakily-un-waitable (recursive xhr polls waiting server side asynchronous cron jobs probably being my least favorite)... 🙈 I definitely appreciate the dialogue and compromise and think we have a few solid approaches here (for the future as well)!

const setItemsRendered = useCallback<
NonNullable<VariableSizeGridProps['onItemsRendered']>
>(
(itemsRendered) => {
const keys = keysOf(itemsRendered);
const pairs = keys.sort().map((key) => `${key}=${itemsRendered[key]}`);
setDataItemsRendered(pairs.join());
},
[setDataItemsRendered]
);

return (
<EuiMutationObserver
observerOptions={{ subtree: true, childList: true }}
onMutation={preventTabbing}
>
{(mutationRef) => (
<div
data-test-subj="euiDataGridBody"
data-itemsrendered={dataItemsRendered}
style={{ width: '100%', height: '100%', overflow: 'hidden' }}
ref={(el) => {
wrapperRef.current = el;
Expand All @@ -694,6 +723,8 @@ export const EuiDataGridBody: FunctionComponent<EuiDataGridBodyProps> = (
{...(virtualizationOptions ? virtualizationOptions : {})}
ref={setGridRef}
innerElementType={InnerElement}
onItemsRendered={setItemsRendered}
innerRef={innerGridRef}
className={VIRTUALIZED_CONTAINER_CLASS}
columnCount={
leadingControlColumns.length +
Expand Down Expand Up @@ -724,6 +755,7 @@ export const EuiDataGridBody: FunctionComponent<EuiDataGridBodyProps> = (
interactiveCellId,
rowHeightsOptions,
rowHeightUtils,
rowManager,
}}
rowCount={
IS_JEST_ENVIRONMENT || headerRowHeight > 0
Expand Down
13 changes: 11 additions & 2 deletions src/components/datagrid/body/data_grid_cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import React, {
memo,
MutableRefObject,
} from 'react';
import { createPortal } from 'react-dom';
import tabbable from 'tabbable';
import { keys } from '../../../services';
import { EuiScreenReaderOnly } from '../../accessibility';
Expand All @@ -33,6 +34,7 @@ import {
} from '../data_grid_types';
import { EuiDataGridCellButtons } from './data_grid_cell_buttons';
import { EuiDataGridCellPopover } from './data_grid_cell_popover';
import { IS_JEST_ENVIRONMENT } from '../../../test';

const EuiDataGridCellContent: FunctionComponent<
EuiDataGridCellValueProps & {
Expand Down Expand Up @@ -145,6 +147,7 @@ export class EuiDataGridCell extends Component<
};

setCellRef = (ref: HTMLDivElement | null) => {
// save the reference
cee-chen marked this conversation as resolved.
Show resolved Hide resolved
this.cellRef.current = ref;

// watch the first cell for size changes and use that to re-compute row heights
Expand Down Expand Up @@ -365,9 +368,11 @@ export class EuiDataGridCell extends Component<
className,
column,
style,
rowHeightsOptions,
rowManager,
...rest
} = this.props;
const { rowIndex, rowHeightsOptions } = rest;
const { rowIndex } = rest;

const showCellButtons =
this.state.isFocused ||
Expand Down Expand Up @@ -556,7 +561,7 @@ export class EuiDataGridCell extends Component<
}
}

return (
const content = (
<div
role="gridcell"
tabIndex={
Expand All @@ -578,5 +583,9 @@ export class EuiDataGridCell extends Component<
{innerContent}
</div>
);

return rowManager && !IS_JEST_ENVIRONMENT
? createPortal(content, rowManager.getRow(rowIndex))
: content;
}
}
6 changes: 3 additions & 3 deletions src/components/datagrid/body/data_grid_footer_row.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('EuiDataGridFooterRow', () => {
expect(component).toMatchInlineSnapshot(`
<div
className="euiDataGridRow euiDataGridFooter"
data-test-subj="dataGridRow"
data-test-subj="dataGridRow dataGridFooterRow"
role="row"
>
<EuiDataGridCell
Expand Down Expand Up @@ -83,7 +83,7 @@ describe('EuiDataGridFooterRow', () => {
expect(component).toMatchInlineSnapshot(`
<div
className="euiDataGridRow euiDataGridFooter"
data-test-subj="dataGridRow"
data-test-subj="dataGridRow dataGridFooterRow"
role="row"
>
<EuiDataGridCell
Expand Down Expand Up @@ -127,7 +127,7 @@ describe('EuiDataGridFooterRow', () => {
expect(component).toMatchInlineSnapshot(`
<div
className="euiDataGridRow euiDataGridFooter"
data-test-subj="dataGridRow"
data-test-subj="dataGridRow dataGridFooterRow"
role="row"
>
<EuiDataGridCell
Expand Down
6 changes: 5 additions & 1 deletion src/components/datagrid/body/data_grid_footer_row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ const EuiDataGridFooterRow = memo(
'euiDataGridFooter',
className
);
const dataTestSubj = classnames('dataGridRow', _dataTestSubj);
const dataTestSubj = classnames(
'dataGridRow',
'dataGridFooterRow',
_dataTestSubj
);

return (
<div
Expand Down
47 changes: 47 additions & 0 deletions src/components/datagrid/body/data_grid_row_manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { RefObject } from 'react';
import { EuiDataGridRowManager } from '../data_grid_types';

export const makeRowManager = (
containerRef: RefObject<HTMLDivElement>
): EuiDataGridRowManager => {
const rowIdToElements = new Map<number, HTMLDivElement>();

return {
reset() {
rowIdToElements.clear();
},
cee-chen marked this conversation as resolved.
Show resolved Hide resolved
getRow(rowIndex) {
let rowElement = rowIdToElements.get(rowIndex);

if (rowElement == null) {
rowElement = document.createElement('div');
rowElement.setAttribute('role', 'row');
chandlerprall marked this conversation as resolved.
Show resolved Hide resolved
rowElement.classList.add('euiDataGridRow');
cee-chen marked this conversation as resolved.
Show resolved Hide resolved
rowIdToElements.set(rowIndex, rowElement);

// add the element to the wrapping container
containerRef.current?.appendChild(rowElement);

// watch the row's children, if they all disappear then remove this row
const observer = new MutationObserver((records) => {
if ((records[0].target as HTMLElement).childElementCount === 0) {
observer.disconnect();
rowElement?.remove();
rowIdToElements.delete(rowIndex);
}
});
observer.observe(rowElement, { childList: true });
}

return rowElement;
},
};
};
Loading