Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions tests/ui/shared/FailureSummaryTab_test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,66 @@ describe('FailureSummaryTab', () => {
screen.getByTitle('Filter by test path: css/css-break/'),
).toBeInTheDocument();
});

describe('new failure lines', () => {
const newFailureSuggestion = (path, message) => ({
search: `TEST-UNEXPECTED-FAIL | ${path} | ${message}`,
search_terms: [path],
path_end: path,
failure_new_in_rev: true,
bugs: { open_recent: [], all_others: [] },
});

const mockSuggestions = (suggestions) => {
fetchMock.reset();
fetchMock.get(getApiUrl('/jobs/?push_id=511138', repoName), selectedJob);
fetchMock.get(
getProjectUrl('/jobs/255514014/bug_suggestions/', repoName),
suggestions,
);
};

test('banner counts every new failure line and flags only the first', async () => {
mockSuggestions([
newFailureSuggestion('path/one.js', 'first new failure'),
newFailureSuggestion('path/two.js', 'second new failure'),
{
// Not a new failure: three-part search but not flagged new in rev.
...newFailureSuggestion('path/three.js', 'not new'),
failure_new_in_rev: false,
},
]);
render(testFailureSummaryTab());

// Banner reflects the count of qualifying lines (2, not 3).
expect(
await screen.findByText(/2 new failure line\(s\)/),
).toBeInTheDocument();

// Only the first qualifying line is flagged with the NEW button.
const newButtons = screen.getAllByTitle(
/number of times this error message has been seen/,
);
expect(newButtons).toHaveLength(1);
});

test('renders no banner or NEW button when there are no new failure lines', async () => {
mockSuggestions([
{
...newFailureSuggestion('path/one.js', 'old failure'),
failure_new_in_rev: false,
},
]);
render(testFailureSummaryTab());

// Wait for the (non-new) failure line to render before asserting absence.
expect(await screen.findByText(/old failure/)).toBeInTheDocument();
expect(screen.queryByText(/new failure line\(s\)/)).toBeNull();
expect(
screen.queryByTitle(
/number of times this error message has been seen/,
),
).toBeNull();
});
});
});
34 changes: 16 additions & 18 deletions ui/shared/tabs/failureSummary/FailureSummaryTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -250,21 +250,18 @@ class FailureSummaryTab extends React.Component {
const jobLogsAllParsed =
logs.length > 0 && logs.every((jlu) => jlu.parse_status !== 'pending');

selectedJob.newFailure = 0;
suggestions.forEach((suggestion) => {
suggestion.showNewButton = false;
// small hack here to use counter==0 and try for display only
if (
suggestion.search.split(' | ').length === 3 &&
(suggestion.failure_new_in_rev === true ||
(suggestion.counter === 0 && currentRepo.name === 'try'))
) {
if (selectedJob.newFailure === 0) {
suggestion.showNewButton = true;
}
selectedJob.newFailure++;
}
});
// A "new failure" line has a three-part `search` and is either flagged new
// in the revision, or (on try) has never been seen before (counter === 0).
// Derived at render time rather than mutated onto the props so render stays
// a pure function of props/state.
const isNewFailureLine = (candidate) =>
candidate.search.split(' | ').length === 3 &&
(candidate.failure_new_in_rev === true ||
(candidate.counter === 0 && currentRepo.name === 'try'));

const newFailureCount = suggestions.filter(isNewFailureLine).length;
// Only the first new-failure line is flagged with the "NEW" button.
const firstNewFailureIndex = suggestions.findIndex(isNewFailureLine);

return (
<div className="w-100 h-100" role="region" aria-label="Failure Summary">
Expand All @@ -275,13 +272,13 @@ class FailureSummaryTab extends React.Component {
ref={this.fsMount}
id="failure-summary-scroll-area"
>
{selectedJob.newFailure > 0 && (
{newFailureCount > 0 && (
<Button
className="failure-summary-new-message border-0"
title="New Test Failure"
>
{selectedJob.newFailure} new failure line(s). First one is
flagged, it might be good to look at all failures in this job.
{newFailureCount} new failure line(s). First one is flagged, it
might be good to look at all failures in this job.
</Button>
)}

Expand All @@ -290,6 +287,7 @@ class FailureSummaryTab extends React.Component {
key={`${selectedJob.id}-${index}`} // eslint-disable-line react/no-array-index-key
index={index}
suggestion={suggestion}
showNewButton={index === firstNewFailureIndex}
toggleBugFiler={() => this.fileBug(suggestion)}
toggleInternalIssueFiler={() =>
this.fileInternalIssue(suggestion)
Expand Down
4 changes: 3 additions & 1 deletion ui/shared/tabs/failureSummary/SuggestionsListItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export default class SuggestionsListItem extends React.Component {
addBug = null,
currentRepo,
developerMode,
showNewButton = false,
} = this.props;
const repoName = currentRepo.name;
const { suggestionShowMore } = this.state;
Expand Down Expand Up @@ -189,7 +190,7 @@ export default class SuggestionsListItem extends React.Component {
<FontAwesomeIcon icon={faCircleExclamation} />
</Button>

{suggestion.showNewButton && (
{showNewButton && (
<Button
className="btn-orange border-outline-secondary"
title="number of times this error message has been seen until now (including this run)"
Expand Down Expand Up @@ -267,4 +268,5 @@ SuggestionsListItem.propTypes = {
toggleInternalIssueFiler: PropTypes.func.isRequired,
developerMode: PropTypes.bool.isRequired,
addBug: PropTypes.func,
showNewButton: PropTypes.bool,
};