Fixes #38806 - Stabilize job detail table and fix unresponsive actions#1009
Fixes #38806 - Stabilize job detail table and fix unresponsive actions#1009adamruzicka merged 1 commit intotheforeman:masterfrom
Conversation
34933fa to
962f783
Compare
| act(() => { | ||
| fireEvent.click(screen.getByText('STDOUT')); | ||
| fireEvent.click(screen.getByText('DEBUG')); | ||
| fireEvent.click(screen.getByText('STDERR')); | ||
| }); | ||
| expect( | ||
| screen.queryAllByText('No output for the selected filters') | ||
| ).toHaveLength(1); | ||
| expect(screen.queryAllByText('Exit status: 1')).toHaveLength(0); | ||
| expect( | ||
| screen.queryAllByText('StandardError: Job execution failed') | ||
| ).toHaveLength(0); |
There was a problem hiding this comment.
These tests need to be moved, not removed
| const getInvocationState = hostId => | ||
| hostInvocationStates[hostId] || { | ||
| showOutputType: { stderr: true, stdout: true, debug: true }, | ||
| showTemplatePreview: false, | ||
| showCommand: false, | ||
| }; | ||
|
|
There was a problem hiding this comment.
It also needs to be used in webpack/JobInvocationDetail/TemplateInvocationPage.js
There was a problem hiding this comment.
Pull Request Overview
This PR fixes issue #38806 by stabilizing the job detail table and resolving unresponsive action buttons. The solution lifts component state from individual TemplateInvocation components to the parent JobInvocationHostTable to prevent state destruction when rows are collapsed/expanded.
- Moved template invocation state (output toggles, preview settings) from child to parent component
- Refactored tests to remove interactive event testing and use prop-based state management
- Added CSS styling to properly hide collapsed rows
Reviewed Changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| TemplateInvocation.js | Removed internal state management, now receives state as props |
| JobInvocationHostTable.js | Added centralized state management for all host invocation states |
| OutputToggleGroup.js | Updated to use direct state values instead of functional updates |
| TemplateInvocation.test.js | Refactored tests to use prop-based state instead of simulated user interactions |
| JobInvocationDetail.scss | Added CSS rule to hide collapsed table rows |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| const handleSTDERRClick = _isSelected => { | ||
| setShowOutputType(prevShowOutputType => ({ | ||
| ...prevShowOutputType, | ||
| setShowOutputType({ | ||
| ...showOutputType, | ||
| stderr: _isSelected, | ||
| })); | ||
| }); | ||
| }; | ||
|
|
||
| const handleSTDOUTClick = _isSelected => { | ||
| setShowOutputType(prevShowOutputType => ({ | ||
| ...prevShowOutputType, | ||
| setShowOutputType({ | ||
| ...showOutputType, | ||
| stdout: _isSelected, | ||
| })); | ||
| }); | ||
| }; | ||
| const handleDEBUGClick = _isSelected => { | ||
| setShowOutputType(prevShowOutputType => ({ | ||
| ...prevShowOutputType, | ||
| setShowOutputType({ | ||
| ...showOutputType, | ||
| debug: _isSelected, | ||
| })); | ||
| }); | ||
| }; |
There was a problem hiding this comment.
The state update functions create new objects on every call, which could cause unnecessary re-renders. Consider using functional updates or memoizing these handlers to avoid creating new objects when the current state hasn't changed.
| jobID: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, | ||
| isInTableView: PropTypes.bool, | ||
| isExpanded: PropTypes.bool, | ||
| showOutputType: PropTypes.object.isRequired, |
There was a problem hiding this comment.
Using PropTypes.object is too generic. Consider using PropTypes.shape() to define the specific structure expected: PropTypes.shape({ stderr: PropTypes.bool, stdout: PropTypes.bool, debug: PropTypes.bool }).isRequired
| showOutputType: PropTypes.object.isRequired, | |
| showOutputType: PropTypes.shape({ | |
| stderr: PropTypes.bool, | |
| stdout: PropTypes.bool, | |
| debug: PropTypes.bool, | |
| }).isRequired, |
| .pf-v5-c-table__tbody > tr.row-hidden { | ||
| display: none; | ||
| } |
There was a problem hiding this comment.
This needs to be more specific
|
|
||
| if (!isExpanded) { | ||
| return null; | ||
| } | ||
|
|
||
| if ((status === STATUS.PENDING && isEmpty(response)) || !response) { | ||
| return <Skeleton />; | ||
| if (!response || (status === STATUS.PENDING && isEmpty(response))) { | ||
| return <Skeleton data-testid="template-invocation-skeleton" />; |
There was a problem hiding this comment.
Could you please explain this change?
There was a problem hiding this comment.
if (!isExpanded) { return null; }
- Removed it so it hides the row instead of destroying it -> saves its state
data-testid
- fixes failing test
Do you think it should be done differently?
962f783 to
c5726c2
Compare
c5726c2 to
c20685c
Compare
adamruzicka
left a comment
There was a problem hiding this comment.
The lint issues seem to be a hard requirement
| /> | ||
| <RowSelectTd | ||
| rowData={result} | ||
| {...{ selectOne, isSelected }} |
There was a problem hiding this comment.
Isn't the splat unnecessary?
| {...{ selectOne, isSelected }} | |
| {selectOne, isSelected} |
There was a problem hiding this comment.
Oh, so the first pair of curly braces is to go from jsx into the "native js" context and then splat the hash inside it? Wouldn't this then be the same thing as
| {...{ selectOne, isSelected }} | |
| selectOne | |
| isSelected |
I don't insist on it, just trying to figure out how these things work
There was a problem hiding this comment.
another way would be just
| {...{ selectOne, isSelected }} | |
| selectOne={selecteOne} | |
| isSelected={isSelected} |
| @@ -64,6 +64,13 @@ const JobInvocationHostTable = ({ | |||
| const [expandedHost, setExpandedHost] = useState([]); | |||
There was a problem hiding this comment.
Although not introduced here, a Set would be a more appropriate data structure, considering how it is being used
There was a problem hiding this comment.
This will also need changes in JobInvocationHostTable.js:352
Uncaught TypeError: expandedHost.includes is not a function
at JobInvocationHostTable.js:352:46
at Array.every (<anonymous>)
at JobInvocationHostTable (JobInvocationHostTable.js:352:17)
c20685c to
a8526ea
Compare
|
@MariaAga @adamruzicka thank you for your reviews! Could you please check if I incorporated everything? |
adamruzicka
left a comment
There was a problem hiding this comment.
To be explicit about #1009 (comment)
a8526ea to
ec900df
Compare
adamruzicka
left a comment
There was a problem hiding this comment.
Looks good, I haven't seen any oddities with this
| jest.mock('@patternfly/react-core', () => { | ||
| const originalModule = jest.requireActual('@patternfly/react-core'); | ||
| return { | ||
| ...originalModule, | ||
| Tooltip: ({ children }) => <>{children}</>, | ||
| Popper: ({ children }) => <>{children}</>, | ||
| ClipboardCopyButton: jest.fn(props => ( | ||
| <button data-testid="mock-clipboard-copy-button"> | ||
| {props.children || 'Mock Copy Button'} | ||
| </button> | ||
| )), | ||
| }; | ||
| }); | ||
|
|
There was a problem hiding this comment.
Could you please explain this mock is needed?
There was a problem hiding this comment.
@MariaAga Sure, the tests were crashing while trying to render this button, so I made this mock. If you think it's bad, I can try to find different solution.
There was a problem hiding this comment.
I would rather test components fully, so if we need to mock something because it crashes, to me it looks like it might also crash when users are using it
MariaAga
left a comment
There was a problem hiding this comment.
Tested on my dev setup and it seem to work, I just have a question about the mock in the test
ec900df to
d38d478
Compare
MariaAga
left a comment
There was a problem hiding this comment.
lgtm, @adamruzicka can this be merged now, or do we need to wait because of branching?
|
We can get this in straight away, thank you both |

Initial issue:
The "Rerun" button in the Job Details table would inconsistently become unresponsive.
My thoughts:
I couldn't reproduce this bug, however, I still decided to make this change. When a row was collapsed, the react state was destroyed, which could break event handlers on other rows during a re-render.
Solution:
Lifting the component state to the JobInvocationHostTable.