From 425cf93c08d8190412922bf10c8bcd0b3f14c561 Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Wed, 6 May 2020 12:58:34 +0200 Subject: [PATCH] Replacing SortableTable and a few MUI tables with new Table component (#722) * Befind work of replacing tables with new Table component * Removed test plugins and fixed test * Fixed lint issue * Removed toolbar from lighthouse audit page * Moved column out of auditstables render function, no no interation with useInterval --- .../ExampleComponent/ExampleComponent.tsx.hbs | 4 +- .../ExampleFetchComponent.tsx.hbs | 69 ++-- .../SortableTable/SortableTable.stories.tsx | 61 ---- .../SortableTable/SortableTable.tsx | 335 ------------------ .../src/components/SortableTable/index.tsx | 17 - .../src/components/Status/Status.stories.tsx | 18 +- .../TrendLine/TrendLine.stories.tsx | 22 +- packages/core/src/index.ts | 3 +- .../src/components/Audit/AuditRow.tsx | 83 ----- .../lighthouse/src/components/Audit/index.tsx | 54 --- .../AuditList/AuditListTable.test.tsx | 19 +- .../components/AuditList/AuditListTable.tsx | 152 ++++---- 12 files changed, 161 insertions(+), 676 deletions(-) delete mode 100644 packages/core/src/components/SortableTable/SortableTable.stories.tsx delete mode 100644 packages/core/src/components/SortableTable/SortableTable.tsx delete mode 100644 packages/core/src/components/SortableTable/index.tsx delete mode 100644 plugins/lighthouse/src/components/Audit/AuditRow.tsx delete mode 100644 plugins/lighthouse/src/components/Audit/index.tsx diff --git a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs index f0f148db48..34e37f3e7b 100644 --- a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs +++ b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.tsx.hbs @@ -47,9 +47,7 @@ const ExampleComponent: FC<{}> = () => ( - - - + diff --git a/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx.hbs b/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx.hbs index a198bfcc3f..2dd54f726e 100644 --- a/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx.hbs +++ b/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx.hbs @@ -16,20 +16,11 @@ import React, { FC } from 'react'; import { makeStyles } from '@material-ui/core/styles'; -import Table from '@material-ui/core/Table'; -import TableBody from '@material-ui/core/TableBody'; -import TableCell from '@material-ui/core/TableCell'; -import TableContainer from '@material-ui/core/TableContainer'; -import TableHead from '@material-ui/core/TableHead'; -import TableRow from '@material-ui/core/TableRow'; +import { Table, TableColumn, Progress } from '@backstage/core'; import Alert from '@material-ui/lab/Alert'; import { useAsync } from 'react-use'; -import { Progress } from '@backstage/core'; const useStyles = makeStyles({ - table: { - minWidth: 650, - }, avatar: { height: 32, width: 32, @@ -66,37 +57,35 @@ type DenseTableProps = { export const DenseTable: FC = ({ users }) => { const classes = useStyles(); + const columns: TableColumn[] = [ + { title: 'Avatar', field: 'avatar' }, + { title: 'Name', field: 'name' }, + { title: 'Email', field: 'email' }, + { title: 'Nationality', field: 'nationality' }, + ]; + + const data = users.map((user) => { + return { + avatar: ( + {user.name.first} + ), + name: `${user.name.first} ${user.name.last}`, + email: user.email, + nationality: user.nat, + }; + }); + return ( - - - - - Avatar - Name - Email - Nationality - - - - {users.map(user => ( - - - {user.name.first} - - - {user.name.first} {user.name.last} - - {user.email} - {user.nat} - - ))} - -
-
+ ); }; diff --git a/packages/core/src/components/SortableTable/SortableTable.stories.tsx b/packages/core/src/components/SortableTable/SortableTable.stories.tsx deleted file mode 100644 index 2a903e1df7..0000000000 --- a/packages/core/src/components/SortableTable/SortableTable.stories.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { StatusError, StatusOK, StatusWarning } from 'components/Status'; -import SortableTable from './SortableTable'; - -export default { - title: 'Sortable Table', - component: SortableTable, -}; -const containerStyle = { width: 600, padding: 20 }; - -const data = [ - { id: 'buffalos', amount: 1, status: , statusValue: 2 }, - { id: 'milk', amount: 3, status: , statusValue: 1 }, - { id: 'cheese', amount: 8, status: , statusValue: 1 }, - { id: 'bread', amount: 2, status: , statusValue: 0 }, -]; -const columns = [ - { id: 'id', label: 'ID' }, - { id: 'amount', disablePadding: false, numeric: true, label: 'AMOUNT' }, - { - id: 'status', - label: 'STATUS', - sortValue: (row: typeof data[0]) => row.statusValue, - }, -]; -const footerData = [ - { id: 'total', amount: 4, statusValue: 2, status: }, -]; - -export const Default = () => ( -
- -
-); - -export const WithFooter = () => ( -
- -
-); diff --git a/packages/core/src/components/SortableTable/SortableTable.tsx b/packages/core/src/components/SortableTable/SortableTable.tsx deleted file mode 100644 index 4db3aee9ff..0000000000 --- a/packages/core/src/components/SortableTable/SortableTable.tsx +++ /dev/null @@ -1,335 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { FC, CSSProperties, memo, useState, useEffect } from 'react'; -import { - Table, - TableBody, - TableCell, - TableFooter, - TableHead, - TableRow, - TableSortLabel, - Tooltip, -} from '@material-ui/core'; - -type Column = { - id: string; - label: string; - numeric?: boolean; - disablePadding: boolean; - style: CSSProperties; - // FIXME (@Koroeskohr) - sortValue: (obj: object) => any; -}; - -// XXX (@Koroeskohr): no idea what I did but this typechecks. Need answer for CellContentProps -type Row = { [name in string]: any }; - -type SortHandler = ( - event: React.MouseEvent, - property: string, -) => void; - -type Order = 'asc' | 'desc'; - -type EnhancedTableHeadProps = { - columns: Column[]; - onRequestSort: SortHandler; - order: Order; - orderBy: string; -}; - -/** - * Table header which supports sorting ascending and desc - */ -const EnhancedTableHead: FC = ({ - columns, - onRequestSort, - order, - orderBy, -}) => { - const createSortHandler = (property: string) => ( - event: React.MouseEvent, - ) => { - onRequestSort(event, property); - }; - - return ( - - - {columns.map(column => ( - - - - {column.label} - - - - ))} - - - ); -}; - -type CellContentProps = { - // XXX (@Koroeskohr): what am I supposed to use here - data: any | any[]; -}; - -/** - * CellContent can be an array or a string - */ -const CellContent: FC = ({ data }) => { - if (Array.isArray(data)) { - return data.map((item, index) => ( - - {item} -
-
- )); - } - return data; -}; - -type DataTableCellProps = { - column: Column; - row: Row; -}; - -const DataTableCell: FC = ({ column, row }) => { - return ( - - - - ); -}; - -const noop = () => {}; -type DataTableRowProps = { - row: Row; - columns: Column[]; - handleRowClick?: (event: React.MouseEvent, rowId: string) => void; - style?: React.CSSProperties; -}; -const _DataTableRow: FC = ({ - row, - columns, - handleRowClick, - style, -}) => { - const onClick: React.MouseEventHandler = event => - (handleRowClick || noop)(event, row.id); - return ( - - {columns.map(column => ( - - ))} - - ); -}; -const DataTableRow = memo(_DataTableRow); - -/** - * Table with sorting capabilites automatic rendering of cells - * Note that the objects in props.data needs have an id property - * The columns array defines which columns from the data to show. - * - * @param data A list of data entries, where object properties must - * be strictly equal to column ids. - * - * @param columns A list of columns with the following shape: - * { - * // The column identifier must be strictly equal the relevant data entry - * // key: - * id: String, - * - * // The display label for the column: - * label: String, - * - * // If true, the column contents will be right-aligned: - * numeric: Boolean, - * - * // If true, padding will be disabled for table cells: - * disablePadding: Boolean, - * - * // A function taking a data row and returning a suitable primitive for - * // sorting: - * sortValue: (Object) => Any - * } - * - * @param orderBy The column ID initially used for sorting - * - * @param dataVersion A version identifier for the data which *must* - * be updated when the contents of the data changes. This can be used for - * components where the same SortableTable element will be used to display - * variable sets of data. - * - * @param footerData A list of data entries to be placed in - * the table footer, which will not be sorted. - * - * @param onRowClicked Get notified when a user clicks - * on the row. The handler will receive the row id as the first argument, and - * the synthetic click event as the second argument. - * - * @example - * render { - * const data = [ - * { id: 'buffalos', amount: 1, status: , statusValue: 2 }, - * { id: 'milk', amount: 3, status: , statusValue: 1 } - * ]; - * const columns = [ - * { id: 'id', label: 'ID' }, - * { id: 'amount', disablePadding: false, numeric: true, label: 'AMOUNT' }, - * { id: 'status', label: 'STATUS', sortValue: row => row.statusValue }, - * ]; - * const footerData = [ - * { id: 'total', amount: 4, statusValue: 2, status: }, - * ]; - * return ( - * {console.log('Row:' + id + ' clicked'); - * ev.preventDefault();}}/>) - * } - * - * // XXX (@koroeskohr): supposedly this is leftover from your internal doc - * @deprecated use shared/components/DataGrid - */ - -type SortableTableProps = { - data: Row[]; - footerData: Row[]; - orderBy: string; - columns: Column[]; - onRowClicked?: ( - id: string, - event: React.MouseEvent, - ) => void; - dataVersion: string; -}; - -type TableState = { - orderBy: string; - order: Order; - data: Row[]; -}; - -const SortableTable: FC = props => { - const [state, setState] = useState({ - orderBy: props.orderBy, - order: 'asc', - data: props.data, - }); - - const updateData = (data: Row[], orderBy: string, order: Order) => { - const sortValueFn = (props.columns.find(col => col.id === orderBy) || {}) - .sortValue; - - const sortedData = data.slice().sort((a, b) => { - const valueA = sortValueFn ? sortValueFn(a) : a[orderBy]; - const valueB = sortValueFn ? sortValueFn(b) : b[orderBy]; - const inc = order === 'desc' ? -1 : 1; - if (valueA === valueB) return 0; - if (valueA === '' || valueA === null) return inc; - if (valueB === '' || valueB === null) return -inc; - return valueA < valueB ? -inc : inc; - }); - setState({ data: sortedData, order, orderBy }); - }; - - const handleRequestSort: SortHandler = (event, property) => { - event.preventDefault(); - const orderBy = property; - let order: Order = 'desc'; - if (state.orderBy === property && state.order === 'desc') { - order = 'asc'; - } - updateData(state.data, orderBy, order); - }; - - const handleRowClick = (event: React.MouseEvent, id: string): void => { - if (props.onRowClicked) { - props.onRowClicked(id, event); - } - }; - - useEffect(() => { - const { data } = props; - const { orderBy, order } = state; - updateData(data, orderBy, order); - }, [props.dataVersion]); - - const { data, order, orderBy } = state; - const { columns, dataVersion, footerData } = props; - - let tableFoot = null; - if (footerData && footerData.length > 0) { - tableFoot = ( - - {footerData.map(row => ( - - ))} - - ); - } - return ( -
- - - {data.map(row => ( - - ))} - - {tableFoot} -
- ); -}; - -export default SortableTable; diff --git a/packages/core/src/components/SortableTable/index.tsx b/packages/core/src/components/SortableTable/index.tsx deleted file mode 100644 index 718f582b8f..0000000000 --- a/packages/core/src/components/SortableTable/index.tsx +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { default } from './SortableTable'; diff --git a/packages/core/src/components/Status/Status.stories.tsx b/packages/core/src/components/Status/Status.stories.tsx index 0cdc38a10d..33a8e51e65 100644 --- a/packages/core/src/components/Status/Status.stories.tsx +++ b/packages/core/src/components/Status/Status.stories.tsx @@ -24,7 +24,7 @@ import { StatusRunning, StatusWarning, } from './Status'; -import SortableTable from 'components/SortableTable'; +import Table from 'components/Table'; import InfoCard from 'layout/InfoCard'; export default { @@ -71,9 +71,9 @@ const data = [ ]; const columns = [ - { id: 'status', label: 'Status' }, - { id: 'label', label: 'Label' }, - { id: 'usage', label: 'Example usage' }, + { field: 'status', title: 'Status' }, + { field: 'label', title: 'Label' }, + { field: 'usage', title: 'Example usage' }, ]; const containerStyle = { width: 600 }; @@ -81,7 +81,15 @@ const containerStyle = { width: 600 }; export const Default = () => (
- + ); diff --git a/packages/core/src/components/TrendLine/TrendLine.stories.tsx b/packages/core/src/components/TrendLine/TrendLine.stories.tsx index 5263385e1c..562feeff80 100644 --- a/packages/core/src/components/TrendLine/TrendLine.stories.tsx +++ b/packages/core/src/components/TrendLine/TrendLine.stories.tsx @@ -16,7 +16,7 @@ import React from 'react'; import TrendLine from '.'; -import SortableTable from 'components/SortableTable'; +import Table from 'components/Table'; import InfoCard from 'layout/InfoCard'; export default { @@ -58,17 +58,25 @@ const data = [ ]; const columns = [ - { id: 'stock', label: 'Stock' }, - { id: 'day', label: 'Day' }, - { id: 'week', label: 'Week' }, - { id: 'month', label: 'Month' }, - { id: 'year', label: 'Year' }, + { field: 'stock', title: 'Stock' }, + { field: 'day', title: 'Day' }, + { field: 'week', title: 'Week' }, + { field: 'month', title: 'Month' }, + { field: 'year', title: 'Year' }, ]; export const Default = () => (
- +
); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 8699034efc..e817ada9c9 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -35,7 +35,8 @@ export { default as Progress } from './components/Progress'; export * from './components/SimpleStepper'; export { AlphaLabel, BetaLabel } from './components/Lifecycle'; export { default as SupportButton } from './components/SupportButton'; -export { default as SortableTable } from './components/SortableTable'; +export { default as Table, SubvalueCell } from './components/Table'; +export type { TableColumn } from './components/Table/Table'; export { default as StructuredMetadataTable } from './components/StructuredMetadataTable'; export { default as TrendLine } from './components/TrendLine'; export { FeatureCalloutCircular } from './components/FeatureDiscovery/FeatureCalloutCircular'; diff --git a/plugins/lighthouse/src/components/Audit/AuditRow.tsx b/plugins/lighthouse/src/components/Audit/AuditRow.tsx deleted file mode 100644 index 71668138f4..0000000000 --- a/plugins/lighthouse/src/components/Audit/AuditRow.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React, { FC } from 'react'; -import { Link, TableCell, TableRow } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; -import { TrendLine } from '@backstage/core'; -import { Website } from '../../api'; -import { - formatTime, - CATEGORIES, - CATEGORY_LABELS, - SparklinesDataByCategory, -} from '../../utils'; -import AuditStatusIcon from '../AuditStatusIcon'; - -const useStyles = makeStyles(theme => ({ - table: { - minWidth: 650, - }, - status: { - textTransform: 'capitalize', - }, - link: { - paddingTop: theme.spacing(2), - paddingBottom: theme.spacing(2), - display: 'inline-block', - }, - statusCell: { whiteSpace: 'nowrap' }, - sparklinesCell: { minWidth: 120 }, -})); - -export const AuditRow: FC<{ - website: Website; - categorySparkline: SparklinesDataByCategory; -}> = ({ website, categorySparkline }) => { - const classes = useStyles(); - - return ( - - - - {website.url} - - - {CATEGORIES.map(category => ( - - - - ))} - - {' '} - - {website.lastAudit.status.toLowerCase()} - - - {formatTime(website.lastAudit.timeCreated)} - - ); -}; - -export default AuditRow; diff --git a/plugins/lighthouse/src/components/Audit/index.tsx b/plugins/lighthouse/src/components/Audit/index.tsx deleted file mode 100644 index 349bdf5ee9..0000000000 --- a/plugins/lighthouse/src/components/Audit/index.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React, { FC, useState } from 'react'; -import { useInterval } from 'react-use'; -import { Website, lighthouseApiRef } from '../../api'; -import { useApi } from '@backstage/core'; -import { - SparklinesDataByCategory, - buildSparklinesDataForItem, -} from '../../utils'; -import { AuditRow } from './AuditRow'; -export const LIMIT = 10; - -export const Audit: FC<{ - website: Website; - categorySparkline: SparklinesDataByCategory; -}> = ({ website, categorySparkline }) => { - const lighthouseApi = useApi(lighthouseApiRef); - const [websiteState, setWebsiteState] = useState(website); - const [sparklineState, setSparklineState] = useState(categorySparkline); - - const runRefresh = async () => { - const response = await lighthouseApi.getWebsiteForAuditId( - websiteState.lastAudit.id, - ); - const auditStatus = response.lastAudit.status; - if (auditStatus === 'COMPLETED' || auditStatus === 'FAILED') { - setSparklineState(buildSparklinesDataForItem(response)); - setWebsiteState(response); - } - }; - - useInterval( - runRefresh, - websiteState?.lastAudit.status === 'RUNNING' ? 5000 : null, - ); - - return ; -}; - -export default Audit; diff --git a/plugins/lighthouse/src/components/AuditList/AuditListTable.test.tsx b/plugins/lighthouse/src/components/AuditList/AuditListTable.test.tsx index 3ab07434d7..cebab93f17 100644 --- a/plugins/lighthouse/src/components/AuditList/AuditListTable.test.tsx +++ b/plugins/lighthouse/src/components/AuditList/AuditListTable.test.tsx @@ -55,7 +55,7 @@ describe('AuditListTable', () => { ); const link = rendered.queryByText('https://anchor.fm'); const website = websiteListResponse.items.find( - w => w.url === 'https://anchor.fm', + (w) => w.url === 'https://anchor.fm', ); if (!website) throw new Error('https://anchor.fm must be present in fixture'); @@ -71,7 +71,7 @@ describe('AuditListTable', () => { wrapInThemedTestApp(auditList(websiteListResponse)), ); const website = websiteListResponse.items.find( - w => w.url === 'https://anchor.fm', + (w) => w.url === 'https://anchor.fm', ); if (!website) throw new Error('https://anchor.fm must be present in fixture'); @@ -85,21 +85,22 @@ describe('AuditListTable', () => { wrapInThemedTestApp(auditList(websiteListResponse)), ); - const completed = await rendered.findAllByText('completed'); + const completed = await rendered.findAllByText('COMPLETED'); expect(completed).toHaveLength( - websiteListResponse.items.filter(w => w.lastAudit.status === 'COMPLETED') - .length, + websiteListResponse.items.filter( + (w) => w.lastAudit.status === 'COMPLETED', + ).length, ); - const failed = await rendered.findAllByText('failed'); + const failed = await rendered.findAllByText('FAILED'); expect(failed).toHaveLength( - websiteListResponse.items.filter(w => w.lastAudit.status === 'FAILED') + websiteListResponse.items.filter((w) => w.lastAudit.status === 'FAILED') .length, ); - const running = await rendered.findAllByText('failed'); + const running = await rendered.findAllByText('FAILED'); expect(running).toHaveLength( - websiteListResponse.items.filter(w => w.lastAudit.status === 'RUNNING') + websiteListResponse.items.filter((w) => w.lastAudit.status === 'RUNNING') .length, ); }); diff --git a/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx b/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx index 082433a550..20dd1f39f8 100644 --- a/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx +++ b/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx @@ -13,81 +13,111 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { FC, useMemo } from 'react'; -import { - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, -} from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; -import { Website } from '../../api'; +import React, { FC, useState } from 'react'; +import { Table, TableColumn, TrendLine, useApi } from '@backstage/core'; +import { Website, lighthouseApiRef } from '../../api'; +import { useInterval } from 'react-use'; import { + formatTime, CATEGORIES, CATEGORY_LABELS, - SparklinesDataByCategory, buildSparklinesDataForItem, } from '../../utils'; -import Audit from '../Audit'; +import { Link } from '@material-ui/core'; +import AuditStatusIcon from '../AuditStatusIcon'; -const useStyles = makeStyles(theme => ({ - table: { - minWidth: 650, +const columns: TableColumn[] = [ + { + title: 'Website URL', + field: 'websiteUrl', }, - status: { - textTransform: 'capitalize', + ...CATEGORIES.map((category) => ({ + title: CATEGORY_LABELS[category], + field: category, + })), + { + title: 'Last Report', + field: 'lastReport', + cellStyle: { + whiteSpace: 'nowrap', + }, }, - link: { - paddingTop: theme.spacing(2), - paddingBottom: theme.spacing(2), - display: 'inline-block', + { + title: 'Last Audit Triggered', + field: 'lastAuditTriggered', + cellStyle: { + minWidth: 120, + }, }, - statusCell: { whiteSpace: 'nowrap' }, - sparklinesCell: { minWidth: 120 }, -})); +]; export const AuditListTable: FC<{ items: Website[] }> = ({ items }) => { - const classes = useStyles(); - const categorySparklines: Record = useMemo( - () => - items.reduce( - (res, item) => ({ - ...res, - [item.url]: buildSparklinesDataForItem(item), - }), - {}, - ), - [items], + const [websiteState, setWebsiteState] = useState(items); + const lighthouseApi = useApi(lighthouseApiRef); + + const runRefresh = (websites: Website[]) => { + websites.forEach(async (website) => { + const response = await lighthouseApi.getWebsiteForAuditId( + website.lastAudit.id, + ); + const auditStatus = response.lastAudit.status; + if (auditStatus === 'COMPLETED' || auditStatus === 'FAILED') { + const newWebsiteData = websiteState.slice(0); + newWebsiteData[ + newWebsiteData.findIndex((w) => w.url === response.url) + ] = response; + setWebsiteState(newWebsiteData); + } + }); + }; + + const runningWebsiteAudits = websiteState + ? websiteState.filter((website) => website.lastAudit.status === 'RUNNING') + : []; + + useInterval( + () => runRefresh(runningWebsiteAudits), + runningWebsiteAudits.length > 0 ? 5000 : null, ); + const data = websiteState.map((website) => { + const trendlineData = buildSparklinesDataForItem(website); + const trendlines: any = {}; + CATEGORIES.forEach((category) => { + trendlines[category] = ( + + ); + }); + + return { + websiteUrl: ( + + {website.url} + + ), + ...trendlines, + lastReport: ( + <> + {' '} + {website.lastAudit.status.toUpperCase()} + + ), + lastAuditTriggered: formatTime(website.lastAudit.timeCreated), + }; + }); + return ( - -
- - - Website URL - {CATEGORIES.map(category => ( - - {CATEGORY_LABELS[category]} - - ))} - Last Report - Last Audit Triggered - - - - {items.map(website => ( - - ))} - -
- + ); };