Skip to content

Commit 52c410f

Browse files
Implement test delta in new tests page (#3729)
The legacy `viewTest.php` page supports an `onlydelta` query parameter which shows only the tests for which the status changed between the previous and current builds. That functionality is used to implement the positive superscripts on the builds page. This PR brings the same functionality to the new build tests page, paving the way for the imminent deletion of the legacy page.
1 parent b50bb79 commit 52c410f

4 files changed

Lines changed: 305 additions & 69 deletions

File tree

app/Http/Controllers/BuildController.php

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,22 +141,32 @@ public function update(int $build_id): View
141141
]);
142142
}
143143

144-
public function tests(int $build_id): View
144+
public function tests(Request $request, int $build_id): View
145145
{
146146
$this->setBuildById($build_id);
147147

148148
$filters = json_decode(request()->query('filters')) ?? ['all' => []];
149149

150150
$eloquent_project = Project::findOrFail((int) $this->project->Id);
151151

152-
return $this->vue('build-tests-page', 'Tests', [
152+
$params = [
153153
'build-id' => $this->build->Id,
154154
'show-test-time-status' => (bool) $eloquent_project->showtesttime,
155155
'project-name' => $eloquent_project->name,
156156
'build-time' => Carbon::parse($this->build->StartTime)->toIso8601String(),
157157
'initial-filters' => $filters,
158158
'pinned-measurements' => $eloquent_project->pinnedTestMeasurements()->orderBy('position')->pluck('name')->toArray(),
159-
]);
159+
];
160+
161+
if ($request->has('onlydelta')) {
162+
$params['only-delta'] = true;
163+
$previousBuildId = $this->build->GetPreviousBuildId();
164+
if ($previousBuildId > 0) {
165+
$params['previous-build-id'] = $previousBuildId;
166+
}
167+
}
168+
169+
return $this->vue('build-tests-page', 'Tests', $params);
160170
}
161171

162172
public function coverage(int $build_id): View

resources/js/angular/views/partials/build.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@
335335
<a class="cdash-link" ng-href="{{ 'builds/' + build.id + '/tests?filters=%7B%22all%22%3A%5B%7B%22eq%22%3A%7B%22status%22%3A%22NOT_RUN%22%7D%7D%5D%7D' }}">
336336
{{::build.test.notrun}}
337337
</a>
338-
<a ng-if="::build.test.nnotrundiffp > 0" class="sup cdash-link" ng-href="viewTest.php?onlydelta&buildid={{::build.id}}{{::cdash.testfilters}}">
338+
<a ng-if="::build.test.nnotrundiffp > 0" class="sup cdash-link" ng-href="{{ 'builds/' + build.id + '/tests?onlydelta&filters=%7B%22all%22%3A%5B%7B%22eq%22%3A%7B%22status%22%3A%22NOT_RUN%22%7D%7D%5D%7D' }}">
339339
+{{::build.test.nnotrundiffp}}
340340
</a>
341341
<span ng-if="::build.test.nnotrundiffn > 0" class="sub">-{{::build.test.nnotrundiffn}}</span>
@@ -348,7 +348,7 @@
348348
<a class="cdash-link" ng-href="{{'builds/' + build.id + '/tests?filters=%7B%22all%22%3A%5B%7B%22eq%22%3A%7B%22status%22%3A%22FAILED%22%7D%7D%5D%7D' }}">
349349
{{::build.test.fail}}
350350
</a>
351-
<a ng-if="::build.test.nfaildiffp > 0" class="sup cdash-link" ng-href="viewTest.php?onlydelta&buildid={{::build.id}}{{::cdash.testfilters}}">
351+
<a ng-if="::build.test.nfaildiffp > 0" class="sup cdash-link" ng-href="{{'builds/' + build.id + '/tests?onlydelta&filters=%7B%22all%22%3A%5B%7B%22eq%22%3A%7B%22status%22%3A%22FAILED%22%7D%7D%5D%7D' }}">
352352
+{{::build.test.nfaildiffp}}
353353
</a>
354354
<span ng-if="::build.test.nfaildiffn > 0" class="sub">
@@ -363,7 +363,7 @@
363363
<a class="cdash-link" ng-href="{{ 'builds/' + build.id + '/tests?filters=%7B%22all%22%3A%5B%7B%22eq%22%3A%7B%22status%22%3A%22PASSED%22%7D%7D%5D%7D' }}">
364364
{{::build.test.pass}}
365365
</a>
366-
<a ng-if="::build.test.npassdiffp > 0" class="sup cdash-link" ng-href="viewTest.php?onlydelta&buildid={{::build.id}}{{::cdash.testfilters}}">
366+
<a ng-if="::build.test.npassdiffp > 0" class="sup cdash-link" ng-href="{{ 'builds/' + build.id + '/tests?onlydelta&filters=%7B%22all%22%3A%5B%7B%22eq%22%3A%7B%22status%22%3A%22PASSED%22%7D%7D%5D%7D' }}">
367367
+{{::build.test.npassdiffp}}
368368
</a>
369369

