Add support for multiple branches and track PRs per file

This commit is contained in:
Yuri Sizov
2023-03-04 15:58:29 +01:00
parent a22a1ec245
commit 33165dcf5b
3 changed files with 122 additions and 62 deletions

View File

@@ -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 {

View File

@@ -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>
`; `;
} }

View File

@@ -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>