Skip to content

Commit 2feef20

Browse files
authored
feat: Tag broken test cases (#671)
1 parent 74fc7a7 commit 2feef20

File tree

2 files changed

+71
-5
lines changed

2 files changed

+71
-5
lines changed

client/src/app/components/pipeline/test-results/pipeline-test-results.component.html

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,12 @@ <h3 class="text-lg">Test Results</h3>
200200
>
201201
</p-tag>
202202
}
203+
@if (
204+
(testCase.combinedFailureRate && testCase.combinedFailureRate > 0.5) ||
205+
(testCase.defaultBranchFailureRate && testCase.defaultBranchFailureRate > 0.5)
206+
) {
207+
<p-tag severity="danger" value="Broken" styleClass="mt-2 text-xs" pTooltip="This test has failure rate over 50% and might be broken" />
208+
}
203209
}
204210
</div>
205211
</div>
@@ -264,6 +270,17 @@ <h4 class="font-medium mb-2">Stack Trace</h4>
264270
<div class="border border-muted-color p-4 rounded overflow-auto max-h-[400px]">
265271
<pre class="font-mono text-sm">{{ selectedTestCase()?.stackTrace?.trimStart() }}</pre>
266272
</div>
273+
<div class="flex justify-end mt-2">
274+
<button
275+
pButton
276+
class="p-button-sm p-button-secondary"
277+
(click)="downloadLogs(selectedTestCase()?.stackTrace?.trimStart() || '', selectedTestCase()?.name + '_stack_trace.txt')"
278+
[disabled]="!selectedTestCase()?.stackTrace"
279+
>
280+
<i-tabler name="download" class="mr-2"></i-tabler>
281+
Download Stack Trace
282+
</button>
283+
</div>
267284
</div>
268285
}
269286

@@ -294,6 +311,17 @@ <h4 class="font-medium mb-2">Test Case Logs</h4>
294311
}
295312
}
296313
</div>
314+
<div class="flex justify-end mt-2">
315+
<button
316+
pButton
317+
class="p-button-sm p-button-secondary"
318+
(click)="downloadLogs(filteredTestCaseLogs(), selectedTestCase()?.name + '_case_logs.txt')"
319+
[disabled]="!filteredTestCaseLogs()"
320+
>
321+
<i-tabler name="download" class="mr-2"></i-tabler>
322+
Download Logs
323+
</button>
324+
</div>
297325
</div>
298326
}
299327

@@ -307,6 +335,17 @@ <h4 class="font-medium mb-2">Test Suite Logs</h4>
307335
}
308336
}
309337
</div>
338+
<div class="flex justify-end mt-2">
339+
<button
340+
pButton
341+
class="p-button-sm p-button-secondary"
342+
(click)="downloadLogs(filteredTestSuiteLogs(), selectedTestCase()?.name + '_suite_logs.txt')"
343+
[disabled]="!filteredTestSuiteLogs()"
344+
>
345+
<i-tabler name="download" class="mr-2"></i-tabler>
346+
Download Logs
347+
</button>
348+
</div>
310349
</div>
311350
}
312351
</div>
@@ -320,7 +359,7 @@ <h4 class="font-medium mb-2">Test Suite Logs</h4>
320359
</ng-template>
321360
<div class="flex flex-col gap-4">
322361
<p>
323-
The <b>flakiness score</b> shows how unpredictable a test is, ranging from <b>0</b> (not flaky) to <b>100</b> (highly flaky). Its based on failure rates from the default
362+
The <b>flakiness score</b> shows how unpredictable a test is, ranging from <b>0</b> (not flaky) to <b>100</b> (highly flaky). It's based on failure rates from the default
324363
branch and all branches combined.
325364
</p>
326365
<p>
@@ -331,7 +370,7 @@ <h4 class="font-medium mb-2">Test Suite Logs</h4>
331370
<b>Flakiness per Branch:</b><br />
332371
&nbsp;&nbsp;&nbsp;&nbsp;If the failure rate is between 0% and 50%, we calculate:<br />
333372
&nbsp;&nbsp;&nbsp;&nbsp;<code>flakiness = (50% - failure rate) / 50%</code><br />
334-
&nbsp;&nbsp;&nbsp;&nbsp;If its 0% or over 50%, flakiness is 0.
373+
&nbsp;&nbsp;&nbsp;&nbsp;If it's 0% or over 50%, flakiness is 0.
335374
</li>
336375
<li>
337376
<b>Weighted Score:</b><br />

