mirror of
https://github.com/godotengine/godot-prs-by-file.git
synced 2025-12-31 21:48:29 +03:00
Add support for multiple branches and track PRs per file
This commit is contained in:
@@ -38,8 +38,8 @@ class DataFetcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleResponseErrors(res) {
|
_handleResponseErrors(queryID, res) {
|
||||||
console.warn(` Failed to get pull requests for '${API_REPOSITORY_ID}'; server responded with ${res.status} ${res.statusText}`);
|
console.warn(` Failed to get data from '${queryID}'; server responded with ${res.status} ${res.statusText}`);
|
||||||
const retry_header = res.headers.get("Retry-After");
|
const retry_header = res.headers.get("Retry-After");
|
||||||
if (retry_header) {
|
if (retry_header) {
|
||||||
console.log(` Retry after: ${retry_header}`);
|
console.log(` Retry after: ${retry_header}`);
|
||||||
@@ -99,7 +99,7 @@ class DataFetcher {
|
|||||||
|
|
||||||
const res = await this.fetchGithub(query);
|
const res = await this.fetchGithub(query);
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
this._handleResponseErrors(res);
|
this._handleResponseErrors(API_REPOSITORY_ID, res);
|
||||||
process.exitCode = ExitCodes.RequestFailure;
|
process.exitCode = ExitCodes.RequestFailure;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -201,7 +201,7 @@ class DataFetcher {
|
|||||||
|
|
||||||
const res = await this.fetchGithub(query);
|
const res = await this.fetchGithub(query);
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
this._handleResponseErrors(res);
|
this._handleResponseErrors(API_REPOSITORY_ID, res);
|
||||||
process.exitCode = ExitCodes.RequestFailure;
|
process.exitCode = ExitCodes.RequestFailure;
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -233,7 +233,7 @@ class DataFetcher {
|
|||||||
|
|
||||||
const res = await this.fetchGithubRest(query);
|
const res = await this.fetchGithubRest(query);
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
this._handleResponseErrors(res);
|
this._handleResponseErrors(query, res);
|
||||||
process.exitCode = ExitCodes.RequestFailure;
|
process.exitCode = ExitCodes.RequestFailure;
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -259,7 +259,10 @@ class DataProcessor {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.authors = {};
|
this.authors = {};
|
||||||
this.pulls = [];
|
this.pulls = [];
|
||||||
this.files = [];
|
this.branches = [];
|
||||||
|
this.files = {};
|
||||||
|
|
||||||
|
this._pullsByFile = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
_explainFileType(type) {
|
_explainFileType(type) {
|
||||||
@@ -299,6 +302,11 @@ class DataProcessor {
|
|||||||
"files": [],
|
"files": [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Store the target branch if it hasn't been stored.
|
||||||
|
if (!this.branches.includes(pr.target_branch)) {
|
||||||
|
this.branches.push(pr.target_branch);
|
||||||
|
}
|
||||||
|
|
||||||
// Compose and link author information.
|
// Compose and link author information.
|
||||||
const author = {
|
const author = {
|
||||||
"id": "",
|
"id": "",
|
||||||
@@ -360,6 +368,17 @@ class DataProcessor {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.pulls.push(pr);
|
this.pulls.push(pr);
|
||||||
|
|
||||||
|
// Cache the pull information for every file that it includes.
|
||||||
|
if (typeof this._pullsByFile[pr.target_branch] === "undefined") {
|
||||||
|
this._pullsByFile[pr.target_branch] = {};
|
||||||
|
}
|
||||||
|
pr.files.forEach((file) => {
|
||||||
|
if (typeof this._pullsByFile[pr.target_branch][file.path] === "undefined") {
|
||||||
|
this._pullsByFile[pr.target_branch][file.path] = [];
|
||||||
|
}
|
||||||
|
this._pullsByFile[pr.target_branch][file.path].push(pr.public_id);
|
||||||
|
})
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(" Error parsing pull request data: " + err);
|
console.error(" Error parsing pull request data: " + err);
|
||||||
@@ -367,23 +386,42 @@ class DataProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processFiles(filesRaw) {
|
processFiles(targetBranch, filesRaw) {
|
||||||
try {
|
try {
|
||||||
|
this.files[targetBranch] = [];
|
||||||
|
|
||||||
filesRaw.forEach((item) => {
|
filesRaw.forEach((item) => {
|
||||||
let file = {
|
let file = {
|
||||||
"type": this._explainFileType(item.type),
|
"type": this._explainFileType(item.type),
|
||||||
"name": item.path.split("/").pop(),
|
"name": item.path.split("/").pop(),
|
||||||
"path": item.path,
|
"path": item.path,
|
||||||
"parent": "",
|
"parent": "",
|
||||||
|
"pulls": [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Store the parent path for future reference.
|
||||||
let parentPath = item.path.split("/");
|
let parentPath = item.path.split("/");
|
||||||
parentPath.pop();
|
parentPath.pop();
|
||||||
if (parentPath.length > 0) {
|
if (parentPath.length > 0) {
|
||||||
file.parent = parentPath.join("/");
|
file.parent = parentPath.join("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.files.push(file);
|
// Fetch the PRs touching this file or files in this folder from the cache.
|
||||||
|
if (typeof this._pullsByFile[targetBranch] !== "undefined") {
|
||||||
|
for (let filePath in this._pullsByFile[targetBranch]) {
|
||||||
|
if (filePath !== file.path && filePath.indexOf(file.path + "/") < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._pullsByFile[targetBranch][filePath].forEach((pullNumber) => {
|
||||||
|
if (!file.pulls.includes(pullNumber)) {
|
||||||
|
file.pulls.push(pullNumber);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.files[targetBranch].push(file);
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(" Error parsing repository file system: " + err);
|
console.error(" Error parsing repository file system: " + err);
|
||||||
@@ -415,24 +453,26 @@ async function main() {
|
|||||||
await dataFetcher.checkRates();
|
await dataFetcher.checkRates();
|
||||||
checkForExit();
|
checkForExit();
|
||||||
|
|
||||||
// console.log("[*] Fetching pull request data from GitHub.");
|
console.log("[*] Fetching pull request data from GitHub.");
|
||||||
// // Pages are starting with 1 for better presentation.
|
// Pages are starting with 1 for better presentation.
|
||||||
// let page = 1;
|
let page = 1;
|
||||||
// while (page <= page_count) {
|
while (page <= page_count) {
|
||||||
// const pullsRaw = await dataFetcher.fetchPulls(page);
|
const pullsRaw = await dataFetcher.fetchPulls(page);
|
||||||
// dataProcessor.processPulls(pullsRaw);
|
dataProcessor.processPulls(pullsRaw);
|
||||||
// checkForExit();
|
checkForExit();
|
||||||
// page++;
|
page++;
|
||||||
|
|
||||||
// // Wait for a bit before proceeding to avoid hitting the secondary rate limit in GitHub API.
|
// Wait for a bit before proceeding to avoid hitting the secondary rate limit in GitHub API.
|
||||||
// // See https://docs.github.com/en/rest/guides/best-practices-for-integrators#dealing-with-secondary-rate-limits.
|
// See https://docs.github.com/en/rest/guides/best-practices-for-integrators#dealing-with-secondary-rate-limits.
|
||||||
// await delay(1500);
|
await delay(1500);
|
||||||
// }
|
}
|
||||||
|
|
||||||
console.log("[*] Fetching repository file system from GitHub.");
|
console.log("[*] Fetching repository file system from GitHub.");
|
||||||
const filesRaw = await dataFetcher.fetchFiles("master");
|
for (let branch of dataProcessor.branches) {
|
||||||
dataProcessor.processFiles(filesRaw);
|
const filesRaw = await dataFetcher.fetchFiles(branch);
|
||||||
checkForExit();
|
dataProcessor.processFiles(branch, filesRaw);
|
||||||
|
checkForExit();
|
||||||
|
}
|
||||||
|
|
||||||
console.log("[*] Checking the rate limits after.")
|
console.log("[*] Checking the rate limits after.")
|
||||||
await dataFetcher.checkRates();
|
await dataFetcher.checkRates();
|
||||||
@@ -443,6 +483,7 @@ async function main() {
|
|||||||
"generated_at": Date.now(),
|
"generated_at": Date.now(),
|
||||||
"authors": dataProcessor.authors,
|
"authors": dataProcessor.authors,
|
||||||
"pulls": dataProcessor.pulls,
|
"pulls": dataProcessor.pulls,
|
||||||
|
"branches": dataProcessor.branches,
|
||||||
"files": dataProcessor.files,
|
"files": dataProcessor.files,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -46,8 +46,12 @@ export default class FileList extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property({ type: Array }) branches = [];
|
||||||
@property({ type: Object }) files = {};
|
@property({ type: Object }) files = {};
|
||||||
@property({ type: Array }) selected = [];
|
|
||||||
|
@property({ type: String }) selectedRepository = "godotengine/godot";
|
||||||
|
@property({ type: String }) selectedBranch = "master";
|
||||||
|
@property({ type: Array }) selectedFolders = [];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -58,34 +62,34 @@ export default class FileList extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const entryIndex = this.selected.indexOf(entryPath);
|
const entryIndex = this.selectedFolders.indexOf(entryPath);
|
||||||
if (entryIndex >= 0) {
|
if (entryIndex >= 0) {
|
||||||
this.selected.splice(entryIndex, 1);
|
this.selectedFolders.splice(entryIndex, 1);
|
||||||
} else {
|
} else {
|
||||||
this.selected.push(entryPath);
|
this.selectedFolders.push(entryPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFolder(levelEntries) {
|
renderFolder(branchFiles, folderFiles) {
|
||||||
return html`
|
return html`
|
||||||
<div class="file-list-folder">
|
<div class="file-list-folder">
|
||||||
${(levelEntries.length > 0) ?
|
${(folderFiles.length > 0) ?
|
||||||
levelEntries.map((item) => {
|
folderFiles.map((item) => {
|
||||||
return html`
|
return html`
|
||||||
<div>
|
<div>
|
||||||
<gr-file-item
|
<gr-file-item
|
||||||
.path="${item.path}"
|
.path="${item.path}"
|
||||||
.name="${item.name}"
|
.name="${item.name}"
|
||||||
.type="${item.type}"
|
.type="${item.type}"
|
||||||
.pull_count="${item.pull_count}"
|
.pull_count="${item.pulls.length}"
|
||||||
?active="${this.selected.includes(item.path)}"
|
?active="${this.selectedFolders.includes(item.path)}"
|
||||||
@click="${this._onItemClicked.bind(this, item.type, item.path)}"
|
@click="${this._onItemClicked.bind(this, item.type, item.path)}"
|
||||||
></gr-file-item>
|
></gr-file-item>
|
||||||
|
|
||||||
${(this.selected.includes(item.path)) ?
|
${(this.selectedFolders.includes(item.path)) ?
|
||||||
this.renderFolder(this.files[item.path] || []) : null
|
this.renderFolder(branchFiles, branchFiles[item.path] || []) : null
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -98,16 +102,17 @@ export default class FileList extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const topLevel = this.files[""] || [];
|
const branchFiles = this.files[this.selectedBranch];
|
||||||
|
const topLevel = branchFiles[""] || [];
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="file-list">
|
<div class="file-list">
|
||||||
<gr-root-item
|
<gr-root-item
|
||||||
.repository="${"godotengine/godot"}"
|
.repository="${this.selectedRepository}"
|
||||||
.branch="${"master"}"
|
.branch="${this.selectedBranch}"
|
||||||
></gr-root-item>
|
></gr-root-item>
|
||||||
|
|
||||||
${this.renderFolder(topLevel)}
|
${this.renderFolder(branchFiles, topLevel)}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export default class EntryComponent extends LitElement {
|
|||||||
this._isLoading = true;
|
this._isLoading = true;
|
||||||
this._generatedAt = null;
|
this._generatedAt = null;
|
||||||
|
|
||||||
|
this._branches = [];
|
||||||
this._files = {};
|
this._files = {};
|
||||||
|
|
||||||
this._requestData();
|
this._requestData();
|
||||||
@@ -56,37 +57,49 @@ export default class EntryComponent extends LitElement {
|
|||||||
if (data) {
|
if (data) {
|
||||||
this._generatedAt = data.generated_at;
|
this._generatedAt = data.generated_at;
|
||||||
|
|
||||||
data.files.forEach((file) => {
|
data.branches.forEach((branch) => {
|
||||||
if (file.type === "file" || file.type === "folder") {
|
if (typeof data.files[branch] === "undefined") {
|
||||||
if (typeof this._files[file.parent] === "undefined") {
|
return;
|
||||||
this._files[file.parent] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
this._files[file.parent].push(file);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
for (let folderName in this._files) {
|
this._branches.push(branch);
|
||||||
this._files[folderName].sort((a, b) => {
|
const branchFiles = {};
|
||||||
if (a.type === "folder" && b.type !== "folder") {
|
|
||||||
return -1;
|
data.files[branch].forEach((file) => {
|
||||||
}
|
if (file.type === "file" || file.type === "folder") {
|
||||||
if (b.type === "folder" && a.type !== "folder") {
|
if (typeof branchFiles[file.parent] === "undefined") {
|
||||||
return 1;
|
branchFiles[file.parent] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const a_name = a.path.toLowerCase();
|
branchFiles[file.parent].push(file);
|
||||||
const b_name = b.path.toLowerCase();
|
}
|
||||||
|
|
||||||
if (a_name > b_name) return 1;
|
|
||||||
if (a_name < b_name) return -1;
|
|
||||||
return 0;
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
for (let folderName in branchFiles) {
|
||||||
|
branchFiles[folderName].sort((a, b) => {
|
||||||
|
if (a.type === "folder" && b.type !== "folder") {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (b.type === "folder" && a.type !== "folder") {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const a_name = a.path.toLowerCase();
|
||||||
|
const b_name = b.path.toLowerCase();
|
||||||
|
|
||||||
|
if (a_name > b_name) return 1;
|
||||||
|
if (a_name < b_name) return -1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this._files[branch] = branchFiles;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this._generatedAt = null;
|
this._generatedAt = null;
|
||||||
|
|
||||||
this._files = [];
|
this._branches = [];
|
||||||
|
this._files = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
this._isLoading = false;
|
this._isLoading = false;
|
||||||
@@ -104,6 +117,7 @@ export default class EntryComponent extends LitElement {
|
|||||||
` : html`
|
` : html`
|
||||||
<div class="files">
|
<div class="files">
|
||||||
<gr-file-list
|
<gr-file-list
|
||||||
|
.branches="${this._branches}"
|
||||||
.files="${this._files}"
|
.files="${this._files}"
|
||||||
></gr-file-list>
|
></gr-file-list>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user