Skip to content

Commit 123348c

Browse files
Overhaul build files page (#2921)
This PR completely revamps the build files page, using Vue to render the page based on new fields in the GraphQL API. Before: ![image](https://github.com/user-attachments/assets/f150c384-ab8c-4977-bd11-c81c36d9e876) After: ![image](https://github.com/user-attachments/assets/07d41f79-04ed-46c8-a0e6-9608bff83a2c)
1 parent 7ee6aaa commit 123348c

File tree

9 files changed

+440
-111
lines changed

9 files changed

+440
-111
lines changed

app/Http/Controllers/BuildController.php

Lines changed: 3 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -759,53 +759,9 @@ private function add_file($file, string $directory, array &$list_of_files): void
759759
public function files(int $build_id): View
760760
{
761761
$this->setBuildById($build_id);
762-
763-
$uploadFilesOrURLs = EloquentBuild::findOrFail($build_id)->uploadedFiles()->get();
764-
765-
$urls = [];
766-
$files = [];
767-
768-
/** @var UploadFile $uploadFileOrURL */
769-
foreach ($uploadFilesOrURLs as $uploadFileOrURL) {
770-
if ($uploadFileOrURL->isurl) {
771-
$urls[] = [
772-
'id' => (int) $uploadFileOrURL->id,
773-
'filename' => htmlspecialchars($uploadFileOrURL->filename),
774-
];
775-
} else {
776-
$filesize = $uploadFileOrURL->filesize;
777-
$ext = 'b';
778-
if ($filesize > 1024) {
779-
$filesize /= 1024;
780-
$ext = 'Kb';
781-
}
782-
if ($filesize > 1024) {
783-
$filesize /= 1024;
784-
$ext = 'Mb';
785-
}
786-
if ($filesize > 1024) {
787-
$filesize /= 1024;
788-
$ext = 'Gb';
789-
}
790-
if ($filesize > 1024) {
791-
$filesize /= 1024;
792-
$ext = 'Tb';
793-
}
794-
$files[] = [
795-
'id' => (int) $uploadFileOrURL->id,
796-
'href' => url("/build/{$build_id}/file/{$uploadFileOrURL->id}"),
797-
'sha1sum' => $uploadFileOrURL->sha1sum,
798-
'filename' => $uploadFileOrURL->filename,
799-
'filesize' => $uploadFileOrURL->filesize,
800-
'filesizedisplay' => round($filesize) . ' ' . $ext,
801-
];
802-
}
803-
}
804-
805-
return $this->view('build.files', 'Files')
806-
->with('build', $this->build)
807-
->with('files', $files)
808-
->with('urls', $urls);
762+
return $this->vue('build-files-page', 'Uploads', [
763+
'build-id' => $build_id,
764+
], true);
809765
}
810766

811767
public function build_file(int $build_id, int $file_id): StreamedResponse

app/Models/UploadFile.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,20 @@ public function file(): File
5151
{
5252
return new File(storage_path("app/upload/{$this->sha1sum}"));
5353
}
54+
55+
/**
56+
* @param Builder<self> $query
57+
*/
58+
public function scopeFiles(Builder $query): void
59+
{
60+
$query->where('isurl', false);
61+
}
62+
63+
/**
64+
* @param Builder<self> $query
65+
*/
66+
public function scopeUrls(Builder $query): void
67+
{
68+
$query->where('isurl', true);
69+
}
5470
}

app/cdash/tests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,8 @@ add_feature_test(/Feature/GraphQL/Mutations/RemoveUserTest)
322322

323323
add_browser_test(/Browser/Pages/UsersPageTest)
324324

325+
add_browser_test(/Browser/Pages/BuildFilesPageTest)
326+
325327
###################################################################################################
326328

327329
set_property(TEST install_2 APPEND PROPERTY DEPENDS

app/cdash/tests/test_uploadfile.php

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,6 @@ public function testVerifyFileSubmission()
4848
}
4949
$this->BuildId = $query[0]['buildid'];
5050

51-
$content = $this->connect("{$this->url}/build/{$this->BuildId}/files");
52-
if (!$content) {
53-
return;
54-
}
55-
56-
$this->assertClickable('http://www.kitware.com/company/about.html');
57-
5851
// Verify content exists on disk
5952
$query = $this->db->query("SELECT id, sha1sum FROM uploadfile WHERE filename='CMakeCache.txt'");
6053
if (count($query) == 0) {

graphql/schema.graphql

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,10 @@ type Build {
489489
commands(
490490
filters: _ @filter
491491
): [BuildCommand!]! @hasMany(type: CONNECTION) @orderBy(column: "id", direction: ASC)
492+
493+
urls: [UploadedUrl!]! @hasMany(relation: "uploadedFiles", type: CONNECTION, scopes: ["urls"]) @orderBy(column: "id", direction: ASC)
494+
495+
files: [UploadedFile!]! @hasMany(relation: "uploadedFiles", type: CONNECTION, scopes: ["files"]) @orderBy(column: "id", direction: ASC)
492496
}
493497

494498

@@ -830,3 +834,24 @@ type Target {
830834
filters: _ @filter
831835
): [Label!]! @belongsToMany(type: CONNECTION) @orderBy(column: "id", direction: DESC)
832836
}
837+
838+
839+
type UploadedUrl @model(class: "App\\Models\\UploadFile") {
840+
"Unique primary key."
841+
id: ID!
842+
843+
href: String! @rename(attribute: "filename")
844+
}
845+
846+
847+
type UploadedFile @model(class: "App\\Models\\UploadFile") {
848+
"Unique primary key. Use /build/<id>/file/<id> to download the file by ID."
849+
id: ID!
850+
851+
name: String! @rename(attribute: "filename")
852+
853+
sha1sum: String!
854+
855+
"File size in bytes."
856+
size: Int! @rename(attribute: "filesize")
857+
}

resources/js/vue/app.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const ProjectSitesPage = Vue.defineAsyncComponent(() => import('./components/Pro
3333
const SitesIdPage = Vue.defineAsyncComponent(() => import('./components/SitesIdPage.vue'));
3434
const ProjectMembersPage = Vue.defineAsyncComponent(() => import('./components/ProjectMembersPage.vue'));
3535
const UsersPage = Vue.defineAsyncComponent(() => import('./components/UsersPage.vue'));
36+
const BuildFilesPage = Vue.defineAsyncComponent(() => import('./components/BuildFilesPage.vue'));
3637

3738
const cdash_components = {
3839
BuildConfigure,
@@ -54,6 +55,7 @@ const cdash_components = {
5455
SitesIdPage,
5556
ProjectMembersPage,
5657
UsersPage,
58+
BuildFilesPage,
5759
};
5860

5961
/**
@@ -102,6 +104,8 @@ const apolloClient = new ApolloClient({
102104
fields: {
103105
tests: relayStylePagination(),
104106
labels: relayStylePagination(),
107+
files: relayStylePagination(),
108+
urls: relayStylePagination(),
105109
},
106110
},
107111
Site: {
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
<template>
2+
<div class="tw-flex tw-flex-col tw-w-full tw-gap-4">
3+
<BuildSummaryCard :build-id="buildId" />
4+
5+
<div
6+
v-if="urls && files && urls.edges.length === 0 && files.edges.length === 0"
7+
data-test="no-urls-or-files-message"
8+
>
9+
No URLs or files were uploaded for this build.
10+
</div>
11+
12+
<loading-indicator :is-loading="!urls">
13+
<data-table
14+
v-if="urls.edges.length > 0"
15+
:column-groups="[
16+
{
17+
displayName: 'URLs',
18+
width: 100,
19+
}
20+
]"
21+
:columns="[
22+
{
23+
name: 'url',
24+
displayName: 'URL',
25+
},
26+
]"
27+
:rows="formattedUrlRows"
28+
:full-width="true"
29+
test-id="urls-table"
30+
/>
31+
</loading-indicator>
32+
33+
<loading-indicator :is-loading="!files">
34+
<data-table
35+
v-if="files.edges.length > 0"
36+
:column-groups="[
37+
{
38+
displayName: 'Files',
39+
width: 100,
40+
}
41+
]"
42+
:columns="[
43+
{
44+
name: 'name',
45+
displayName: 'Name',
46+
},
47+
{
48+
name: 'size',
49+
displayName: 'Size',
50+
},
51+
{
52+
name: 'hash',
53+
displayName: 'SHA-1',
54+
},
55+
]"
56+
:rows="formattedFileRows"
57+
:full-width="true"
58+
test-id="files-table"
59+
/>
60+
</loading-indicator>
61+
</div>
62+
</template>
63+
64+
<script>
65+
import BuildSummaryCard from './shared/BuildSummaryCard.vue';
66+
import DataTable from './shared/DataTable.vue';
67+
import LoadingIndicator from './shared/LoadingIndicator.vue';
68+
import gql from 'graphql-tag';
69+
70+
export default {
71+
components: {LoadingIndicator, DataTable, BuildSummaryCard},
72+
73+
props: {
74+
buildId: {
75+
type: Number,
76+
required: true,
77+
},
78+
},
79+
80+
apollo: {
81+
urls: {
82+
query: gql`
83+
query($buildId: ID!, $after: String) {
84+
build(id: $buildId) {
85+
id
86+
urls(after: $after) {
87+
edges {
88+
node {
89+
id
90+
href
91+
}
92+
}
93+
pageInfo {
94+
hasNextPage
95+
hasPreviousPage
96+
startCursor
97+
endCursor
98+
}
99+
}
100+
}
101+
}
102+
`,
103+
update: data => data?.build?.urls,
104+
variables() {
105+
return {
106+
buildId: this.buildId,
107+
};
108+
},
109+
result({data}) {
110+
if (data && data.build.urls.pageInfo.hasNextPage) {
111+
this.$apollo.queries.urls.fetchMore({
112+
variables: {
113+
buildId: this.buildId,
114+
after: data.build.urls.pageInfo.endCursor,
115+
},
116+
});
117+
}
118+
},
119+
},
120+
121+
files: {
122+
query: gql`
123+
query($buildId: ID!, $after: String) {
124+
build(id: $buildId) {
125+
id
126+
files(after: $after) {
127+
edges {
128+
node {
129+
id
130+
name
131+
size
132+
sha1sum
133+
}
134+
}
135+
pageInfo {
136+
hasNextPage
137+
hasPreviousPage
138+
startCursor
139+
endCursor
140+
}
141+
}
142+
}
143+
}
144+
`,
145+
update: data => data?.build?.files,
146+
variables() {
147+
return {
148+
buildId: this.buildId,
149+
};
150+
},
151+
result({data}) {
152+
if (data && data.build.files.pageInfo.hasNextPage) {
153+
this.$apollo.queries.files.fetchMore({
154+
variables: {
155+
buildId: this.buildId,
156+
after: data.build.files.pageInfo.endCursor,
157+
},
158+
});
159+
}
160+
},
161+
},
162+
},
163+
164+
computed: {
165+
formattedUrlRows() {
166+
return this.urls.edges?.map(edge => {
167+
return {
168+
url: {
169+
value: edge.node.href,
170+
text: edge.node.href,
171+
href: edge.node.href,
172+
},
173+
};
174+
});
175+
},
176+
177+
formattedFileRows() {
178+
return this.files.edges?.map(edge => {
179+
return {
180+
name: {
181+
value: edge.node.name,
182+
text: edge.node.name,
183+
href: `${this.$baseURL}/build/${this.buildId}/file/${edge.node.id}`,
184+
},
185+
size: {
186+
value: edge.node.size,
187+
text: this.humanReadableFileSize(edge.node.size),
188+
},
189+
hash: {
190+
value: edge.node.sha1sum,
191+
text: edge.node.sha1sum,
192+
},
193+
};
194+
});
195+
},
196+
},
197+
198+
methods: {
199+
humanReadableFileSize(bytes) {
200+
if (bytes < 1024) {
201+
return `${bytes} bytes`;
202+
}
203+
else if (bytes < 1024 * 1024) {
204+
return `${(bytes / 1024).toFixed(2)} KiB`;
205+
}
206+
else if (bytes < 1024 * 1024 * 1024) {
207+
return `${(bytes / (1024 * 1024)).toFixed(2)} MiB`;
208+
}
209+
else if (bytes < 1024 * 1024 * 1024 * 1024) {
210+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
211+
}
212+
else {
213+
return `${(bytes / (1024 * 1024 * 1024 * 1024)).toFixed(2)} TiB`;
214+
}
215+
},
216+
},
217+
};
218+
</script>

0 commit comments

Comments
 (0)