Add user follow-up section (#250)
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
issues_open:{totalCount:faker.datatype.number(100)},
|
||||
issues_closed:{totalCount:faker.datatype.number(100)},
|
||||
pr_open:{totalCount:faker.datatype.number(100)},
|
||||
pr_closed:{totalCount:faker.datatype.number(100)},
|
||||
pr_merged:{totalCount:faker.datatype.number(100)},
|
||||
releases:{totalCount:faker.datatype.number(100)},
|
||||
forkCount:faker.datatype.number(100),
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
issues_open:{totalCount:faker.datatype.number(100)},
|
||||
issues_closed:{totalCount:faker.datatype.number(100)},
|
||||
pr_open:{totalCount:faker.datatype.number(100)},
|
||||
pr_closed:{totalCount:faker.datatype.number(100)},
|
||||
pr_merged:{totalCount:faker.datatype.number(100)},
|
||||
releases:{totalCount:faker.datatype.number(100)},
|
||||
forkCount:faker.datatype.number(100),
|
||||
|
||||
13
source/app/mocks/api/github/graphql/followup.user.mjs
Normal file
13
source/app/mocks/api/github/graphql/followup.user.mjs
Normal file
@@ -0,0 +1,13 @@
|
||||
/**Mocked data */
|
||||
export default function({faker, query, login = faker.internet.userName()}) {
|
||||
console.debug("metrics/compute/mocks > mocking graphql api result > followup/user")
|
||||
return ({
|
||||
user:{
|
||||
issues_open:{totalCount:faker.datatype.number(100)},
|
||||
issues_closed:{totalCount:faker.datatype.number(100)},
|
||||
pr_open:{totalCount:faker.datatype.number(100)},
|
||||
pr_closed:{totalCount:faker.datatype.number(100)},
|
||||
pr_merged:{totalCount:faker.datatype.number(100)},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -67,6 +67,7 @@
|
||||
issues_open:faker.datatype.number(1000),
|
||||
issues_closed:faker.datatype.number(1000),
|
||||
pr_open:faker.datatype.number(1000),
|
||||
pr_closed:{totalCount:faker.datatype.number(100)},
|
||||
pr_merged:faker.datatype.number(1000),
|
||||
forks:faker.datatype.number(1000),
|
||||
releases:faker.datatype.number(1000),
|
||||
@@ -161,8 +162,13 @@
|
||||
//Follow-up
|
||||
...(set.plugins.enabled.followup ? ({
|
||||
followup:{
|
||||
sections:options["followup.sections"].split(",").map(x => x.trim()).filter(x => ["user", "repositories"].includes(x)),
|
||||
issues:{get count() { return this.open + this.closed }, open:faker.datatype.number(1000), closed:faker.datatype.number(1000)},
|
||||
pr:{get count() { return this.open + this.merged }, open:faker.datatype.number(1000), merged:faker.datatype.number(1000)},
|
||||
pr:{get count() { return this.open + this.merged }, open:faker.datatype.number(1000), closed:faker.datatype.number(1000), merged:faker.datatype.number(1000)},
|
||||
user:{
|
||||
issues:{get count() { return this.open + this.closed }, open:faker.datatype.number(1000), closed:faker.datatype.number(1000)},
|
||||
pr:{get count() { return this.open + this.merged }, open:faker.datatype.number(1000), closed:faker.datatype.number(1000), merged:faker.datatype.number(1000)},
|
||||
}
|
||||
}
|
||||
}) : null),
|
||||
//Notable
|
||||
|
||||
@@ -34,6 +34,9 @@ query BaseRepositories {
|
||||
pr_open: pullRequests(states: OPEN) {
|
||||
totalCount
|
||||
}
|
||||
pr_closed: pullRequests(states: CLOSED) {
|
||||
totalCount
|
||||
}
|
||||
pr_merged: pullRequests(states: MERGED) {
|
||||
totalCount
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ query BaseRepository {
|
||||
pr_open: pullRequests(states: OPEN) {
|
||||
totalCount
|
||||
}
|
||||
pr_closed: pullRequests(states: CLOSED) {
|
||||
totalCount
|
||||
}
|
||||
pr_merged: pullRequests(states: MERGED) {
|
||||
totalCount
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
const {"config.animations":animations, "config.timezone":_timezone, "debug.flags":dflags} = imports.metadata.plugins.core.inputs({data, account, q})
|
||||
|
||||
//Init
|
||||
const computed = {commits:0, sponsorships:0, licenses:{favorite:"", used:{}}, token:{}, repositories:{watchers:0, stargazers:0, issues_open:0, issues_closed:0, pr_open:0, pr_merged:0, forks:0, forked:0, releases:0}}
|
||||
const computed = {commits:0, sponsorships:0, licenses:{favorite:"", used:{}}, token:{}, repositories:{watchers:0, stargazers:0, issues_open:0, issues_closed:0, pr_open:0, pr_closed:0, pr_merged:0, forks:0, forked:0, releases:0}}
|
||||
const avatar = imports.imgb64(data.user.avatarUrl)
|
||||
data.computed = computed
|
||||
console.debug(`metrics/compute/${login} > formatting common metrics`)
|
||||
@@ -57,7 +57,7 @@
|
||||
//Iterate through user's repositories
|
||||
for (const repository of data.user.repositories.nodes) {
|
||||
//Simple properties with totalCount
|
||||
for (const property of ["watchers", "stargazers", "issues_open", "issues_closed", "pr_open", "pr_merged", "releases"])
|
||||
for (const property of ["watchers", "stargazers", "issues_open", "issues_closed", "pr_open", "pr_closed", "pr_merged", "releases"])
|
||||
computed.repositories[property] += repository[property].totalCount
|
||||
//Forks
|
||||
computed.repositories.forks += repository.forkCount
|
||||
|
||||
@@ -5,6 +5,9 @@ The *followup* plugin displays the ratio of open/closed issues and the ratio of
|
||||
<table>
|
||||
<td align="center">
|
||||
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.followup.svg">
|
||||
<details><summary>Created by user version</summary>
|
||||
<img src="https://github.com/lowlighter/lowlighter/blob/master/metrics.plugin.followup.user.svg">
|
||||
</details>
|
||||
<img width="900" height="1" alt="">
|
||||
</td>
|
||||
</table>
|
||||
@@ -18,5 +21,6 @@ The *followup* plugin displays the ratio of open/closed issues and the ratio of
|
||||
with:
|
||||
# ... other options
|
||||
plugin_followup: yes
|
||||
plugin_followup_sections: repositories, user #
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//Setup
|
||||
export default async function({data, computed, imports, q, account}, {enabled = false} = {}) {
|
||||
export default async function({login, data, computed, imports, q, graphql, queries, account}, {enabled = false} = {}) {
|
||||
//Plugin execution
|
||||
try {
|
||||
//Check if plugin is enabled and requirements are met
|
||||
@@ -7,10 +7,11 @@
|
||||
return null
|
||||
|
||||
//Load inputs
|
||||
imports.metadata.plugins.followup.inputs({data, account, q})
|
||||
let {sections} = imports.metadata.plugins.followup.inputs({data, account, q})
|
||||
|
||||
//Define getters
|
||||
const followup = {
|
||||
sections,
|
||||
issues:{
|
||||
get count() {
|
||||
return this.open + this.closed
|
||||
@@ -24,17 +25,42 @@
|
||||
},
|
||||
pr:{
|
||||
get count() {
|
||||
return this.open + this.merged
|
||||
return this.open + this.closed + this.merged
|
||||
},
|
||||
get open() {
|
||||
return computed.repositories.pr_open
|
||||
},
|
||||
get closed() {
|
||||
return computed.repositories.pr_closed
|
||||
},
|
||||
get merged() {
|
||||
return computed.repositories.pr_merged
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
//Load user issues and pull requests
|
||||
if (sections.includes("user")) {
|
||||
const {user} = await graphql(queries.followup.user({login}))
|
||||
followup.user = {
|
||||
issues:{
|
||||
get count() {
|
||||
return this.open + this.closed
|
||||
},
|
||||
open:user.issues_open.totalCount,
|
||||
closed:user.issues_closed.totalCount,
|
||||
},
|
||||
pr:{
|
||||
get count() {
|
||||
return this.open + this.closed + this.merged
|
||||
},
|
||||
open:user.pr_open.totalCount,
|
||||
closed:user.pr_closed.totalCount,
|
||||
merged:user.pr_merged.totalCount,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
//Results
|
||||
return followup
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: "🎟️ Follow-up of issues and pull requests"
|
||||
cost: 0 API request
|
||||
cost: 0 API request (1 GraphQL request if "user" section is enabled)
|
||||
categorie: github
|
||||
index: 11
|
||||
supports:
|
||||
@@ -13,3 +13,13 @@ inputs:
|
||||
description: Display follow-up of repositories issues and pull requests
|
||||
type: boolean
|
||||
default: no
|
||||
|
||||
# Sections to display
|
||||
plugin_followup_sections:
|
||||
description: Sections to display
|
||||
type: array
|
||||
format: comma-separated
|
||||
default: repositories
|
||||
values:
|
||||
- repositories # Overall status of issues and pull requests on your repositories
|
||||
- user # Overall status of issues and pull requests you have created on GitHub
|
||||
19
source/plugins/followup/queries/user.graphql
Normal file
19
source/plugins/followup/queries/user.graphql
Normal file
@@ -0,0 +1,19 @@
|
||||
query FollowupUser {
|
||||
user(login: "$login") {
|
||||
issues_open:issues(states: OPEN) {
|
||||
totalCount
|
||||
}
|
||||
issues_closed:issues(states: CLOSED) {
|
||||
totalCount
|
||||
}
|
||||
pr_open:pullRequests(states: OPEN) {
|
||||
totalCount
|
||||
}
|
||||
pr_closed:pullRequests(states: CLOSED) {
|
||||
totalCount
|
||||
}
|
||||
pr_merged:pullRequests(states: MERGED) {
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,3 +3,10 @@
|
||||
with:
|
||||
token: MOCKED_TOKEN
|
||||
plugin_followup: yes
|
||||
|
||||
- name: Follow-up plugin (complete)
|
||||
uses: lowlighter/metrics@latest
|
||||
with:
|
||||
token: MOCKED_TOKEN
|
||||
plugin_followup: yes
|
||||
plugin_followup_sections: repositories, user
|
||||
@@ -1,12 +1,10 @@
|
||||
<% if (plugins.followup) { %>
|
||||
<div class="column">
|
||||
<section>
|
||||
<h2 class="field">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M6 2a.75.75 0 01.696.471L10 10.731l1.304-3.26A.75.75 0 0112 7h3.25a.75.75 0 010 1.5h-2.742l-1.812 4.528a.75.75 0 01-1.392 0L6 4.77 4.696 8.03A.75.75 0 014 8.5H.75a.75.75 0 010-1.5h2.742l1.812-4.529A.75.75 0 016 2z"></path></svg>
|
||||
Overall repositories issues and pull requests status
|
||||
Overall issues and pull requests status
|
||||
</h2>
|
||||
<div class="row fill-width">
|
||||
<section class="column">
|
||||
<h3 class="no-margin-top">Issues</h3>
|
||||
</section>
|
||||
<% if (plugins.followup.error) { %>
|
||||
<section>
|
||||
<div class="field error">
|
||||
@@ -15,57 +13,61 @@
|
||||
</div>
|
||||
</section>
|
||||
<% } else { %>
|
||||
<% for (const name of plugins.followup.sections) { const section = {repositories:plugins.followup, user:plugins.followup?.user}[name] %>
|
||||
<div class="column">
|
||||
<h3>
|
||||
<%= {repositories:`On ${user.login}'${[...user.login].pop() === "s" ? "" : "s"} repositories`, user:`Created by ${user.login}`}[name] %>
|
||||
</h3>
|
||||
<div class="row fill-width">
|
||||
<section class="column">
|
||||
<h3 class="no-margin-top">Issues</h3>
|
||||
<svg class="bar" xmlns="http://www.w3.org/2000/svg" width="220" height="8">
|
||||
<mask id="issues-bar">
|
||||
<rect x="0" y="0" width="220" height="8" fill="white" rx="5"/>
|
||||
</mask>
|
||||
<rect mask="url(#issues-bar)" x="0" y="0" width="<%= plugins.followup.issues.count ? 0 : 220 %>" height="8" fill="#d1d5da"/>
|
||||
<rect mask="url(#issues-bar)" x="0" y="0" width="<%= (plugins.followup.issues.closed/plugins.followup.issues.count)*220 || 0 %>" height="8" fill="#d73a49"/>
|
||||
<rect mask="url(#issues-bar)" x="<%= (plugins.followup.issues.closed/plugins.followup.issues.count)*220 || 0 %>" y="0" width="<%= (1-plugins.followup.issues.closed/plugins.followup.issues.count)*220 || 0 %>" height="8" fill="#28a745"/>
|
||||
<rect mask="url(#issues-bar)" x="0" y="0" width="<%= section.issues.count ? 0 : 220 %>" height="8" fill="#d1d5da"/>
|
||||
<rect mask="url(#issues-bar)" x="0" y="0" width="<%= (section.issues.open/section.issues.count)*220 || 0 %>" height="8" fill="#28a745"/>
|
||||
<rect mask="url(#issues-bar)" x="<%= (section.issues.open/section.issues.count)*220 || 0 %>" y="0" width="<%= (1-section.issues.open/section.issues.count)*220 || 0 %>" height="8" fill="#d73a49"/>
|
||||
</svg>
|
||||
<div class="field horizontal fill-width">
|
||||
<div class="field center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#d73a49" fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 0110.65-5.003.75.75 0 00.959-1.153 8 8 0 102.592 8.33.75.75 0 10-1.444-.407A6.5 6.5 0 011.5 8zM8 12a1 1 0 100-2 1 1 0 000 2zm0-8a.75.75 0 01.75.75v3.5a.75.75 0 11-1.5 0v-3.5A.75.75 0 018 4zm4.78 4.28l3-3a.75.75 0 00-1.06-1.06l-2.47 2.47-.97-.97a.749.749 0 10-1.06 1.06l1.5 1.5a.75.75 0 001.06 0z"></path></svg>
|
||||
<span class="no-wrap"><%= plugins.followup.issues.closed %> Closed</span>
|
||||
</div>
|
||||
<div class="followup legend field horizontal fill-width">
|
||||
<div class="field center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#28a745" fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zm-.25-6.25a.75.75 0 00-1.5 0v3.5a.75.75 0 001.5 0v-3.5z"></path></svg>
|
||||
<span class="no-wrap"><%= plugins.followup.issues.open %> Open</span>
|
||||
<span class="no-wrap"><%= section.issues.open %> <small>open</small></span>
|
||||
</div>
|
||||
<div class="field center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#d73a49" fill-rule="evenodd" d="M1.5 8a6.5 6.5 0 0110.65-5.003.75.75 0 00.959-1.153 8 8 0 102.592 8.33.75.75 0 10-1.444-.407A6.5 6.5 0 011.5 8zM8 12a1 1 0 100-2 1 1 0 000 2zm0-8a.75.75 0 01.75.75v3.5a.75.75 0 11-1.5 0v-3.5A.75.75 0 018 4zm4.78 4.28l3-3a.75.75 0 00-1.06-1.06l-2.47 2.47-.97-.97a.749.749 0 10-1.06 1.06l1.5 1.5a.75.75 0 001.06 0z"></path></svg>
|
||||
<span class="no-wrap"><%= section.issues.closed %> <small>closed</small></span>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
</section>
|
||||
|
||||
<section class="column">
|
||||
<h3 class="no-margin-top">Pull requests</h3>
|
||||
<% if (plugins.followup.error) { %>
|
||||
<section>
|
||||
<div class="field error">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2.343 13.657A8 8 0 1113.657 2.343 8 8 0 012.343 13.657zM6.03 4.97a.75.75 0 00-1.06 1.06L6.94 8 4.97 9.97a.75.75 0 101.06 1.06L8 9.06l1.97 1.97a.75.75 0 101.06-1.06L9.06 8l1.97-1.97a.75.75 0 10-1.06-1.06L8 6.94 6.03 4.97z"></path></svg>
|
||||
<%= plugins.followup.error.message %>
|
||||
</div>
|
||||
</section>
|
||||
<% } else { %>
|
||||
<svg class="bar" xmlns="http://www.w3.org/2000/svg" width="220" height="8">
|
||||
<mask id="pr-bar">
|
||||
<rect x="0" y="0" width="220" height="8" fill="white" rx="5"/>
|
||||
</mask>
|
||||
<rect mask="url(#pr-bar)" x="0" y="0" width="<%= plugins.followup.pr.count ? 0 : 220 %>" height="8" fill="#d1d5da"/>
|
||||
<rect mask="url(#pr-bar)" x="0" y="0" width="<%= (plugins.followup.pr.merged/plugins.followup.pr.count)*220 || 0 %>" height="8" fill="#6f42c1"/>
|
||||
<rect mask="url(#pr-bar)" x="<%= (plugins.followup.pr.merged/plugins.followup.pr.count)*220 || 0 %>" y="0" width="<%= (1-plugins.followup.pr.merged/plugins.followup.pr.count)*220 || 0 %>" height="8" fill="#28a745"/>
|
||||
<rect mask="url(#pr-bar)" x="0" y="0" width="<%= section.pr.count ? 0 : 220 %>" height="8" fill="#d1d5da"/>
|
||||
<rect mask="url(#pr-bar)" x="0" y="0" width="<%= (section.pr.open/section.pr.count)*220 || 0 %>" height="8" fill="#28a745"/>
|
||||
<rect mask="url(#pr-bar)" x="<%= (section.pr.open/section.pr.count)*220 || 0 %>" y="0" width="<%= (section.pr.closed/section.pr.count)*220 || 0 %>" height="8" fill="#d73a49"/>
|
||||
<rect mask="url(#pr-bar)" x="<%= ((section.pr.open+section.pr.closed)/section.pr.count)*220 || 0 %>" y="0" width="<%= (1-(section.pr.open+section.pr.closed)/section.pr.count)*220 || 0 %>" height="8" fill="#6f42c1"/>
|
||||
</svg>
|
||||
<div class="field horizontal fill-width">
|
||||
<div class="field center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#6f42c1" fill-rule="evenodd" d="M5 3.254V3.25v.005a.75.75 0 110-.005v.004zm.45 1.9a2.25 2.25 0 10-1.95.218v5.256a2.25 2.25 0 101.5 0V7.123A5.735 5.735 0 009.25 9h1.378a2.251 2.251 0 100-1.5H9.25a4.25 4.25 0 01-3.8-2.346zM12.75 9a.75.75 0 100-1.5.75.75 0 000 1.5zm-8.5 4.5a.75.75 0 100-1.5.75.75 0 000 1.5z"></path></svg>
|
||||
<span class="no-wrap"><%= plugins.followup.pr.merged %> Merged</span>
|
||||
</div>
|
||||
<div class="followup legend field horizontal fill-width">
|
||||
<div class="field center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#28a745" fill-rule="evenodd" d="M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122v5.256a2.251 2.251 0 11-1.5 0V5.372A2.25 2.25 0 011.5 3.25zM11 2.5h-1V4h1a1 1 0 011 1v5.628a2.251 2.251 0 101.5 0V5A2.5 2.5 0 0011 2.5zm1 10.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.75 12a.75.75 0 100 1.5.75.75 0 000-1.5z"></path></svg>
|
||||
<span class="no-wrap"><%= plugins.followup.pr.open %> Open</span>
|
||||
<span class="no-wrap"><%= section.pr.open %> <small>open</small></span>
|
||||
</div>
|
||||
<div class="field center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#d73a49" fill-rule="evenodd" d="M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122v5.256a2.251 2.251 0 11-1.5 0V5.372A2.25 2.25 0 011.5 3.25zM11 2.5h-1V4h1a1 1 0 011 1v5.628a2.251 2.251 0 101.5 0V5A2.5 2.5 0 0011 2.5zm1 10.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.75 12a.75.75 0 100 1.5.75.75 0 000-1.5z"></path></svg>
|
||||
<span class="no-wrap"><%= section.pr.closed %> <small>closed</small></span>
|
||||
</div>
|
||||
<div class="field center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill="#6f42c1" fill-rule="evenodd" d="M5 3.254V3.25v.005a.75.75 0 110-.005v.004zm.45 1.9a2.25 2.25 0 10-1.95.218v5.256a2.25 2.25 0 101.5 0V7.123A5.735 5.735 0 009.25 9h1.378a2.251 2.251 0 100-1.5H9.25a4.25 4.25 0 01-3.8-2.346zM12.75 9a.75.75 0 100-1.5.75.75 0 000 1.5zm-8.5 4.5a.75.75 0 100-1.5.75.75 0 000 1.5z"></path></svg>
|
||||
<span class="no-wrap"><%= section.pr.merged %> <small>merged</small></span>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<% } %>
|
||||
<% } %>
|
||||
@@ -157,6 +157,22 @@
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
/* Follow-up */
|
||||
.followup.legend {
|
||||
font-size: 12px;
|
||||
}
|
||||
.followup.legend svg {
|
||||
margin: 0 3px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
.followup.legend svg:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
.followup.legend svg:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
/* Labels */
|
||||
.label {
|
||||
background-color: #58A6FF30;
|
||||
|
||||
Reference in New Issue
Block a user