From d5afff63877d9aa633e5001290893d9a926814a1 Mon Sep 17 00:00:00 2001 From: mshriver Date: Thu, 23 Jan 2025 08:59:49 -0500 Subject: [PATCH] Convert ClassifyFailureTable to functional --- frontend/src/components/classify-failures.js | 374 ++++++++++--------- 1 file changed, 191 insertions(+), 183 deletions(-) diff --git a/frontend/src/components/classify-failures.js b/frontend/src/components/classify-failures.js index 7d860c6f..6cd13022 100644 --- a/frontend/src/components/classify-failures.js +++ b/frontend/src/components/classify-failures.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { @@ -10,7 +10,7 @@ import { Flex, FlexItem, TextContent, - Text, + Title, } from '@patternfly/react-core'; import { TableVariant, @@ -33,45 +33,64 @@ import { } from './index'; -export class ClassifyFailuresTable extends React.Component { - static propTypes = { - filters: PropTypes.object, - run_id: PropTypes.string - } +function ClassifyFailuresTable(props) { + const { + filters, + run_id, + } = props; - constructor(props) { - super(props); - this.state = { - columns: [{title: 'Test', cellFormatters: [expandable]}, 'Result', 'Exception Name', 'Classification', 'Duration'], - rows: [getSpinnerRow(5)], - results: [], - selectedResults: [], - cursor: null, - pageSize: 10, - page: 1, - totalItems: 0, - totalPages: 0, - isEmpty: false, - isError: false, - isFieldOpen: false, - isOperationOpen: false, - includeSkipped: false, - filters: Object.assign({ - 'result': {op: 'in', val: 'failed;error'}, - 'run_id': {op: 'eq', val: props.run_id}}, props.filters), - }; - this.refreshResults = this.refreshResults.bind(this); - this.onCollapse = this.onCollapse.bind(this); - } + const [columns, setColumns] = useState([{title: 'Test', cellFormatters: [expandable]}, 'Result', 'Exception Name', 'Classification', 'Duration']); + const [rows, setRows] = useState([getSpinnerRow(5)]); + const [results, setResults] = useState([]); + const [selectedResults, setSelectedResults] = useState([]); + const [cursor, setCursor] = useState(null); + const [pageSize, setPageSize] = useState(10); + const [page, setPage] = useState(1); + const [totalItems, setTotalItems] = useState(0); + const [totalPages, setTotalPages] = useState(0); + const [isEmpty, setIsEmpty] = useState(false); + const [isError, setIsError] = useState(false); + const [isFieldOpen, setIsFieldOpen] = useState(false); + const [isOperationOpen, setIsOperationOpen] = useState(false); + const [includeSkipped, setIncludeSkipped] = useState(false); + const [appliedFilters, setAppliedFilters] = useState(Object.assign({ + 'result': {op: 'in', val: 'failed;error'}, + 'run_id': {op: 'eq', val: props.run_id}}, filters) + ); + + // wtf + // const refreshResults = this.refreshResults.bind(this); + // const onCollapse = this.onCollapse.bind(this); + + useEffect(()=>{ + // update results table when pagination changes + getResultsForTable(); + }, [page, pageSize, getResultsForTable]) - refreshResults = () => { - this.setState({selectedResults: []}); - this.getResultsForTable(); - } - onCollapse(event, rowIndex, isOpen) { - const { rows } = this.state; + useEffect(()=>{ + // fetch results on initial render + getResultsForTable(); + }, [getResultsForTable]) + + const refreshResults = useCallback(() => { + setSelectedResults([]); + getResultsForTable(); + }, [getResultsForTable]); + + const updateFilters = useCallback((filterId, name, operator, value, callback) => { + let localFilters = appliedFilters; + if ((value === null) || (value.length === 0)) { + delete localFilters[name]; + } + else { + localFilters[name] = {'op': operator, 'val': value}; + } + setAppliedFilters({filters: localFilters, page: 1}); + callback?.(); + }, [appliedFilters]); + function onCollapse(event, rowIndex, isOpen) { // lazy-load the result view so we don't have to make a bunch of artifact requests if (isOpen) { let result = rows[rowIndex].result; @@ -83,184 +102,173 @@ export class ClassifyFailuresTable extends React.Component { hideTestObject=false; defaultTab='summary'; } - rows[rowIndex + 1].cells = [{ - title: - }] + // can't update the state object directly + const updatedRows = rows.map((row, index) => { + if (index === (rowIndex + 1)) { + return {...row, cells: [{ + title: + }]}; + } else { + return row; + } + }) + setRows(updatedRows); } - rows[rowIndex].isOpen = isOpen; - this.setState({rows}); + + const updatedRows = rows.map((row, index)=>{ + if (index === rowIndex) { + return {...row, isOpen: isOpen} + } else { + return row; + } + }) + setRows(updatedRows); } - onTableRowSelect = (event, isSelected, rowId) => { - let rows; + function onTableRowSelect(_event, isSelected, rowId) { + let mappedRows; if (rowId === -1) { - rows = this.state.rows.map(oneRow => { - oneRow.selected = isSelected; + mappedRows = rows.map(oneRow => { + oneRow.selected == isSelected; return oneRow; }); } else { - rows = [...this.state.rows]; + mappedRows = [...rows]; rows[rowId].selected = isSelected; } - this.setState({ - rows, - }); - this.getSelectedResults(); - } - - setPage = (_event, pageNumber) => { - this.setState({page: pageNumber}, () => { - this.getResultsForTable(); - }); - } - - pageSizeSelect = (_event, perPage) => { - this.setState({pageSize: perPage}, () => { - this.getResultsForTable(); - }); + setRows(mappedRows); + getSelectedResults(); } - updateFilters(filterId, name, operator, value, callback) { - let filters = this.state.filters; - if ((value === null) || (value.length === 0)) { - delete filters[name]; - } - else { - filters[name] = {'op': operator, 'val': value}; - } - this.setState({filters: filters, page: 1}, callback); - } - - setFilter = (filterId, field, value) => { + const setFilter = useCallback((filterId, field, value) => { // maybe process values array to string format here instead of expecting caller to do it? let operator = (value.includes(';')) ? 'in' : 'eq' - this.updateFilters(filterId, field, operator, value, this.refreshResults) - } + updateFilters(filterId, field, operator, value, refreshResults) + }, [refreshResults, updateFilters]); - removeFilter = (filterId, id) => { + function removeFilter(filterId, id) { if ((id !== 'result') && (id !== 'run_id')) { // Don't allow removal of error/failure filter - this.updateFilters(filterId, id, null, null, this.refreshResults) + updateFilters(filterId, id, null, null, refreshResults) } } - onSkipCheck = (checked) => { - let { filters } = this.state; - filters['result']['val'] = ('failed;error') + ((checked) ? ';skipped;xfailed' : '') - this.setState( - {includeSkipped: checked, filters}, - this.refreshResults - ); + function onSkipCheck(checked) { + setIncludeSkipped(checked); + setAppliedFilters({ + ...appliedFilters, + 'result': { + ...appliedFilters.result, + 'val': ('failed;error') + ((checked) ? ';skipped;xfailed' : '') + } + }); + + refreshResults(); // useEffect on appliedFilters change? } - getSelectedResults = () => { - const { results, rows } = this.state; - let selectedResults = []; + function getSelectedResults() { + let fetchedResults = []; for (const [index, row] of rows.entries()) { if (row.selected && row.parent === null) { // rows with a parent attr are the child rows - selectedResults.push(results[index / 2]); // divide by 2 to convert row index to result index + fetchedResults.push(results[index / 2]); // divide by 2 to convert row index to result index } } - this.setState({selectedResults}); + setSelectedResults(fetchedResults); } - getResultsForTable() { - const filters = this.state.filters; - this.setState({rows: [getSpinnerRow(5)], isEmpty: false, isError: false}); - // get only failed results - let params = buildParams(filters); - params['filter'] = toAPIFilter(filters); - params['pageSize'] = this.state.pageSize; - params['page'] = this.state.page; - this.setState({rows: [['Loading...', '', '', '', '']]}); + const getResultsForTable = useCallback(()=> { + setRows([getSpinnerRow(5)]); + setIsEmpty(false); + setIsError(false); + + // get only filtered results + let params = buildParams(appliedFilters); + params['filter'] = toAPIFilter(appliedFilters); + params['pageSize'] = pageSize; + params['page'] = page; + setRows([['Loading...', '', '', '', '']]); + HttpClient.get([Settings.serverUrl, 'result'], params) .then(response => HttpClient.handleResponse(response)) - .then(data => this.setState({ - results: data.results, - rows: data.results.map((result, index) => resultToClassificationRow(result, index, this.setFilter)).flat(), - page: data.pagination.page, - pageSize: data.pagination.pageSize, - totalItems: data.pagination.totalItems, - totalPages: data.pagination.totalPages, - isEmpty: data.pagination.totalItems === 0, - })) + .then(data => { + setResults(data.results); + setRows(data.results.map((result, index) => resultToClassificationRow(result, index, setFilter)).flat()); + setPage(data.pagination.page); + setPageSize(data.pagination.pageSize); + setTotalItems(data.pagination.totalItems); + setTotalPages(data.pagination.totalPages); + setIsEmpty(data.pagination.totalItems === 0); + }) .catch((error) => { console.error('Error fetching result data:', error); - this.setState({rows: [], isEmpty: false, isError: true}); + setRows([]); + setIsEmpty(false); + setIsError(true); }); - } - - componentDidMount() { - this.getResultsForTable(); - } + }, [page, pageSize, appliedFilters, setFilter]); - render() { - const { - columns, - rows, - selectedResults, - includeSkipped, - filters - } = this.state; - const { run_id } = this.props - const pagination = { - pageSize: this.state.pageSize, - page: this.state.page, - totalItems: this.state.totalItems - } - // filters for the metadata - const resultFilters = [ - , - ] - return ( - - - - - - Test Failures - - - - - this.onSkipCheck(checked)}/> - - - - - - - - - - - - - - - ); - } + const resultFilters = [ + , + ] + return ( + // mt-lg == margin top large + + + + + + Test Failures + + + + + onSkipCheck(checked)}/> + + + + + + + + + + + + {setPage(change)}} + onSetPageSize={(_event, change) => {setPageSize(change)}} + canSelectAll={true} + onRowSelect={onTableRowSelect} + variant={TableVariant.compact} + filters={resultFilters} + /> + + + ); +} +ClassifyFailuresTable.propTypes = { + filters: PropTypes.object, + run_id: PropTypes.string } + +export default ClassifyFailuresTable;