From 9d38ea3d228a64369f3fad7f50ab396e33e5c700 Mon Sep 17 00:00:00 2001 From: YanJin Date: Thu, 19 Nov 2020 12:26:37 +0100 Subject: [PATCH 1/7] ui/table: Use the react-window to virtualize the lines Autosizer automatically adjusts the width and height of a single child. Refs: #2931 ui/table: Add flex for table row to fully use the space Refs: #2931 --- ui/src/components/VolumeListTable.js | 201 ++++++++++++++++----------- 1 file changed, 121 insertions(+), 80 deletions(-) diff --git a/ui/src/components/VolumeListTable.js b/ui/src/components/VolumeListTable.js index a029bf1f7f..da0b3c0488 100644 --- a/ui/src/components/VolumeListTable.js +++ b/ui/src/components/VolumeListTable.js @@ -9,7 +9,10 @@ import { useGlobalFilter, useAsyncDebounce, useSortBy, + useBlockLayout, } from 'react-table'; +import { FixedSizeList as List } from 'react-window'; +import AutoSizer from 'react-virtualized-auto-sizer'; import { useQuery } from '../services/utils'; import { fontSize, @@ -40,24 +43,24 @@ const VolumeListContainer = styled.div` color: ${(props) => props.theme.brand.textPrimary}; font-family: 'Lato'; font-size: ${fontSize.base}; - border-color: ${(props) => props.theme.brand.borderLight}; background-color: ${(props) => props.theme.brand.primary}; - .sc-progressbarcontainer { - width: 100%; - } - .sc-progressbarcontainer > div { - background-color: ${(props) => props.theme.brand.secondaryDark1}; - } - .ReactTable .rt-thead { - overflow-y: scroll; - } + table { - border-spacing: 0; + display: block; + padding-bottom: 13px; + + thead { + width: 100%; + display: inline-block; + } .sc-select-container { width: 120px; height: 10px; } tr { + display: table; + table-layout: fixed; + width: 100%; :last-child { td { border-bottom: 0; @@ -70,34 +73,29 @@ const VolumeListContainer = styled.div` font-weight: bold; height: 35px; text-align: left; - padding: ${padding.smaller}; + padding-top: 3px; cursor: pointer; - vertical-align: baseline; } td { margin: 0; - padding: 0.5rem; text-align: left; - padding: 5px; - border: none; + border-bottom: 1px solid ${(props) => props.theme.brand.border}; :last-child { border-right: 0; } } } -`; -const HeadRow = styled.tr` - width: 100%; - /* To display scroll bar on the table */ - display: table; - table-layout: fixed; + .sc-progressbarcontainer { + width: 100%; + } + .sc-progressbarcontainer > div { + background-color: ${(props) => props.theme.brand.secondaryDark1}; + } `; -const TableRow = styled(HeadRow)` - height: 48px; - border-bottom: 1px solid ${(props) => props.theme.brand.border}; +const TableRow = styled.tr` &:hover, &:focus { background-color: ${(props) => props.theme.brand.backgroundBluer}; @@ -117,16 +115,8 @@ const TableRow = styled(HeadRow)` // * table body const Body = styled.tbody` - /* To display scroll bar on the table */ display: block; height: calc(100vh - 250px); - overflow: auto; - overflow-y: scroll; -`; - -const Cell = styled.td` - overflow-wrap: break-word; - border-top: 1px solid #424242; `; const CreateVolumeButton = styled(Button)` @@ -135,9 +125,9 @@ const CreateVolumeButton = styled(Button)` const ActionContainer = styled.span` display: flex; - justify-content: space-between; - padding: ${padding.base}; flex-direction: row-reverse; + justify-content: space-between; + padding: ${padding.base} ${padding.base} ${padding.base} 17px; `; const TooltipContent = styled.div` @@ -146,6 +136,10 @@ const TooltipContent = styled.div` min-width: 60px; `; +const UnknownIcon = styled.i` + color: ${(props) => props.theme.brand.textSecondary}; +`; + function GlobalFilter({ preGlobalFilteredRows, globalFilter, @@ -275,6 +269,7 @@ function Table({ useFilters, useGlobalFilter, useSortBy, + useBlockLayout, ); // Synchronizes the params query with the Table sort state @@ -284,12 +279,65 @@ function Table({ ?.isSortedDesc; useTableSortURLSync(sorted, desc, data); + const RenderRow = React.useCallback( + ({ index, style }) => { + const row = rows[index]; + prepareRow(row); + + return ( + rowClicked(row), + // Note: + // We need to pass the style property to the row component. + // Otherwise when we scroll down, the next rows are flashing because they are re-rendered in loop. + style: { ...style, marginLeft: '5px' }, + })} + volumeName={volumeName} + row={row} + > + {row.cells.map((cell) => { + let cellProps = cell.getCellProps({ + style: { + ...cell.column.cellStyle, + // Center text vertically in cells. + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + }, + }); + + if (cell.column.Header === 'Name') { + return ( + + {cell.render('Cell')} + + ); + } else if ( + cell.column.Header !== 'Name' && + cell.value === undefined + ) { + return ( + +
{intl.translate('unknown')}
+ + ); + } else { + return {cell.render('Cell')}; + } + })} +
+ ); + }, + [prepareRow, rowClicked, rows, volumeName], + ); + return ( <> {/* The first row should be the search bar */} - + - + {headerGroups.map((headerGroup) => { return ( - + {headerGroup.headers.map((column) => { const headerStyleProps = column.getHeaderProps( Object.assign(column.getSortByToggleProps(), { @@ -352,13 +406,13 @@ function Table({ ); })} - + ); })} {data.length === 0 ? ( - No Volume - + ) : null} - {rows.map((row, i) => { - prepareRow(row); - return ( - rowClicked(row) })} - volumeName={volumeName} - row={row} + + {({ height, width }) => ( + - {row.cells.map((cell) => { - let cellProps = cell.getCellProps({ - style: { - ...cell.column.cellStyle, - }, - }); - if (cell.column.Header === 'Name') { - return ( - - {cell.render('Cell')} - - ); - } else if ( - cell.column.Header !== 'Name' && - cell.value === undefined - ) { - return ( - -
{intl.translate('unknown')}
-
- ); - } else { - return {cell.render('Cell')}; - } - })} -
- ); - })} + {RenderRow} + + )} +
@@ -436,7 +467,10 @@ const VolumeListTable = (props) => { { Header: 'Health', accessor: 'health', - cellStyle: { textAlign: 'center', width: '90px' }, + cellStyle: { + textAlign: 'center', + width: '80px', + }, Cell: (cellProps) => { return ( @@ -447,6 +481,7 @@ const VolumeListTable = (props) => { { Header: 'Name', accessor: 'name', + cellStyle: { flex: 1 }, }, { Header: 'Usage', @@ -481,7 +516,7 @@ const VolumeListTable = (props) => { accessor: 'status', cellStyle: { textAlign: 'center', - width: isNodeColumn ? '70px' : '110px', + width: isNodeColumn ? '50px' : '110px', }, Cell: (cellProps) => { const volume = volumeListData?.find( @@ -528,7 +563,7 @@ const VolumeListTable = (props) => { accessor: 'latency', cellStyle: { textAlign: 'center', - width: isNodeColumn ? '70px' : '110px', + width: isNodeColumn ? '75px' : '110px', }, Cell: (cellProps) => { return cellProps.value !== undefined ? cellProps.value + ' µs' : null; @@ -537,7 +572,13 @@ const VolumeListTable = (props) => { ], [volumeListData, theme, isNodeColumn], ); - const nodeCol = { Header: 'Node', accessor: 'node' }; + const nodeCol = { + Header: 'Node', + accessor: 'node', + cellStyle: { + width: '100px', + }, + }; if (isNodeColumn) { columns.splice(1, 0, nodeCol); } From 718daca024b3960e4d1a1bdc85a9e9ae8df5fd24 Mon Sep 17 00:00:00 2001 From: YanJin Date: Fri, 20 Nov 2020 10:38:30 +0100 Subject: [PATCH 2/7] ui/table: Replace the text unknown by hyphen icon Too many "Unknown" in the table seems extremly noisy and since the "Unknown" information is rather less important, we should replace it by an icon with tooltip. Refs: #2931 --- ui/src/components/VolumeListTable.js | 30 +++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/ui/src/components/VolumeListTable.js b/ui/src/components/VolumeListTable.js index da0b3c0488..599dc262fa 100644 --- a/ui/src/components/VolumeListTable.js +++ b/ui/src/components/VolumeListTable.js @@ -319,7 +319,19 @@ function Table({ ) { return ( -
{intl.translate('unknown')}
+ + {intl.translate('unknown')} + + } + > + + ); } else { @@ -329,7 +341,7 @@ function Table({ ); }, - [prepareRow, rowClicked, rows, volumeName], + [prepareRow, rowClicked, rows, volumeName, theme], ); return ( @@ -553,7 +565,19 @@ const VolumeListTable = (props) => { ); default: - return
{intl.translate('unknown')}
; + return ( + {intl.translate('unknown')} + } + > + + + ); } }, sortType: 'status', From 2ee989f9fc7d79f938967baa2075546630225e35 Mon Sep 17 00:00:00 2001 From: YanJin Date: Fri, 20 Nov 2020 10:46:17 +0100 Subject: [PATCH 3/7] ui/package: Update the UI packages - Upgrade the react-table version to get support for react (v17.0.1) - Introduce react-window and react-virtualized-auto-sizer for the virtualize list Refs: #2931 --- ui/package-lock.json | 20 +++++++++++++++++--- ui/package.json | 4 +++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index 0cdeeb1e74..840b64a441 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -15008,9 +15008,9 @@ } }, "react-table": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.5.1.tgz", - "integrity": "sha512-rprrUElCqvj79lyY2XbUoYLzwA5Mm4CGS8ElQ8OyzocvmkvCcmunvvfbpIg9Jm9HnMBjVZcVyPFPZ1BFelIBKw==" + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.6.2.tgz", + "integrity": "sha512-urwNZTieb+xg/+BITUIrqdH5jZfJlw7rKVAAq25iXpBPwbQojLCEKJuGycLbVwn8fzU+Ovly3y8HHNaLNrPCvQ==" }, "react-test-renderer": { "version": "17.0.1", @@ -15074,6 +15074,20 @@ "react-lifecycles-compat": "^3.0.4" } }, + "react-virtualized-auto-sizer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz", + "integrity": "sha512-MYXhTY1BZpdJFjUovvYHVBmkq79szK/k7V3MO+36gJkWGkrXKtyr4vCPtpphaTLRAdDNoYEYFZWE8LjN+PIHNg==" + }, + "react-window": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.6.tgz", + "integrity": "sha512-8VwEEYyjz6DCnGBsd+MgkD0KJ2/OXFULyDtorIiTz+QzwoP94tBoA7CnbtyXMm+cCeAUER5KJcPtWl9cpKbOBg==", + "requires": { + "@babel/runtime": "^7.0.0", + "memoize-one": ">=3.1.1 <6" + } + }, "read-only-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", diff --git a/ui/package.json b/ui/package.json index 4edb2b2d95..6bee5d6a7e 100644 --- a/ui/package.json +++ b/ui/package.json @@ -22,8 +22,10 @@ "react-router-dom": "^5.1.0", "react-scripts": "^3.4.4", "react-select": "^3.0.8", - "react-table": "^7.2.2", + "react-table": "^7.6.2", "react-virtualized": "^9.21.0", + "react-virtualized-auto-sizer": "^1.0.2", + "react-window": "^1.8.6", "redux": "^4.0.1", "redux-oidc": "^3.1.0", "redux-saga": "^1.0.2", From e6ae2252ed4f7764197b31f53729f2e6cc62ed66 Mon Sep 17 00:00:00 2001 From: YanJin Date: Mon, 23 Nov 2020 16:07:33 +0100 Subject: [PATCH 4/7] ui/volumes: Fix the bugs cause too many API calls For non-specified instance URL, such as `/volumes`, we should automatically choose the first one in the list. However, since we are not aware currectVolumeName in VolumePage component, we fall into update the URL which triger the VolumePage re-renders. Cause the infinite API calls. So I move this logic to VolumePageContent which solve the issue. Refs: #2931 --- ui/src/containers/VolumePage.js | 22 ++-------------------- ui/src/containers/VolumePageContent.js | 20 ++++++++++++++++++-- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/ui/src/containers/VolumePage.js b/ui/src/containers/VolumePage.js index 6811731b22..45cbc13111 100644 --- a/ui/src/containers/VolumePage.js +++ b/ui/src/containers/VolumePage.js @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { useRouteMatch, useHistory } from 'react-router'; +import { useRouteMatch } from 'react-router'; import { useSelector, useDispatch } from 'react-redux'; import VolumeContent from './VolumePageContent'; import { fetchPodsAction } from '../ducks/app/pods'; @@ -32,7 +32,6 @@ import { getVolumeListData } from '../services/NodeVolumesUtils'; import { Breadcrumb } from '@scality/core-ui'; import { PageContainer } from '../components/CommonLayoutStyle'; import { intl } from '../translations/IntlGlobalProvider'; -import { useQuery } from '../services/utils'; // component fetchs all the data used by volume page from redux store. // the data for : get the default metrics time span `last 24 hours`, and the component itself can change the time span base on the dropdown selection. @@ -41,8 +40,6 @@ const VolumePage = (props) => { const dispatch = useDispatch(); const match = useRouteMatch(); const currentVolumeName = match.params.name; - const query = useQuery(); - const history = useHistory(); useEffect(() => { if (currentVolumeName) @@ -80,7 +77,6 @@ const VolumePage = (props) => { (state) => state.app.volumes.currentVolumeObject, ); const pVList = useSelector((state) => state.app.volumes.pVList); - /* ** The PVCs list is used to check when the alerts will be mapped to the corresponding volumes ** in order to auto select the volume when all the data are there. @@ -96,21 +92,6 @@ const VolumePage = (props) => { getVolumeListData(state, props), ); - // If data has been retrieved and no volume is selected yet we select the first one - useEffect(() => { - if ( - volumeListData[0]?.name && - alerts.list?.length && - pVCList.length && - !currentVolumeName - ) { - history.replace({ - pathname: `/volumes/${volumeListData[0]?.name}/overview`, - search: query.toString(), - }); - } - }, [volumeListData, currentVolumeName, query, history, alerts.list, pVCList]); - return ( @@ -132,6 +113,7 @@ const VolumePage = (props) => { nodes={nodes} node={node} pVList={pVList} + pVCList={pVCList} pods={pods} alerts={alerts} volumeStats={volumeStats} diff --git a/ui/src/containers/VolumePageContent.js b/ui/src/containers/VolumePageContent.js index e84b503226..e2ef418dbf 100644 --- a/ui/src/containers/VolumePageContent.js +++ b/ui/src/containers/VolumePageContent.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { useSelector } from 'react-redux'; import styled from 'styled-components'; import { useHistory, useLocation, useRouteMatch } from 'react-router'; @@ -70,6 +70,7 @@ const VolumePageContent = (props) => { node, volumeListData, pVList, + pVCList, pods, alerts, volumeStats, @@ -82,8 +83,23 @@ const VolumePageContent = (props) => { const query = new URLSearchParams(location.search); const theme = useSelector((state) => state.config.theme); - const currentVolumeName = match.params.name; + + // If data has been retrieved and no volume is selected yet we select the first one + useEffect(() => { + if ( + volumeListData[0]?.name && + alerts.list?.length && + pVCList.length && + !currentVolumeName + ) { + history.replace({ + pathname: `/volumes/${volumeListData[0]?.name}/overview`, + search: query.toString(), + }); + } + }, [volumeListData, currentVolumeName, query, history, alerts.list, pVCList]); + const volume = volumes?.find( (volume) => volume.metadata.name === currentVolumeName, ); From 450d46024657a1cb14397372165f60c4272d05ee Mon Sep 17 00:00:00 2001 From: YanJin Date: Mon, 23 Nov 2020 16:34:29 +0100 Subject: [PATCH 5/7] ui/cypress: Fix the failed test caused by the table virtualization Refs: #2931 --- ui/cypress/integration/node/nodetabs.spec.js | 3 ++- .../integration/volume/volumelist.spec.js | 20 ++++++++++++------- ui/src/components/VolumeListTable.js | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/ui/cypress/integration/node/nodetabs.spec.js b/ui/cypress/integration/node/nodetabs.spec.js index 5082cb199f..36e250475e 100644 --- a/ui/cypress/integration/node/nodetabs.spec.js +++ b/ui/cypress/integration/node/nodetabs.spec.js @@ -137,9 +137,10 @@ describe('Node page volumes tabs', () => { it('brings me to the loki-vol volume page', () => { cy.stubHistory(); + cy.get('[data-cy="volume_table_name_cell"]') .contains('td', 'loki-vol') - .click(); + .click({ force: true }); cy.get('@historyPush').should( 'be.calledWith', '/volumes/loki-vol/overview?node=master-0', diff --git a/ui/cypress/integration/volume/volumelist.spec.js b/ui/cypress/integration/volume/volumelist.spec.js index 89ef04aa42..e50f3f7206 100644 --- a/ui/cypress/integration/volume/volumelist.spec.js +++ b/ui/cypress/integration/volume/volumelist.spec.js @@ -20,15 +20,21 @@ describe('Volume list', () => { ); }); - it('brings me to the overview tab of master-0-alertmanager Volume', () => { + it('brings me to the overview tab of worker-0-burry-1 Volume', () => { + // After implementing the virtualized table, not all the volumes are visible at the first render. + // So we should test the first several volumes which are visiable. + cy.visit('/volumes'); cy.stubHistory(); + // The application re-renders, it's possible the element we're interacting with has become "dead" + // cy... failed because the element has been detached from the DOM cy.get('[data-cy="volume_table_name_cell"]') - .contains('master-1-prometheus') - .click(); + .contains('worker-0-burry-1') + .click({ force: true }); + cy.get('@historyPush').should('be.calledWithExactly', { - pathname: '/volumes/master-1-prometheus/overview', + pathname: '/volumes/worker-0-burry-1/overview', search: '', }); }); @@ -38,10 +44,10 @@ describe('Volume list', () => { cy.stubHistory(); cy.get('[data-cy="volume_table_name_cell"]') - .contains('prom-m0-reldev') - .click(); + .contains('master-0-alertmanager') + .click({ force: true }); cy.get('@historyPush').should('be.calledOnce').and('be.calledWithExactly', { - pathname: '/volumes/prom-m0-reldev/metrics', + pathname: '/volumes/master-0-alertmanager/metrics', search: 'from=now-7d', }); }); diff --git a/ui/src/components/VolumeListTable.js b/ui/src/components/VolumeListTable.js index 599dc262fa..5ae70409a3 100644 --- a/ui/src/components/VolumeListTable.js +++ b/ui/src/components/VolumeListTable.js @@ -341,7 +341,7 @@ function Table({ ); }, - [prepareRow, rowClicked, rows, volumeName, theme], + [prepareRow, rowClicked, rows, volumeName, theme, data], ); return ( From 9163f9799b4387abcc2e0f59f72eaeb9ed7e0587 Mon Sep 17 00:00:00 2001 From: YanJin Date: Tue, 24 Nov 2020 18:41:00 +0100 Subject: [PATCH 6/7] ui/tables: Replace all the html tags in table with div Since is a
so it breaks the table layout, we need to use
for all the HTML tags in table (thead, tbody, tr, td...) and retrieve the defaullt styles by className. Refs: #2931 --- ui/cypress/integration/node/nodetabs.spec.js | 2 +- ui/src/components/CommonLayoutStyle.js | 3 +- ui/src/components/NodeListTable.js | 25 ++++++-- ui/src/components/VolumeListTable.js | 63 +++++++++++--------- 4 files changed, 57 insertions(+), 36 deletions(-) diff --git a/ui/cypress/integration/node/nodetabs.spec.js b/ui/cypress/integration/node/nodetabs.spec.js index 36e250475e..b9d5823ecb 100644 --- a/ui/cypress/integration/node/nodetabs.spec.js +++ b/ui/cypress/integration/node/nodetabs.spec.js @@ -139,7 +139,7 @@ describe('Node page volumes tabs', () => { cy.stubHistory(); cy.get('[data-cy="volume_table_name_cell"]') - .contains('td', 'loki-vol') + .contains('div', 'loki-vol') .click({ force: true }); cy.get('@historyPush').should( 'be.calledWith', diff --git a/ui/src/components/CommonLayoutStyle.js b/ui/src/components/CommonLayoutStyle.js index 07bd4a66f5..aa22b6dbec 100644 --- a/ui/src/components/CommonLayoutStyle.js +++ b/ui/src/components/CommonLayoutStyle.js @@ -89,7 +89,8 @@ export const SortIncentive = styled.span` display: none; `; -export const TableHeader = styled.th` +export const TableHeader = styled.span` + padding: ${padding.base}; &:hover { ${SortIncentive} { display: block; diff --git a/ui/src/components/NodeListTable.js b/ui/src/components/NodeListTable.js index 84182bb96c..b69fa59aa0 100644 --- a/ui/src/components/NodeListTable.js +++ b/ui/src/components/NodeListTable.js @@ -15,11 +15,6 @@ import { fontSize, padding } from '@scality/core-ui/dist/style/theme'; import CircleStatus from './CircleStatus'; import { Button } from '@scality/core-ui'; import { intl } from '../translations/IntlGlobalProvider'; -import { - SortCaretWrapper, - SortIncentive, - TableHeader, -} from './CommonLayoutStyle'; import { compareHealth, useTableSortURLSync } from '../services/utils'; import { API_STATUS_READY, @@ -140,6 +135,24 @@ const StatusText = styled.div` }}; `; +export const SortCaretWrapper = styled.span` + padding-left: ${padding.smaller}; + position: absolute; +`; + +export const SortIncentive = styled.span` + position: absolute; + display: none; +`; + +export const TableHeader = styled.th` + &:hover { + ${SortIncentive} { + display: block; + } + } +`; + function GlobalFilter({ preGlobalFilteredRows, globalFilter, @@ -315,7 +328,7 @@ function Table({ columns, data, rowClicked, theme, selectedNodeName }) { }), ); return ( - + {column.render('Header')} {column.isSorted ? ( diff --git a/ui/src/components/VolumeListTable.js b/ui/src/components/VolumeListTable.js index 5ae70409a3..6bfa60b132 100644 --- a/ui/src/components/VolumeListTable.js +++ b/ui/src/components/VolumeListTable.js @@ -95,7 +95,7 @@ const VolumeListContainer = styled.div` } `; -const TableRow = styled.tr` +const TableRow = styled.div` &:hover, &:focus { background-color: ${(props) => props.theme.brand.backgroundBluer}; @@ -114,7 +114,7 @@ const TableRow = styled.tr` `; // * table body -const Body = styled.tbody` +const Body = styled.div` display: block; height: calc(100vh - 250px); `; @@ -127,7 +127,7 @@ const ActionContainer = styled.span` display: flex; flex-direction: row-reverse; justify-content: space-between; - padding: ${padding.base} ${padding.base} ${padding.base} 17px; + padding: ${padding.large} ${padding.base} ${padding.base} 20px; `; const TooltipContent = styled.div` @@ -300,7 +300,7 @@ function Table({ let cellProps = cell.getCellProps({ style: { ...cell.column.cellStyle, - // Center text vertically in cells. + // Vertically center the text in cells. display: 'flex', flexDirection: 'column', justifyContent: 'center', @@ -309,16 +309,20 @@ function Table({ if (cell.column.Header === 'Name') { return ( - +
{cell.render('Cell')} - +
); } else if ( cell.column.Header !== 'Name' && cell.value === undefined ) { return ( - +
- +
); } else { - return {cell.render('Cell')}; + return ( +
+ {cell.render('Cell')} +
+ ); } })} @@ -346,15 +354,11 @@ function Table({ return ( <> - - +
+
{/* The first row should be the search bar */} -
- - + + {headerGroups.map((headerGroup) => { return ( - ); })} - + ); })} - + {data.length === 0 ? ( - - - + + ) : null} - + {/* is a
so it breaks the table layout, + we need to use
for all the parts of table(thead, tbody, tr, td...) and retrieve the defaullt styles by className. */} {({ height, width }) => ( -
+
+
) : null} -
No Volume -
+
); } From 8f0ca1505c73a64c3f02d6203b9fca10439057c5 Mon Sep 17 00:00:00 2001 From: YanJin Date: Wed, 25 Nov 2020 12:34:28 +0100 Subject: [PATCH 7/7] ui/volumes: Increase the height of Unknown icon So that the users don't need to hover on the hypen precisely Refs: #2931 --- ui/src/components/VolumeListTable.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/src/components/VolumeListTable.js b/ui/src/components/VolumeListTable.js index 6bfa60b132..739449c6a2 100644 --- a/ui/src/components/VolumeListTable.js +++ b/ui/src/components/VolumeListTable.js @@ -138,6 +138,8 @@ const TooltipContent = styled.div` const UnknownIcon = styled.i` color: ${(props) => props.theme.brand.textSecondary}; + // Increase the height so that the users don't need to hover precisely on the hyphen. + height: 30px; `; function GlobalFilter({