@@ -382,7 +382,7 @@
382382
<a class="cdash-link" ng-href="{{ 'builds/' + build.id + '/tests?filters=%7B%22all%22%3A%5B%7B%22eq%22%3A%7B%22timeStatusCategory%22%3A%22FAILED%22%7D%7D%5D%7D' }}">
383383
{{::build.test.timestatus}}
384384
</a>
385-
<a ng-if="::build.test.ntimediffp > 0" class="sup cdash-link" ng-href="viewTest.php?onlydelta&buildid={{::build.id}}{{::cdash.testfilters}}">
385+
<a ng-if="::build.test.ntimediffp > 0" class="sup cdash-link" ng-href="{{ 'builds/' + build.id + '/tests?onlydelta&filters=%7B%22all%22%3A%5B%7B%22eq%22%3A%7B%22timeStatusCategory%22%3A%22FAILED%22%7D%7D%5D%7D' }}">
386386
+{{::build.test.ntimediffp}}
387387
</a>
388388
<span ng-if="::build.test.ntimediffn > 0" class="sub">
@@ -392,7 +392,7 @@
392392

393393
<span ng-if="::build.test.timestatus">
394394
{{::build.test.time}}
395-
<a ng-if="::build.test.ntimediffp > 0" class="sup cdash-link" ng-href="viewTest.php?onlydelta&buildid={{::buildid}}{{::cdash.testfilters}}">
395+
<a ng-if="::build.test.ntimediffp > 0" class="sup cdash-link" ng-href="{{ 'builds/' + build.id + '/tests?onlydelta&filters=%7B%22all%22%3A%5B%7B%22eq%22%3A%7B%22timeStatusCategory%22%3A%22FAILED%22%7D%7D%5D%7D' }}">
396396
+{{::build.test.ntimediffp}}
397397
</a>
398398
<span ng-if="::build.test.ntimediffn > 0" class="sub">

resources/js/vue/components/BuildTestsPage.vue

