Skip to content

Commit ae1a8a2

Browse files
Revamp build update page (Kitware#3635)
This PR overhauls the build update page, completely redesigning the UI while switching the underlying data source to GraphQL. Another improvement is the addition of browser tests for this page, which was previously untested. Along the way, I fixed the comparison links reported broken in Kitware#3625. Closes Kitware#3625. | Before | After | | --- | --- | | <img width="2832" height="1480" alt="image" src="https://github.com/user-attachments/assets/1cd46b74-2956-4957-adce-6c85277af046" /> | <img width="2834" height="1482" alt="image" src="https://github.com/user-attachments/assets/552a0c68-ade0-46e1-b56f-a564b180ea9f" /> |
1 parent 6b022a9 commit ae1a8a2

14 files changed

Lines changed: 614 additions & 99 deletions

File tree

app/Http/Controllers/BuildController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public function update(int $build_id): View
116116
{
117117
$this->setBuildById($build_id);
118118

119-
return $this->vue('build-update', 'Files Updated', [
119+
return $this->vue('build-update-page', 'Files Updated', [
120120
'build-id' => $this->build->Id,
121121
'repository-type' => $this->project->CvsViewerType,
122122
'repository-url' => $this->project->CvsUrl,

app/Models/BuildUpdate.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
namespace App\Models;
44

5+
use Database\Factories\BuildUpdateFactory;
56
use Illuminate\Database\Eloquent\Builder;
67
use Illuminate\Database\Eloquent\Casts\Attribute;
8+
use Illuminate\Database\Eloquent\Factories\HasFactory;
79
use Illuminate\Database\Eloquent\Model;
810
use Illuminate\Database\Eloquent\Relations\HasMany;
911
use Illuminate\Support\Carbon;
@@ -26,6 +28,9 @@
2628
*/
2729
class BuildUpdate extends Model
2830
{
31+
/** @use HasFactory<BuildUpdateFactory> */
32+
use HasFactory;
33+
2934
protected $table = 'buildupdate';
3035

3136
public $timestamps = false;

app/Models/BuildUpdateFile.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace App\Models;
44

5+
use Database\Factories\BuildUpdateFileFactory;
56
use Illuminate\Database\Eloquent\Builder;
7+
use Illuminate\Database\Eloquent\Factories\HasFactory;
68
use Illuminate\Database\Eloquent\Model;
79
use Illuminate\Support\Carbon;
810

@@ -24,6 +26,9 @@
2426
*/
2527
class BuildUpdateFile extends Model
2628
{
29+
/** @use HasFactory<BuildUpdateFileFactory> */
30+
use HasFactory;
31+
2732
protected $table = 'updatefile';
2833

2934
public $timestamps = false;

app/cdash/tests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,8 @@ add_browser_test(/Browser/Pages/BuildSidebarComponentTest)
428428

429429
add_browser_test(/Browser/Pages/ProjectSettingsPageTest)
430430

431+
add_browser_test(/Browser/Pages/BuildUpdatePageTest)
432+
431433
add_php_test(image)
432434
set_tests_properties(image PROPERTIES DEPENDS /CDash/XmlHandler/UpdateHandler)
433435

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Database\Factories;
4+
5+
use App\Models\BuildUpdate;
6+
use Illuminate\Database\Eloquent\Factories\Factory;
7+
use Illuminate\Support\Carbon;
8+
use Illuminate\Support\Str;
9+
10+
/**
11+
* @extends Factory<BuildUpdate>
12+
*/
13+
class BuildUpdateFactory extends Factory
14+
{
15+
/**
16+
* Define the model's default state.
17+
*
18+
* @return array<string, mixed>
19+
*/
20+
public function definition(): array
21+
{
22+
return [
23+
'starttime' => Carbon::createFromInterface(fake()->dateTime()),
24+
'endtime' => Carbon::createFromInterface(fake()->dateTime()),
25+
'command' => fake()->text(),
26+
'type' => 'GIT',
27+
'status' => '',
28+
'nfiles' => fake()->numberBetween(0, 1000),
29+
'warnings' => fake()->numberBetween(0, 1000),
30+
'revision' => fake()->sha1(),
31+
'priorrevision' => fake()->sha1(),
32+
'path' => Str::uuid()->toString(),
33+
];
34+
}
35+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Database\Factories;
4+
5+
use App\Models\BuildUpdateFile;
6+
use Illuminate\Database\Eloquent\Factories\Factory;
7+
use Illuminate\Support\Carbon;
8+
use Illuminate\Support\Str;
9+
10+
/**
11+
* @extends Factory<BuildUpdateFile>
12+
*/
13+
class BuildUpdateFileFactory extends Factory
14+
{
15+
/**
16+
* Define the model's default state.
17+
*
18+
* @return array<string, mixed>
19+
*/
20+
public function definition(): array
21+
{
22+
return [
23+
'filename' => Str::uuid()->toString(),
24+
'checkindate' => Carbon::createFromInterface(fake()->dateTime()),
25+
'author' => fake()->name(),
26+
'email' => fake()->safeEmail(),
27+
'committer' => fake()->name(),
28+
'committeremail' => fake()->safeEmail(),
29+
'log' => fake()->text(),
30+
'revision' => fake()->sha1(),
31+
'priorrevision' => fake()->sha1(),
32+
'status' => 'UPDATED',
33+
];
34+
}
35+
}

graphql/schema.graphql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1436,6 +1436,8 @@ type UpdateFile @model(class: "App\\Models\\BuildUpdateFile") {
14361436

14371437
committerEmail: String @rename(attribute: "committeremail") @filterable
14381438

1439+
checkinDate: DateTimeTz @rename(attribute: "checkindate")
1440+
14391441
log: String
14401442

14411443
revision: String @filterable

resources/js/vue/app.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const app = Vue.createApp({
1212
BuildConfigure: Vue.defineAsyncComponent(() => import('./components/BuildConfigure')),
1313
BuildNotesPage: Vue.defineAsyncComponent(() => import('./components/BuildNotesPage.vue')),
1414
BuildSummary: Vue.defineAsyncComponent(() => import('./components/BuildSummary')),
15-
BuildUpdate: Vue.defineAsyncComponent(() => import('./components/BuildUpdate')),
15+
BuildUpdatePage: Vue.defineAsyncComponent(() => import('./components/BuildUpdatePage.vue')),
1616
ManageAuthTokens: Vue.defineAsyncComponent(() => import('./components/ManageAuthTokens.vue')),
1717
ManageMeasurements: Vue.defineAsyncComponent(() => import('./components/ManageMeasurements')),
1818
Monitor: Vue.defineAsyncComponent(() => import('./components/Monitor')),
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<template>
2+
<div
3+
class="tw-p-2 tw-border tw-rounded-md tw-flex tw-flex-col tw-gap-2"
4+
data-test="commit-card"
5+
>
6+
<div>
7+
<span class="tw-font-bold">Revision </span>
8+
<a
9+
v-if="repository"
10+
:href="repository.getCommitUrl(revision)"
11+
class="tw-font-mono tw-link tw-link-hover tw-link-info"
12+
>{{ revision }}</a>
13+
<span
14+
v-else
15+
class="tw-font-mono"
16+
>{{ revision }}</span>
17+
authored by
18+
<span class="tw-italic">{{ authorName }}</span>,
19+
committed by
20+
<span class="tw-italic">{{ committerName }}</span>
21+
</div>
22+
23+
<div class="tw-flex tw-flex-row tw-gap-2">
24+
<CodeBox
25+
:text="commitMessage"
26+
class="tw-w-1/2"
27+
/>
28+
<ul class="tw-menu tw-menu-xs tw-bg-base-200 tw-rounded-lg tw-w-1/2">
29+
<commit-file-tree-node
30+
v-for="node in fileTree"
31+
:key="node.name"
32+
:node="node"
33+
:repository="repository"
34+
/>
35+
</ul>
36+
</div>
37+
</div>
38+
</template>
39+
40+
<script>
41+
42+
import {Repository} from '../shared/RepositoryIntegrations';
43+
import CodeBox from '../shared/CodeBox.vue';
44+
import CommitFileTreeNode from './CommitFileTreeNode.vue';
45+
46+
export default {
47+
components: {CodeBox, CommitFileTreeNode},
48+
props: {
49+
commitFiles: {
50+
type: Array,
51+
required: true,
52+
},
53+
54+
repository: {
55+
type: [Repository, null],
56+
required: true,
57+
},
58+
},
59+
60+
computed: {
61+
revision() {
62+
return this.commitFiles[0].revision;
63+
},
64+
65+
commitMessage() {
66+
return this.commitFiles[0].log;
67+
},
68+
69+
authorName() {
70+
return this.commitFiles[0].authorName ?? 'unknown';
71+
},
72+
73+
committerName() {
74+
return this.commitFiles[0].committerName ?? 'unknown';
75+
},
76+
77+
fileTree() {
78+
const tree = [];
79+
80+
this.commitFiles.forEach(file => {
81+
const parts = file.fileName.split('/');
82+
let currentLevel = tree;
83+
84+
parts.forEach((part, index) => {
85+
const existingPath = currentLevel.find(item => item.name === part);
86+
87+
if (existingPath) {
88+
currentLevel = existingPath.children;
89+
}
90+
else {
91+
const newNode = {
92+
name: part,
93+
};
94+
95+
if (index < parts.length - 1) {
96+
newNode.children = [];
97+
}
98+
else {
99+
newNode.file = file;
100+
}
101+
102+
currentLevel.push(newNode);
103+
currentLevel = newNode.children;
104+
}
105+
});
106+
});
107+
108+
return tree;
109+
},
110+
},
111+
};
112+
</script>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<template>
2+
<li v-if="!node.children">
3+
<span class="tw-flex tw-items-center tw-justify-between tw-w-full">
4+
<span
5+
class="tw-truncate"
6+
:title="node.name"
7+
>
8+
<font-awesome-icon :icon="FA.faFile" />
9+
{{ node.name }}
10+
</span>
11+
<span
12+
v-if="node.file?.status && node.file?.status !== 'UPDATED'"
13+
class="tw-badge tw-badge-sm tw-border"
14+
:class="node.file?.status === 'CONFLICTING' ? 'tw-badge-error tw-border-error' : 'tw-badge-ghost tw-border-gray-300'"
15+
>
16+
{{ node.file.status }}
17+
</span>
18+
</span>
19+
</li>
20+
<li v-else>
21+
<details open>
22+
<summary
23+
class="tw-truncate"
24+
:title="node.name"
25+
>
26+
<font-awesome-icon :icon="FA.faFolderOpen" />
27+
{{ node.name }}
28+
</summary>
29+
<ul>
30+
<commit-file-tree-node
31+
v-for="child in node.children"
32+
:key="child.name"
33+
:node="child"
34+
:repository="repository"
35+
/>
36+
</ul>
37+
</details>
38+
</li>
39+
</template>
40+
41+
<script>
42+
import {Repository} from '../shared/RepositoryIntegrations';
43+
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome';
44+
import {faFolderOpen, faFile} from '@fortawesome/free-regular-svg-icons';
45+
46+
export default {
47+
name: 'CommitFileTreeNode',
48+
components: {FontAwesomeIcon},
49+
props: {
50+
node: {
51+
type: Object,
52+
required: true,
53+
},
54+
repository: {
55+
type: [Repository, null],
56+
required: true,
57+
},
58+
},
59+
computed: {
60+
FA() {
61+
return {
62+
faFolderOpen,
63+
faFile,
64+
};
65+
},
66+
},
67+
};
68+
</script>

0 commit comments

Comments
 (0)