client/src/app/components/pipeline/test-results/pipeline-test-results.component.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import { SliderModule } from 'primeng/slider';
2121
import { provideTablerIcons, TablerIconComponent } from 'angular-tabler-icons';
2222
import {
2323
IconCheck,
24+
IconCircleX,
25+
IconCircleChevronsRight,
26+
IconDownload,
2427
IconChevronDown,
2528
IconChevronsRight,
2629
IconChevronUp,
@@ -45,7 +48,7 @@ interface LogLevel {
4548
}
4649

4750
const LOG_LEVELS: LogLevel[] = [
48-
{ value: 0, name: 'OFF', label: 'OFF', color: 'text-gray-900', includes: ['OFF'] },
51+
{ value: 0, name: 'OFF', label: 'OFF', color: '', includes: ['OFF'] },
4952
{ value: 1, name: 'FATAL', label: 'FATAL+', color: 'text-red-800', includes: ['FATAL', 'OFF'] },
5053
{ value: 2, name: 'ERROR', label: 'ERROR+', color: 'text-red-600', includes: ['ERROR', 'FATAL', 'OFF'] },
5154
{ value: 3, name: 'WARN', label: 'WARN+', color: 'text-amber-600', includes: ['WARN', 'ERROR', 'FATAL', 'OFF'] },
@@ -80,6 +83,9 @@ const LOG_LEVELS: LogLevel[] = [
8083
provideTablerIcons({
8184
IconProgress,
8285
IconCheck,
86+
IconDownload,
87+
IconCircleX,
88+
IconCircleChevronsRight,
8389
IconX,
8490
IconChevronsRight,
8591
IconClock,
@@ -108,11 +114,11 @@ export class PipelineTestResultsComponent {
108114
selectedTestCase = signal<(TestCaseDto & { suiteSystemOut: string | undefined }) | null>(null);
109115

110116
// Log level filtering
111-
selectedLogLevelValue = signal<number>(7); // Default to ALL
117+
selectedLogLevelValue = signal<number>(2); // Default to ERROR
112118

113119
// Get the current log level object based on the selected value
114120
selectedLogLevel = computed(() => {
115-
return LOG_LEVELS.find(level => level.value === this.selectedLogLevelValue()) || LOG_LEVELS[7];
121+
return LOG_LEVELS.find(level => level.value === this.selectedLogLevelValue()) || LOG_LEVELS[2];
116122
});
117123

118124
// Get the array of log level names that should be included based on the selection
@@ -155,6 +161,27 @@ export class PipelineTestResultsComponent {
155161
return this.filterLogsByLevel(this.selectedTestCase()?.suiteSystemOut);
156162
});
157163

164+
// Function to download logs with current filter applied
165+
downloadLogs(logContent: string, fileName: string): void {
166+
if (!logContent) return;
167+
168+
const blob = new Blob([logContent], { type: 'text/plain' });
169+
const url = URL.createObjectURL(blob);
170+
const link = document.createElement('a');
171+
172+
// Create the download link
173+
link.href = url;
174+
link.download = fileName;
175+
document.body.appendChild(link);
176+
177+
// Trigger the download
178+
link.click();
179+
180+
// Clean up
181+
document.body.removeChild(link);
182+
URL.revokeObjectURL(url);
183+
}
184+
158185
// Helper function for log level styling (colors)
159186
getLogLevelClass(line: string): string {
160187
// Use the same regex pattern as in filterLogsByLevel

0 commit comments

Comments
 (0)