Lines changed: 149 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,15 @@
1313
:execute-query-link="executeQueryLink"
1414
@change-filters="filters => changedFilters = filters"
1515
/>
16-
<loading-indicator :is-loading="!tests">
16+
<loading-indicator :is-loading="!tests || (onlyDelta && !previousTests && previousBuildId !== null)">
17+
<div
18+
v-if="onlyDelta && tests && filteredTests.length === 0"
19+
class="tw-self-center"
20+
>
21+
No tests with changed state for this build.
22+
</div>
1723
<data-table
24+
v-if="!onlyDelta || filteredTests.length > 0"
1825
:columns="[
1926
...(hasSubProjects ? [{
2027
name: 'subProject',
@@ -67,6 +74,63 @@ import BuildSummaryCard from './shared/BuildSummaryCard.vue';
6774
import BuildSidebar from './shared/BuildSidebar.vue';
6875
import {DateTime} from 'luxon';
6976
77+
const TEST_QUERY = gql`
78+
query(
79+
$buildid: ID,
80+
$filters: BuildTestsFiltersMultiFilterInput,
81+
$measurementFilters: TestTestMeasurementsFiltersMultiFilterInput,
82+
) {
83+
build(id: $buildid) {
84+
id
85+
tests(filters: $filters, first: 1000000) {
86+
edges {
87+
node {
88+
id
89+
name
90+
status
91+
details
92+
runningTime
93+
timeStatusCategory
94+
testMeasurements(filters: $measurementFilters) {
95+
id
96+
name
97+
value
98+
}
99+
}
100+
}
101+
}
102+
children(first: 100000) {
103+
edges {
104+
node {
105+
id
106+
tests(filters: $filters, first: 1000000) {
107+
edges {
108+
node {
109+
id
110+
name
111+
status
112+
details
113+
runningTime
114+
timeStatusCategory
115+
testMeasurements(filters: $measurementFilters) {
116+
id
117+
name
118+
value
119+
}
120+
}
121+
}
122+
}
123+
subProject {
124+
id
125+
name
126+
}
127+
}
128+
}
129+
}
130+
}
131+
}
132+
`;
133+
70134
export default {
71135
name: 'BuildTestsPage',
72136
@@ -109,68 +173,28 @@ export default {
109173
type: Array,
110174
required: true,
111175
},
176+
177+
onlyDelta: {
178+
type: Boolean,
179+
required: false,
180+
default: false,
181+
},
182+
183+
previousBuildId: {
184+
type: [Number, null],
185+
required: false,
186+
default: null,
187+
},
112188
},
113189
114190
apollo: {
115191
tests: {
116-
query: gql`
117-
query(
118-
$buildid: ID,
119-
$filters: BuildTestsFiltersMultiFilterInput,
120-
$measurementFilters: TestTestMeasurementsFiltersMultiFilterInput,
121-
) {
122-
build(id: $buildid) {
123-
id
124-
tests(filters: $filters, first: 1000000) {
125-
edges {
126-
node {
127-
id
128-
name
129-
status
130-
details
131-
runningTime
132-
timeStatusCategory
133-
testMeasurements(filters: $measurementFilters) {
134-
id
135-
name
136-
value
137-
}
138-
}
139-
}
140-
}
141-
children(first: 100000) {
142-
edges {
143-
node {
144-
id
145-
tests(filters: $filters, first: 1000000) {
146-
edges {
147-
node {
148-
id
149-
name
150-
status
151-
details
152-
runningTime
153-
timeStatusCategory
154-
testMeasurements(filters: $measurementFilters) {
155-
id
156-
name
157-
value
158-
}
159-
}
160-
}
161-
}
162-
subProject {
163-
id
164-
name
165-
}
166-
}
167-
}
168-
}
169-
}
170-
}
171-
`,
192+
query: TEST_QUERY,
172193
update: (data) => {
173-
let tests = [...data.build.tests.edges];
194+
let tests = data.build.tests.edges.map((test) => ({
195+
...test,
196+
subProject: '',
197+
}));
174198
data.build.children.edges.forEach((child) => {
175199
tests = tests.concat(
176200
child.node.tests.edges.map((test) => ({
@@ -195,6 +219,41 @@ export default {
195219
};
196220
},
197221
},
222+
223+
previousTests: {
224+
query: TEST_QUERY,
225+
update: (data) => {
226+
let tests = data.build.tests.edges.map((test) => ({
227+
...test,
228+
subProject: '',
229+
}));
230+
data.build.children.edges.forEach((child) => {
231+
tests = tests.concat(
232+
child.node.tests.edges.map((test) => ({
233+
...test,
234+
subProject: child.node.subProject.name,
235+
})),
236+
);
237+
});
238+
return tests;
239+
},
240+
variables() {
241+
return {
242+
buildid: this.previousBuildId,
243+
filters: this.initialFilters,
244+
measurementFilters: {
245+
any: this.pinnedMeasurements.map((name) => ({
246+
eq: {
247+
name: name,
248+
},
249+
})),
250+
},
251+
};
252+
},
253+
skip() {
254+
return !this.onlyDelta || this.previousBuildId === null;
255+
},
256+
},
198257
},
199258
200259
data() {
@@ -204,8 +263,33 @@ export default {
204263
},
205264
206265
computed: {
266+
filteredTests() {
267+
if (!this.onlyDelta) {
268+
return this.tests;
269+
}
270+
271+
if (!this.tests) {
272+
return [];
273+
}
274+
275+
if (!this.previousTests) {
276+
return [];
277+
}
278+
279+
const previousTestsMap = new Map(this.previousTests.map(test => [
280+
`${test.subProject}:${test.node.name}`,
281+
test.node.status,
282+
]));
283+
284+
return this.tests.filter(test => {
285+
const key = `${test.subProject}:${test.node.name}`;
286+
const previousStatus = previousTestsMap.get(key);
287+
return previousStatus !== test.node.status;
288+
});
289+
},
290+
207291
hasSubProjects() {
208-
return this.tests?.some((element) => element.subProject) ?? false;
292+
return this.filteredTests?.some((element) => element.subProject) ?? false;
209293
},
210294
211295
pinnedMeasurementColumns() {
@@ -216,11 +300,15 @@ export default {
216300
},
217301
218302
executeQueryLink() {
219-
return `${window.location.origin}${window.location.pathname}?filters=${encodeURIComponent(JSON.stringify(this.changedFilters))}`;
303+
let link = `${window.location.origin}${window.location.pathname}?filters=${encodeURIComponent(JSON.stringify(this.changedFilters))}`;
304+
if (this.onlyDelta) {
305+
link += '&onlydelta';
306+
}
307+
return link;
220308
},
221309
222310
formattedTestRows() {
223-
return this.tests?.map(edge => {
311+
return this.filteredTests?.map(edge => {
224312
return {
225313
name: {
226314
value: edge.node.name,

0 commit comments

Comments
 (0)