Add ability to filter files by PR

This commit is contained in:
Yuri Sizov
2023-03-04 19:09:24 +01:00
parent 29ef016116
commit f4ee3b522b
7 changed files with 216 additions and 125 deletions

View File

@@ -167,16 +167,6 @@ class DataFetcher {
title title
url url
} }
labels (first: 100) {
edges {
node {
id
name
color
}
}
}
files (first: 100) { files (first: 100) {
edges { edges {
@@ -295,8 +285,6 @@ class DataProcessor {
"updated_at": item.updatedAt, "updated_at": item.updatedAt,
"target_branch": item.baseRef.name, "target_branch": item.baseRef.name,
"labels": [],
"milestone": null, "milestone": null,
"files": [], "files": [],
@@ -338,21 +326,6 @@ class DataProcessor {
}; };
} }
// Add labels, if available.
let labels = mapNodes(item.labels);
labels.forEach((labelItem) => {
pr.labels.push({
"id": labelItem.id,
"name": labelItem.name,
"color": "#" + labelItem.color,
});
});
pr.labels.sort((a, b) => {
if (a.name > b.name) return 1;
if (a.name < b.name) return -1;
return 0;
});
// Add changed files. // Add changed files.
let files = mapNodes(item.files); let files = mapNodes(item.files);
const visitedPaths = []; const visitedPaths = [];

View File

@@ -53,6 +53,7 @@ export default class FileList extends LitElement {
@property({ type: String }) selectedBranch = "master"; @property({ type: String }) selectedBranch = "master";
@property({ type: String }) selectedPath = ""; @property({ type: String }) selectedPath = "";
@property({ type: Array }) selectedFolders = []; @property({ type: Array }) selectedFolders = [];
@property({ type: String }) filteredPull = "";
constructor() { constructor() {
super(); super();
@@ -86,6 +87,10 @@ export default class FileList extends LitElement {
<div class="file-list-folder"> <div class="file-list-folder">
${(folderFiles.length > 0) ? ${(folderFiles.length > 0) ?
folderFiles.map((item) => { folderFiles.map((item) => {
if (this.filteredPull !== "" && !item.pulls.includes(parseInt(this.filteredPull, 10))) {
return html``;
}
return html` return html`
<div> <div>
<gr-file-item <gr-file-item

View File

@@ -0,0 +1,107 @@
import { LitElement, html, css, customElement, property } from 'lit-element';
const GH_PULL_URL_RE = RegExp("^https://github.com/([a-z0-9-_]+/[a-z0-9-_]+)/pull/([0-9]+)$", "i");
const GH_PULL_REF_RE = RegExp("^([a-z0-9-_]+/[a-z0-9-_]+)?#([0-9]+)$", "i");
const GH_PULL_NUMBER_RE = RegExp("^[#]?([0-9]+)$", "i");
@customElement('gr-pull-filter')
export default class PullFilter extends LitElement {
static get styles() {
return css`
/** Colors and variables **/
:host {
}
@media (prefers-color-scheme: dark) {
:host {
}
}
/** Component styling **/
:host {
}
:host .pull-filter {
display: flex;
gap: 12px;
align-items: center;
justify-content: center;
margin-top: 24px;
}
:host .pull-filter-value {
background: var(--g-background-extra2-color);
border: 2px solid var(--g-background-extra-color);
border-radius: 4px 4px;
color: var(--g-font-color);
font-size: 16px;
flex-grow: 1;
padding: 8px 12px;
}
:host .pull-filter-resolved {
font-weight: 600;
padding: 0 8px;
min-width: 60px;
}
@media only screen and (max-width: 900px) {
}
`;
}
constructor() {
super();
this._resolvedValue = "";
}
_parsePullNumber(value) {
let match = value.match(GH_PULL_URL_RE);
if (match) {
return match[2];
}
match = value.match(GH_PULL_REF_RE);
if (match) {
return match[2];
}
match = value.match(GH_PULL_NUMBER_RE);
if (match) {
return match[1];
}
return "00000";
}
_filterChanged(event) {
this._resolvedValue = "";
const rawValue = event.target.value.trim();
if (rawValue !== "") {
this._resolvedValue = this._parsePullNumber(rawValue);
}
this.dispatchEvent(greports.util.createEvent("filterchanged", {
"pull": this._resolvedValue,
}));
this.requestUpdate();
}
render(){
return html`
<div class="pull-filter">
<span class="pull-filter-label">Input PR link or number:</span>
<input
class="pull-filter-value"
type="text"
@change="${this._filterChanged.bind(this)}"
/>
<span class="pull-filter-resolved">
${this._resolvedValue}
</span>
</div>
`;
}
}

View File

@@ -7,16 +7,14 @@ export default class PullRequestItem extends LitElement {
/** Colors and variables **/ /** Colors and variables **/
:host { :host {
--pr-border-color: #fcfcfa; --pr-border-color: #fcfcfa;
--draft-font-color: #ffcc31; --star-font-color: #ffcc31;
--draft-background-color: #9db3c0;
--ghost-font-color: #738b99; --ghost-font-color: #738b99;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:host { :host {
--pr-border-color: #0d1117; --pr-border-color: #0d1117;
--draft-font-color: #e0c537; --star-font-color: #e0c537;
--draft-background-color: #1e313c;
--ghost-font-color: #495d68; --ghost-font-color: #495d68;
} }
} }
@@ -48,13 +46,11 @@ export default class PullRequestItem extends LitElement {
word-break: break-word; word-break: break-word;
} }
:host .pr-title-draft { :host .pr-container--draft .pr-title {
background-color: var(--draft-background-color); filter: saturate(0.4);
border-radius: 6px 6px; }
color: var(--draft-font-color); :host .pr-container--draft .pr-title-name {
font-size: 14px; opacity: 0.7;
padding: 1px 6px;
vertical-align: bottom;
} }
:host .pr-meta { :host .pr-meta {
@@ -65,27 +61,6 @@ export default class PullRequestItem extends LitElement {
font-size: 13px; font-size: 13px;
} }
:host .pr-labels {
display: flex;
flex-flow: column wrap;
padding: 4px 0;
max-height: 60px;
}
:host .pr-label {
padding-right: 8px;
}
:host .pr-label-dot {
border-radius: 4px;
box-shadow: rgb(0 0 0 / 28%) 0 0 3px 0;
display: inline-block;
width: 8px;
height: 8px;
}
:host .pr-label-name {
padding-left: 3px;
}
:host .pr-milestone-value { :host .pr-milestone-value {
font-weight: 700; font-weight: 700;
} }
@@ -107,7 +82,7 @@ export default class PullRequestItem extends LitElement {
} }
:host .pr-author-value--hot:before { :host .pr-author-value--hot:before {
content: "★"; content: "★";
color: var(--draft-font-color); color: var(--star-font-color);
} }
:host .pr-author-value--ghost { :host .pr-author-value--ghost {
color: var(--ghost-font-color); color: var(--ghost-font-color);
@@ -137,10 +112,6 @@ export default class PullRequestItem extends LitElement {
:host .pr-meta { :host .pr-meta {
flex-wrap: wrap; flex-wrap: wrap;
} }
:host .pr-labels {
width: 100%;
justify-content: space-between;
}
} }
`; `;
} }
@@ -151,7 +122,6 @@ export default class PullRequestItem extends LitElement {
@property({ type: String, reflect: true }) diff_url = ''; @property({ type: String, reflect: true }) diff_url = '';
@property({ type: String, reflect: true }) patch_url = ''; @property({ type: String, reflect: true }) patch_url = '';
@property({ type: Boolean }) draft = false; @property({ type: Boolean }) draft = false;
@property({ type: Array }) labels = [];
@property({ type: String, reflect: true }) milestone = ''; @property({ type: String, reflect: true }) milestone = '';
@property({ type: String, reflect: true }) branch = ''; @property({ type: String, reflect: true }) branch = '';
@property({ type: String }) created_at = ''; @property({ type: String }) created_at = '';
@@ -167,46 +137,17 @@ export default class PullRequestItem extends LitElement {
authorClassList.push("pr-author-value--ghost"); authorClassList.push("pr-author-value--ghost");
} }
// Keep it to two columns, but if there isn't enough labels, keep it to one.
let labels_height = Math.ceil(this.labels.length / 2) * 20;
if (labels_height < 60) {
labels_height = 60;
}
return html` return html`
<div class="pr-container"> <div class="pr-container ${(this.draft ? "pr-container--draft" : "")}">
<a <a
class="pr-title" class="pr-title"
href="${this.url}" href="${this.url}"
target="_blank" target="_blank"
> >
${(this.draft ? html`
<span class="pr-title-draft">draft</span>
` : '')}
<span class="pr-title-id">#${this.id}</span> <span class="pr-title-name">${this.title}</span> <span class="pr-title-id">#${this.id}</span> <span class="pr-title-name">${this.title}</span>
</a> </a>
<div class="pr-meta"> <div class="pr-meta">
<div class="pr-labels" style="max-height:${labels_height}px">
${this.labels.map((item) => {
return html`
<span
class="pr-label"
>
<span
class="pr-label-dot"
style="background-color: ${item.color}"
></span>
<span
class="pr-label-name"
>
${item.name}
</span>
</span>
`;
})}
</div>
<div class="pr-milestone"> <div class="pr-milestone">
<div> <div>
<span>milestone: </span> <span>milestone: </span>
@@ -229,6 +170,20 @@ export default class PullRequestItem extends LitElement {
</div> </div>
</div> </div>
<div class="pr-people">
<div class="pr-author">
<span>author: </span>
<a
class="${authorClassList.join(" ")}"
href="https://github.com/godotengine/godot/pulls/${this.author.user}"
target="_blank"
title="Open ${this.author.pull_count} ${(this.author.pull_count > 1) ? 'PRs' : 'PR'} by ${this.author.user}"
>
${this.author.user}
</a>
</div>
</div>
<div class="pr-timing"> <div class="pr-timing">
<div class="pr-time"> <div class="pr-time">
<span>created: </span> <span>created: </span>
@@ -248,17 +203,6 @@ export default class PullRequestItem extends LitElement {
${greports.format.formatDate(this.updated_at)} ${greports.format.formatDate(this.updated_at)}
</span> </span>
</div> </div>
<div class="pr-author">
<span>author: </span>
<a
class="${authorClassList.join(" ")}"
href="https://github.com/godotengine/godot/pulls/${this.author.user}"
target="_blank"
title="Open ${this.author.pull_count} ${(this.author.pull_count > 1) ? 'PRs' : 'PR'} by ${this.author.user}"
>
${this.author.user}
</a>
</div>
</div> </div>
</div> </div>

View File

@@ -39,15 +39,14 @@ export default class PullRequestList extends LitElement {
min-width: 60px; min-width: 60px;
} }
:host .team-pulls { :host .file-pulls {
background-color: var(--pulls-background-color); background-color: var(--pulls-background-color);
border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0;
padding: 8px 12px; padding: 8px 12px;
max-width: 760px; max-width: 760px;
min-height: 200px;
} }
:host .team-pulls-toolbar { :host .file-pulls-toolbar {
background: var(--pulls-toolbar-color); background: var(--pulls-toolbar-color);
border-radius: 4px; border-radius: 4px;
display: flex; display: flex;
@@ -68,12 +67,12 @@ export default class PullRequestList extends LitElement {
} }
@media only screen and (max-width: 900px) { @media only screen and (max-width: 900px) {
:host .team-pulls { :host .file-pulls {
padding: 8px; padding: 8px;
max-width: 95%; max-width: 95%;
margin: 0px auto; margin: 0px auto;
} }
:host .team-pulls-toolbar { :host .file-pulls-toolbar {
flex-wrap: wrap; flex-wrap: wrap;
} }
:host .pulls-count { :host .pulls-count {
@@ -95,6 +94,7 @@ export default class PullRequestList extends LitElement {
@property({ type: String }) selectedBranch = ""; @property({ type: String }) selectedBranch = "";
@property({ type: String }) selectedPath = ""; @property({ type: String }) selectedPath = "";
@property({ type: Array }) selectedPulls = []; @property({ type: Array }) selectedPulls = [];
@property({ type: String }) filteredPull = "";
render(){ render(){
if (this.selectedPath === "") { if (this.selectedPath === "") {
@@ -115,22 +115,61 @@ export default class PullRequestList extends LitElement {
}); });
const total_pulls = this.pulls.length; const total_pulls = this.pulls.length;
const filtered_pulls = pulls.length let filtered_pulls = pulls.length
const has_pinned = (this.filteredPull !== "");
if (has_pinned) {
filtered_pulls -= 1;
}
return html` return html`
<div class="team-pulls"> <div class="file-pulls">
<div class="team-pulls-toolbar">
<div class="pulls-count">
<span>PRs affecting this path: </span>
<strong>${filtered_pulls}</strong>
${(filtered_pulls !== total_pulls) ? html`
<span class="pulls-count-total"> (out of ${total_pulls})</span>
` : ''
}
</div>
</div>
${pulls.map((item) => { ${pulls.map((item) => {
if (!has_pinned || parseInt(this.filteredPull, 10) !== item.public_id) {
return html``;
}
let author = null;
if (typeof this.authors[item.authored_by] != "undefined") {
author = this.authors[item.authored_by];
}
return html`
<gr-pull-request
.id="${item.public_id}"
.title="${item.title}"
.url="${item.url}"
?draft="${item.is_draft}"
.milestone="${item.milestone}"
.branch="${item.target_branch}"
.created_at="${item.created_at}"
.updated_at="${item.updated_at}"
.author="${author}"
.diff_url="${item.diff_url}"
.patch_url="${item.patch_url}"
/>
`;
})}
<div class="file-pulls-toolbar">
<div class="pulls-count">
<span>${(has_pinned ? "Other " : "")}PRs affecting this path: </span>
<strong>${filtered_pulls}</strong>
${(filtered_pulls !== total_pulls) ? html`
<span class="pulls-count-total"> (out of ${total_pulls})</span>
` : ''
}
</div>
</div>
${pulls.map((item) => {
if (has_pinned && parseInt(this.filteredPull, 10) === item.public_id) {
return html``;
}
let author = null; let author = null;
if (typeof this.authors[item.authored_by] != "undefined") { if (typeof this.authors[item.authored_by] != "undefined") {
author = this.authors[item.authored_by]; author = this.authors[item.authored_by];
@@ -143,7 +182,6 @@ export default class PullRequestList extends LitElement {
.url="${item.url}" .url="${item.url}"
?draft="${item.is_draft}" ?draft="${item.is_draft}"
.labels="${item.labels}"
.milestone="${item.milestone}" .milestone="${item.milestone}"
.branch="${item.target_branch}" .branch="${item.target_branch}"

View File

@@ -4,6 +4,7 @@ import PageContent from 'src/shared/components/PageContent';
import IndexHeader from "./components/IndexHeader"; import IndexHeader from "./components/IndexHeader";
import IndexDescription from "./components/IndexDescription"; import IndexDescription from "./components/IndexDescription";
import PullFilter from './components/filters/PullFilter';
import FileList from "./components/files/FileList"; import FileList from "./components/files/FileList";
import PullList from "./components/pulls/PullRequestList" import PullList from "./components/pulls/PullRequestList"
@@ -53,6 +54,8 @@ export default class EntryComponent extends LitElement {
this._selectedPath = ""; this._selectedPath = "";
this._selectedPathPulls = []; this._selectedPathPulls = [];
this._filteredPull = "";
this._requestData(); this._requestData();
} }
@@ -130,6 +133,19 @@ export default class EntryComponent extends LitElement {
this.requestUpdate(); this.requestUpdate();
} }
_onPullFilterChanged(event) {
this._filteredPull = event.detail.pull;
if (this._filteredPull !== "") {
const pullNumber = parseInt(this._filteredPull, 10);
if (!this._selectedPathPulls.includes(pullNumber)) {
this._selectedPath = "";
this._selectedPathPulls = [];
}
}
this.requestUpdate();
}
_onPathClicked(event) { _onPathClicked(event) {
this._selectedPath = event.detail.path; this._selectedPath = event.detail.path;
this._selectedPathPulls = event.detail.pulls; this._selectedPathPulls = event.detail.pulls;
@@ -142,6 +158,10 @@ export default class EntryComponent extends LitElement {
<gr-index-entry .generated_at="${this._generatedAt}"></gr-index-entry> <gr-index-entry .generated_at="${this._generatedAt}"></gr-index-entry>
<gr-index-description></gr-index-description> <gr-index-description></gr-index-description>
<gr-pull-filter
@filterchanged="${this._onPullFilterChanged}"
></gr-pull-filter>
${(this._isLoading ? html` ${(this._isLoading ? html`
<h3>Loading...</h3> <h3>Loading...</h3>
` : html` ` : html`
@@ -152,6 +172,7 @@ export default class EntryComponent extends LitElement {
.selectedRepository="${this._selectedRepository}" .selectedRepository="${this._selectedRepository}"
.selectedBranch="${this._selectedBranch}" .selectedBranch="${this._selectedBranch}"
.selectedPath="${this._selectedPath}" .selectedPath="${this._selectedPath}"
.filteredPull="${this._filteredPull}"
@pathclicked="${this._onPathClicked}" @pathclicked="${this._onPathClicked}"
></gr-file-list> ></gr-file-list>
@@ -161,6 +182,7 @@ export default class EntryComponent extends LitElement {
.selectedBranch="${this._selectedBranch}" .selectedBranch="${this._selectedBranch}"
.selectedPath="${this._selectedPath}" .selectedPath="${this._selectedPath}"
.selectedPulls="${this._selectedPathPulls}" .selectedPulls="${this._selectedPathPulls}"
.filteredPull="${this._filteredPull}"
></gr-pull-list> ></gr-pull-list>
</div> </div>
`)} `)}

View File

@@ -2,6 +2,7 @@
:root { :root {
--g-background-color: #fcfcfa; --g-background-color: #fcfcfa;
--g-background-extra-color: #98a5b8; --g-background-extra-color: #98a5b8;
--g-background-extra2-color: #cad3e1;
--g-font-color: #121314; --g-font-color: #121314;
--g-font-size: 15px; --g-font-size: 15px;
--g-font-weight: 400; --g-font-weight: 400;
@@ -19,6 +20,7 @@
:root { :root {
--g-background-color: #0d1117; --g-background-color: #0d1117;
--g-background-extra-color: #515c6c; --g-background-extra-color: #515c6c;
--g-background-extra2-color: #22252b;
--g-font-color: rgba(228, 228, 232, 0.9); --g-font-color: rgba(228, 228, 232, 0.9);
--link-font-color: #367df7; --link-font-color: #367df7;