-
Notifications
You must be signed in to change notification settings - Fork 131
TRT-2273: Show Regressions for Test Analysis #3775
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
Changes from all commits
068156f
a992a14
f466df8
5bf82bd
53f4c81
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| import { Link } from 'react-router-dom' | ||
| import { | ||
| Paper, | ||
| Table, | ||
| TableBody, | ||
| TableCell, | ||
| TableContainer, | ||
| TableHead, | ||
| TableRow, | ||
| Tooltip, | ||
| Typography, | ||
| } from '@mui/material' | ||
| import { | ||
| parseVariantName, | ||
| relativeTime, | ||
| safeEncodeURIComponent, | ||
| } from '../helpers' | ||
| import Alert from '@mui/material/Alert' | ||
| import PropTypes from 'prop-types' | ||
| import React, { useEffect, useMemo } from 'react' | ||
|
|
||
| export default function TestRegressionsTable({ | ||
| release, | ||
| testName, | ||
| filterModel, | ||
| }) { | ||
| const [isLoaded, setLoaded] = React.useState(false) | ||
| const [regressions, setRegressions] = React.useState([]) | ||
| const [fetchError, setFetchError] = React.useState('') | ||
|
|
||
| useEffect(() => { | ||
| if (!testName || !release) return | ||
|
|
||
| setLoaded(false) | ||
| setFetchError('') | ||
|
|
||
| const url = `${ | ||
| process.env.REACT_APP_API_URL | ||
| }/api/component_readiness/regressions?release=${safeEncodeURIComponent( | ||
| release | ||
| )}&test=${safeEncodeURIComponent(testName)}` | ||
|
|
||
| fetch(url) | ||
| .then((res) => { | ||
| if (res.status !== 200) { | ||
| throw new Error('server returned ' + res.status) | ||
| } | ||
| return res.json() | ||
| }) | ||
| .then((data) => { | ||
| setRegressions(data || []) | ||
| setLoaded(true) | ||
| }) | ||
| .catch((error) => { | ||
| setFetchError('Could not retrieve regression data: ' + error) | ||
| setLoaded(true) | ||
| }) | ||
| }, [release, testName]) | ||
|
|
||
| const variantFilters = useMemo(() => { | ||
| if (!filterModel || !filterModel.items) return [] | ||
| return filterModel.items.filter((f) => f.columnField === 'variants') | ||
| }, [filterModel]) | ||
|
|
||
| const filteredRegressions = useMemo(() => { | ||
| let filtered = regressions.filter((r) => !r.closed || !r.closed.Valid) | ||
|
|
||
| if (variantFilters.length === 0) return filtered | ||
|
|
||
| return filtered.filter((regression) => { | ||
| const variantValues = (regression.variants || []).map( | ||
| (v) => parseVariantName(v).name | ||
| ) | ||
|
|
||
| return variantFilters.every((filter) => { | ||
| const hasMatch = variantValues.some( | ||
| (v) => v.toLowerCase() === filter.value.toLowerCase() | ||
| ) | ||
| if (filter.not) { | ||
| return !hasMatch | ||
| } | ||
| return hasMatch | ||
| }) | ||
| }) | ||
| }, [regressions, variantFilters]) | ||
|
|
||
| if (!isLoaded) { | ||
| return <p>Loading...</p> | ||
| } | ||
| if (fetchError !== '') { | ||
| return <Alert severity="error">{fetchError}</Alert> | ||
| } | ||
| if (filteredRegressions.length === 0) { | ||
| return <Typography>No active regressions found</Typography> | ||
| } | ||
|
|
||
| return ( | ||
| <TableContainer component={Paper} style={{ marginTop: 20 }}> | ||
| <Table size="small" aria-label="regressions-table"> | ||
| <TableHead> | ||
| <TableRow> | ||
| <TableCell>ID</TableCell> | ||
| <TableCell>Variants</TableCell> | ||
| <TableCell>Regressed Since</TableCell> | ||
| <TableCell>Last Failure</TableCell> | ||
| </TableRow> | ||
| </TableHead> | ||
| <TableBody> | ||
| {filteredRegressions.map((regression) => { | ||
| const variantValues = (regression.variants || []) | ||
| .map((v) => parseVariantName(v).name) | ||
| .filter((val) => !['default', 'none', 'unknown'].includes(val)) | ||
|
|
||
| const tooltipLines = (regression.variants || []).sort().join('\n') | ||
|
|
||
| return ( | ||
| <TableRow key={regression.id}> | ||
| <TableCell> | ||
| <Link | ||
| to={`/sippy-ng/component_readiness/regressions/${regression.id}`} | ||
| > | ||
| {regression.id} | ||
| </Link> | ||
| </TableCell> | ||
| <TableCell> | ||
| <Tooltip | ||
| title={ | ||
| <span style={{ whiteSpace: 'pre-line' }}> | ||
| {tooltipLines} | ||
| </span> | ||
| } | ||
| > | ||
| <span>{variantValues.join(', ')}</span> | ||
| </Tooltip> | ||
| </TableCell> | ||
| <TableCell> | ||
| {regression.opened | ||
| ? relativeTime(new Date(regression.opened), new Date()) | ||
| : ''} | ||
| </TableCell> | ||
| <TableCell> | ||
| {regression.last_failure && regression.last_failure.Valid | ||
| ? relativeTime( | ||
| new Date(regression.last_failure.Time), | ||
| new Date() | ||
| ) | ||
| : ''} | ||
| </TableCell> | ||
| </TableRow> | ||
| ) | ||
| })} | ||
| </TableBody> | ||
| </Table> | ||
| </TableContainer> | ||
| ) | ||
| } | ||
|
|
||
| TestRegressionsTable.propTypes = { | ||
| release: PropTypes.string.isRequired, | ||
| testName: PropTypes.string.isRequired, | ||
| filterModel: PropTypes.object, | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -760,6 +760,63 @@ func Test_RegressionAPI(t *testing.T) { | |
| err := util.SippyGet(fmt.Sprintf("/api/component_readiness/regressions?view=%s-main&release=%s", util.Release, util.Release), ®ressions) | ||
| require.Error(t, err, "Expected error when both view and release are provided") | ||
| }) | ||
| t.Run("filter regressions by test name", func(t *testing.T) { | ||
| defer cleanupAllTriages(dbc) | ||
|
|
||
| // Create a regression with a unique test name | ||
| uniqueTestName := "TestUniqueFilterByName" | ||
| uniqueRegression := createTestRegressionWithDetails(t, tracker, view, "filter-test-id", "comp-filter", "cap-filter", uniqueTestName, crtest.ExtremeRegression) | ||
| defer dbc.DB.Delete(uniqueRegression.Regression) | ||
|
Comment on lines
+767
to
+769
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Other regressions weren't created though...we better make sure to have some more that we verify are not included in the response.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added decoy regressions with different test names to both test cases ("filter regressions by test name" and "filter regressions by test name without release"). The tests now create additional regressions and explicitly verify they are NOT included in the filtered response. |
||
|
|
||
| // Create additional regressions with different test names to verify they are excluded | ||
| otherRegression1 := createTestRegressionWithDetails(t, tracker, view, "filter-other-id-1", "comp-other-1", "cap-other-1", "TestOtherName1", crtest.SignificantRegression) | ||
| defer dbc.DB.Delete(otherRegression1.Regression) | ||
| otherRegression2 := createTestRegressionWithDetails(t, tracker, view, "filter-other-id-2", "comp-other-2", "cap-other-2", "TestOtherName2", crtest.ExtremeRegression) | ||
| defer dbc.DB.Delete(otherRegression2.Regression) | ||
|
|
||
| // Verify filtering by the unique test name returns only that regression | ||
| var filtered []models.TestRegression | ||
| err := util.SippyGet(fmt.Sprintf("/api/component_readiness/regressions?release=%s&test=%s", release, uniqueTestName), &filtered) | ||
| require.NoError(t, err) | ||
| require.Len(t, filtered, 1, "expected exactly one regression matching the test name") | ||
| assert.Equal(t, uniqueRegression.Regression.ID, filtered[0].ID) | ||
| assert.Equal(t, uniqueTestName, filtered[0].TestName) | ||
|
|
||
| // Verify non-matching regressions are not included | ||
| for _, r := range filtered { | ||
| assert.NotEqual(t, otherRegression1.Regression.ID, r.ID, "otherRegression1 should not be in filtered results") | ||
| assert.NotEqual(t, otherRegression2.Regression.ID, r.ID, "otherRegression2 should not be in filtered results") | ||
| } | ||
|
|
||
| // Verify filtering by a non-existent test name returns empty | ||
| var empty []models.TestRegression | ||
| err = util.SippyGet(fmt.Sprintf("/api/component_readiness/regressions?release=%s&test=%s", release, "NonExistentTestName"), &empty) | ||
| require.NoError(t, err) | ||
| assert.Empty(t, empty, "expected no regressions for a non-existent test name") | ||
| }) | ||
| t.Run("filter regressions by test name without release", func(t *testing.T) { | ||
| defer cleanupAllTriages(dbc) | ||
|
|
||
| uniqueTestName := "TestFilterNoRelease" | ||
| uniqueRegression := createTestRegressionWithDetails(t, tracker, view, "filter-norel-id", "comp-norel", "cap-norel", uniqueTestName, crtest.SignificantRegression) | ||
| defer dbc.DB.Delete(uniqueRegression.Regression) | ||
|
|
||
| // Create another regression with a different test name to verify it is excluded | ||
| otherRegression := createTestRegressionWithDetails(t, tracker, view, "filter-norel-other-id", "comp-norel-other", "cap-norel-other", "TestDifferentNoRelease", crtest.ExtremeRegression) | ||
| defer dbc.DB.Delete(otherRegression.Regression) | ||
|
|
||
| // Filtering by test name alone (no release) should also work | ||
| var filtered []models.TestRegression | ||
| err := util.SippyGet(fmt.Sprintf("/api/component_readiness/regressions?test=%s", uniqueTestName), &filtered) | ||
| require.NoError(t, err) | ||
| require.Len(t, filtered, 1, "expected exactly one regression matching the test name") | ||
| assert.Equal(t, uniqueRegression.Regression.ID, filtered[0].ID) | ||
|
|
||
| // Verify the other regression is not included | ||
| for _, r := range filtered { | ||
| assert.NotEqual(t, otherRegression.Regression.ID, r.ID, "otherRegression should not be in filtered results") | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| // Test_RegressionPotentialMatchingTriages tests the /api/component_readiness/regressions/{id}/matches endpoint | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.