-
Notifications
You must be signed in to change notification settings - Fork 136
Improvements and fixes for new Feature Gate page #3779
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
811082c
249eaba
49c3856
1e48e68
c066822
1f25eec
99607ae
8d9ec0f
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 |
|---|---|---|
|
|
@@ -7,126 +7,17 @@ import { | |
| Container, | ||
| Tab, | ||
| Tabs, | ||
| Tooltip, | ||
| Typography, | ||
| } from '@mui/material' | ||
| import { DataGrid } from '@mui/x-data-grid' | ||
| import { Link } from 'react-router-dom' | ||
| import { pathForTestByVariant, safeEncodeURIComponent } from '../helpers' | ||
| import { safeEncodeURIComponent } from '../helpers' | ||
| import Alert from '@mui/material/Alert' | ||
| import HelpOutlineIcon from '@mui/icons-material/HelpOutline' | ||
| import PropTypes from 'prop-types' | ||
| import React, { Fragment, useEffect } from 'react' | ||
| import SimpleBreadcrumbs from '../components/SimpleBreadcrumbs' | ||
|
|
||
| function TestResultsSection({ title, apiUrl, release }) { | ||
| const [rows, setRows] = React.useState([]) | ||
| const [isLoaded, setLoaded] = React.useState(false) | ||
| const [fetchError, setFetchError] = React.useState('') | ||
| const [sortModel, setSortModel] = React.useState([ | ||
| { field: 'current_pass_percentage', sort: 'asc' }, | ||
| ]) | ||
|
|
||
| useEffect(() => { | ||
| if (!apiUrl) { | ||
| setRows([]) | ||
| setLoaded(true) | ||
| return | ||
| } | ||
| setLoaded(false) | ||
| setFetchError('') | ||
|
|
||
| fetch(apiUrl) | ||
| .then((response) => { | ||
| if (response.status !== 200) { | ||
| throw new Error('server returned ' + response.status) | ||
| } | ||
| return response.json() | ||
| }) | ||
| .then((json) => { | ||
| setRows(json || []) | ||
| setLoaded(true) | ||
| }) | ||
| .catch((error) => { | ||
| setFetchError('Could not retrieve ' + title + ': ' + error) | ||
| setLoaded(true) | ||
| }) | ||
| }, [apiUrl, title]) | ||
|
|
||
| const columns = [ | ||
| { | ||
| field: 'name', | ||
| headerName: 'Test Name', | ||
| flex: 4, | ||
| renderCell: (params) => ( | ||
| <Link to={pathForTestByVariant(release, params.value)}> | ||
| {params.value} | ||
| </Link> | ||
| ), | ||
| }, | ||
| { | ||
| field: 'current_successes', | ||
| headerName: 'Current Successes', | ||
| type: 'number', | ||
| flex: 1, | ||
| }, | ||
| { | ||
| field: 'current_failures', | ||
| headerName: 'Current Failures', | ||
| type: 'number', | ||
| flex: 1, | ||
| }, | ||
| { | ||
| field: 'current_flakes', | ||
| headerName: 'Current Flakes', | ||
| type: 'number', | ||
| flex: 1, | ||
| }, | ||
| { | ||
| field: 'current_pass_percentage', | ||
| headerName: 'Current Pass %', | ||
| type: 'number', | ||
| flex: 1, | ||
| renderCell: (params) => | ||
| params.value != null ? params.value.toFixed(2) + '%' : '', | ||
| }, | ||
| { | ||
| field: 'current_runs', | ||
| headerName: 'Current Runs', | ||
| type: 'number', | ||
| flex: 1, | ||
| }, | ||
| ] | ||
|
|
||
| if (fetchError) { | ||
| return <Alert severity="error">{fetchError}</Alert> | ||
| } | ||
|
|
||
| return ( | ||
| <Box sx={{ mt: 2 }}> | ||
| <Typography variant="h6" sx={{ mb: 1 }}> | ||
| {title} ({isLoaded ? rows.length : '...'} tests) | ||
| </Typography> | ||
| <DataGrid | ||
| loading={!isLoaded} | ||
| rows={rows} | ||
| columns={columns} | ||
| getRowId={(row) => row.name} | ||
| getRowHeight={() => 'auto'} | ||
| autoHeight={true} | ||
| rowsPerPageOptions={[10, 25, 50]} | ||
| pageSize={25} | ||
| sortModel={sortModel} | ||
| onSortModelChange={setSortModel} | ||
| disableSelectionOnClick | ||
| /> | ||
| </Box> | ||
| ) | ||
| } | ||
|
|
||
| TestResultsSection.propTypes = { | ||
| title: PropTypes.string.isRequired, | ||
| apiUrl: PropTypes.string, | ||
| release: PropTypes.string.isRequired, | ||
| } | ||
| import TestTable from './TestTable' | ||
|
|
||
| export default function FeatureGateDetail(props) { | ||
| const { release, featureGate } = props | ||
|
|
@@ -178,6 +69,50 @@ export default function FeatureGateDetail(props) { | |
| }) | ||
| }, [release, featureGate]) | ||
|
|
||
| const tabAutoSelected = React.useRef(false) | ||
|
|
||
| // <= 1 because the API always returns an implicit "Overall" row | ||
| const handleAnnotationDataLoaded = React.useCallback((count) => { | ||
| if (!tabAutoSelected.current && count <= 1) { | ||
| setActiveTab(1) | ||
| tabAutoSelected.current = true | ||
| } | ||
| }, []) | ||
|
Comment on lines
+75
to
+80
Contributor
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. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Only auto-switch when annotations are empty. Line 75 uses 🤖 Prompt for AI Agents
Contributor
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. This is required because there's an implicit Overall row. We should comment this.
Contributor
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.
Here's a suggested comment to make that explicit: const handleAnnotationDataLoaded = React.useCallback((count) => {
// TestTable always includes an implicit "Overall" row, so a count of 1
// means there are no real annotated tests. Auto-switch to the capability
// tab in that case.
if (!tabAutoSelected.current && count <= 1) {
setActiveTab(1)
tabAutoSelected.current = true
}
}, [])✏️ Learnings added
|
||
|
|
||
| const annotationFilter = { | ||
| items: [ | ||
| { | ||
| columnField: 'name', | ||
| operatorValue: 'contains', | ||
| value: `FeatureGate:${featureGate}]`, | ||
| }, | ||
| ], | ||
| } | ||
|
|
||
| // Installer gates currently run a broad conformance suite where full passes | ||
| // aren't required, so "install should succeed" is the meaningful signal. | ||
| // Switch to "openshift-tests should work" once installer jobs run a minimal | ||
| // conformance suite. | ||
| const capabilityTestName = featureGate.includes('Install') | ||
| ? 'install should succeed' | ||
| : 'openshift-tests should work' | ||
|
|
||
| const capabilityFilter = { | ||
| items: [ | ||
| { | ||
| columnField: 'name', | ||
| operatorValue: 'contains', | ||
| value: capabilityTestName, | ||
| }, | ||
| { | ||
| columnField: 'variants', | ||
| operatorValue: 'has entry containing', | ||
| value: `Capability:${featureGate}`, | ||
| }, | ||
| ], | ||
| linkOperator: 'and', | ||
| } | ||
|
|
||
| if (fetchError) { | ||
| return ( | ||
| <Container size="xl"> | ||
|
|
@@ -231,6 +166,16 @@ export default function FeatureGateDetail(props) { | |
| </Typography> | ||
| <Typography variant="body1" sx={{ mb: 1 }}> | ||
| <strong>Total Test Count:</strong> {gate.unique_test_count} | ||
| <Tooltip title="Unique test names. The tables below may show more rows due to per-variant breakdowns."> | ||
| <HelpOutlineIcon | ||
| fontSize="small" | ||
| sx={{ | ||
| ml: 0.5, | ||
| verticalAlign: 'middle', | ||
| color: 'text.secondary', | ||
| }} | ||
| /> | ||
| </Tooltip> | ||
| </Typography> | ||
| <Typography variant="body1" component="div"> | ||
| <strong>Enabled:</strong>{' '} | ||
|
|
@@ -253,21 +198,24 @@ export default function FeatureGateDetail(props) { | |
| </Tabs> | ||
| </Box> | ||
|
|
||
| {activeTab === 0 && gate.links && ( | ||
| <TestResultsSection | ||
| title="Tests tagged with FeatureGate annotation" | ||
| apiUrl={gate.links.tests_by_annotation} | ||
| <Box sx={{ display: activeTab === 0 ? 'block' : 'none' }}> | ||
| <TestTable | ||
| key={'fg-annotation-' + featureGate} | ||
| release={release} | ||
| collapse={false} | ||
| filterModel={annotationFilter} | ||
| onDataLoaded={handleAnnotationDataLoaded} | ||
| /> | ||
| )} | ||
| </Box> | ||
|
|
||
| {activeTab === 1 && gate.links && ( | ||
| <TestResultsSection | ||
| title="Tests matching capability variant" | ||
| apiUrl={gate.links.tests_by_capability} | ||
| <Box sx={{ display: activeTab === 1 ? 'block' : 'none' }}> | ||
| <TestTable | ||
| key={'fg-capability-' + featureGate} | ||
| release={release} | ||
| collapse={false} | ||
| filterModel={capabilityFilter} | ||
| /> | ||
| )} | ||
| </Box> | ||
|
Comment on lines
+201
to
+218
Contributor
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. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Isolate URL state for the two Both mounted tables use the same URL-backed query keys ( 🤖 Prompt for AI Agents
Contributor
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. Beyond scope for this I think.
Contributor
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.
|
||
| </Container> | ||
| </Fragment> | ||
| ) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.