FEATURE: part 2 of dashboard improvements
- moderation tab - sorting/pagination - improved third party reports support - trending charts - better perf - many fixes - refactoring - new reports Co-Authored-By: Simon Cossar <scossar@users.noreply.github.com>
This commit is contained in:
@@ -7,25 +7,18 @@ acceptance("Dashboard Next", {
|
||||
QUnit.test("Visit dashboard next page", async assert => {
|
||||
await visit("/admin");
|
||||
|
||||
assert.ok($(".dashboard-next").length, "has dashboard-next class");
|
||||
|
||||
assert.ok($(".dashboard-mini-chart.signups").length, "has a signups chart");
|
||||
|
||||
assert.ok($(".dashboard-mini-chart.posts").length, "has a posts chart");
|
||||
assert.ok(exists(".dashboard-next"), "has dashboard-next class");
|
||||
|
||||
assert.ok(exists(".admin-report.signups"), "signups report");
|
||||
assert.ok(exists(".admin-report.posts"), "posts report");
|
||||
assert.ok(exists(".admin-report.dau-by-mau"), "dau-by-mau report");
|
||||
assert.ok(
|
||||
$(".dashboard-mini-chart.dau_by_mau").length,
|
||||
"has a dau_by_mau chart"
|
||||
exists(".admin-report.daily-engaged-users"),
|
||||
"daily-engaged-users report"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
$(".dashboard-mini-chart.daily_engaged_users").length,
|
||||
"has a daily_engaged_users chart"
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
$(".dashboard-mini-chart.new_contributors").length,
|
||||
"has a new_contributors chart"
|
||||
exists(".admin-report.new-contributors"),
|
||||
"new-contributors report"
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import componentTest from "helpers/component-test";
|
||||
|
||||
moduleForComponent("admin-report", {
|
||||
integration: true
|
||||
});
|
||||
|
||||
componentTest("default", {
|
||||
template: "{{admin-report dataSourceName='signups'}}",
|
||||
|
||||
test(assert) {
|
||||
andThen(() => {
|
||||
assert.ok(exists(".admin-report.signups"));
|
||||
|
||||
assert.ok(
|
||||
exists(".admin-report.table.signups", "it defaults to table mode")
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
find(".report-header .title")
|
||||
.text()
|
||||
.trim(),
|
||||
"Signups",
|
||||
"it has a title"
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
find(".report-header .info").attr("data-tooltip"),
|
||||
"New account registrations for this period",
|
||||
"it has a description"
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
find(".report-body .report-table thead tr th:first-child")
|
||||
.text()
|
||||
.trim(),
|
||||
"Day",
|
||||
"it has col headers"
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
find(".report-body .report-table thead tr th:nth-child(2)")
|
||||
.text()
|
||||
.trim(),
|
||||
"Count",
|
||||
"it has col headers"
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
find(".report-body .report-table tbody tr:nth-child(1) td:nth-child(1)")
|
||||
.text()
|
||||
.trim(),
|
||||
"June 16, 2018",
|
||||
"it has rows"
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
find(".report-body .report-table tbody tr:nth-child(1) td:nth-child(2)")
|
||||
.text()
|
||||
.trim(),
|
||||
"12",
|
||||
"it has rows"
|
||||
);
|
||||
|
||||
assert.ok(exists(".totals-sample-table"), "it has totals");
|
||||
});
|
||||
|
||||
click(".admin-report-table-header.y .sort-button");
|
||||
andThen(() => {
|
||||
assert.equal(
|
||||
find(".report-body .report-table tbody tr:nth-child(1) td:nth-child(2)")
|
||||
.text()
|
||||
.trim(),
|
||||
"7",
|
||||
"it can sort rows"
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
componentTest("options", {
|
||||
template: "{{admin-report dataSourceName='signups' reportOptions=options}}",
|
||||
|
||||
beforeEach() {
|
||||
this.set("options", {
|
||||
table: {
|
||||
perPage: 4,
|
||||
total: false
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
test(assert) {
|
||||
andThen(() => {
|
||||
assert.ok(exists(".pagination"), "it paginates the results");
|
||||
assert.equal(
|
||||
find(".pagination button").length,
|
||||
3,
|
||||
"it creates the correct number of pages"
|
||||
);
|
||||
|
||||
assert.notOk(exists(".totals-sample-table"), "it hides totals");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
componentTest("switch modes", {
|
||||
template: "{{admin-report dataSourceName='signups'}}",
|
||||
|
||||
test(assert) {
|
||||
click(".mode-button.chart");
|
||||
|
||||
andThen(() => {
|
||||
assert.notOk(
|
||||
exists(".admin-report.table.signups"),
|
||||
"it removes the table"
|
||||
);
|
||||
assert.ok(exists(".admin-report.chart.signups"), "it shows the chart");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
componentTest("timeout", {
|
||||
template: "{{admin-report dataSourceName='signups_timeout'}}",
|
||||
|
||||
test(assert) {
|
||||
andThen(() => {
|
||||
assert.ok(exists(".alert-error"), "it displays a timeout error");
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
export default {
|
||||
"/admin/general.json": {
|
||||
reports: [],
|
||||
last_backup_taken_at: "2018-04-13T12:51:19.926Z",
|
||||
updated_at: "2018-04-25T08:06:11.292Z",
|
||||
disk_space: {
|
||||
uploads_used: "74.5 KB",
|
||||
uploads_free: "117 GB",
|
||||
backups_used: "4.24 GB",
|
||||
backups_free: "117 GB"
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,20 +1,7 @@
|
||||
export default {
|
||||
"/admin/reports/daily_engaged_users": {
|
||||
report: {
|
||||
type: "daily_engaged_users",
|
||||
title: "Daily Engaged Users",
|
||||
xaxis: "Day",
|
||||
yaxis: "Engaged Users",
|
||||
description: "Number of users that have liked or posted in the last day",
|
||||
data: null,
|
||||
total: null,
|
||||
start_date: "2018-04-03",
|
||||
end_date: "2018-05-03",
|
||||
category_id: null,
|
||||
group_id: null,
|
||||
prev30Days: null,
|
||||
labels: null,
|
||||
report_key: ""
|
||||
report_key: "daily_engaged_users"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
export default {
|
||||
"/admin/dashboard-next.json": {
|
||||
reports: [],
|
||||
last_backup_taken_at: "2018-04-13T12:51:19.926Z",
|
||||
updated_at: "2018-04-25T08:06:11.292Z",
|
||||
disk_space: {
|
||||
uploads_used: "74.5 KB",
|
||||
uploads_free: "117 GB",
|
||||
backups_used: "4.24 GB",
|
||||
backups_free: "117 GB"
|
||||
}
|
||||
updated_at: "2018-04-25T08:06:11.292Z"
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
export default {
|
||||
"/admin/reports/dau_by_mau": {
|
||||
report: {
|
||||
type: "dau_by_mau",
|
||||
title: "DAU/MAU",
|
||||
xaxis: "Day",
|
||||
yaxis: "DAU/MAY",
|
||||
description: "Percentage of daily active users on monthly active users",
|
||||
data: null,
|
||||
total: null,
|
||||
start_date: "2018-01-26T00:00:00.000Z",
|
||||
end_date: "2018-04-27T23:59:59.999Z",
|
||||
category_id: null,
|
||||
group_id: null,
|
||||
prev30Days: 46,
|
||||
labels: null,
|
||||
report_key: ""
|
||||
report_key: "dau_by_mau"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,60 +1,7 @@
|
||||
export default {
|
||||
"/admin/reports/new_contributors": {
|
||||
report: {
|
||||
type: "new_contributors",
|
||||
title: "New Contributors",
|
||||
xaxis: "",
|
||||
yaxis: "",
|
||||
data: [
|
||||
{
|
||||
x: "2018-04-11",
|
||||
y: 10
|
||||
},
|
||||
{
|
||||
x: "2018-04-12",
|
||||
y: 10
|
||||
},
|
||||
{
|
||||
x: "2018-04-13",
|
||||
y: 60
|
||||
},
|
||||
{
|
||||
x: "2018-04-14",
|
||||
y: 60
|
||||
},
|
||||
{
|
||||
x: "2018-04-15",
|
||||
y: 10
|
||||
},
|
||||
{
|
||||
x: "2018-04-16",
|
||||
y: 10
|
||||
},
|
||||
{
|
||||
x: "2018-04-17",
|
||||
y: 10
|
||||
},
|
||||
{
|
||||
x: "2018-04-19",
|
||||
y: 10
|
||||
},
|
||||
{
|
||||
x: "2018-04-18",
|
||||
y: 10
|
||||
},
|
||||
{
|
||||
x: "2018-04-20",
|
||||
y: 1
|
||||
}
|
||||
],
|
||||
total: 121,
|
||||
start_date: "2018-03-26T00:00:00.000Z",
|
||||
end_date: "2018-04-25T23:59:59.999Z",
|
||||
category_id: null,
|
||||
group_id: null,
|
||||
prev30Days: null,
|
||||
labels: null,
|
||||
report_key: ""
|
||||
report_key: "new_contributors"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
export default {
|
||||
"/admin/reports/posts": {
|
||||
report: {
|
||||
type: "topics",
|
||||
title: "Topics",
|
||||
xaxis: "Day",
|
||||
yaxis: "Number of new posts",
|
||||
data: null,
|
||||
total: null,
|
||||
start_date: "2018-03-26T00:00:00.000Z",
|
||||
end_date: "2018-04-25T23:59:59.999Z",
|
||||
category_id: null,
|
||||
group_id: null,
|
||||
prev30Days: 0,
|
||||
labels: null,
|
||||
report_key: ""
|
||||
report_key: "posts"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,53 +4,76 @@ export default {
|
||||
type: "signups",
|
||||
title: "Signups",
|
||||
xaxis: "Day",
|
||||
yaxis: "Number of new users",
|
||||
yaxis: "Number of signups",
|
||||
description: "New account registrations for this period",
|
||||
data: [
|
||||
{
|
||||
x: "2018-04-11",
|
||||
y: 10
|
||||
},
|
||||
{
|
||||
x: "2018-04-12",
|
||||
y: 10
|
||||
},
|
||||
{
|
||||
x: "2018-04-13",
|
||||
y: 22
|
||||
},
|
||||
{
|
||||
x: "2018-04-14",
|
||||
y: 58
|
||||
},
|
||||
{
|
||||
x: "2018-04-15",
|
||||
y: 10
|
||||
},
|
||||
{
|
||||
x: "2018-04-16",
|
||||
y: 10
|
||||
},
|
||||
{
|
||||
x: "2018-04-17",
|
||||
y: 19
|
||||
},
|
||||
{
|
||||
x: "2018-04-18",
|
||||
y: 12
|
||||
},
|
||||
{
|
||||
x: "2018-04-19",
|
||||
y: 19
|
||||
}
|
||||
{ x: "2018-06-16", y: 12 },
|
||||
{ x: "2018-06-17", y: 16 },
|
||||
{ x: "2018-06-18", y: 42 },
|
||||
{ x: "2018-06-19", y: 38 },
|
||||
{ x: "2018-06-20", y: 41 },
|
||||
{ x: "2018-06-21", y: 32 },
|
||||
{ x: "2018-06-22", y: 23 },
|
||||
{ x: "2018-06-23", y: 23 },
|
||||
{ x: "2018-06-24", y: 17 },
|
||||
{ x: "2018-06-25", y: 27 },
|
||||
{ x: "2018-06-26", y: 32 },
|
||||
{ x: "2018-06-27", y: 7 }
|
||||
],
|
||||
total: 136,
|
||||
start_date: "2018-03-26T00:00:00.000Z",
|
||||
end_date: "2018-04-25T23:59:59.999Z",
|
||||
start_date: "2018-06-16T00:00:00Z",
|
||||
end_date: "2018-07-16T23:59:59Z",
|
||||
prev_data: [
|
||||
{ x: "2018-05-17", y: 32 },
|
||||
{ x: "2018-05-18", y: 30 },
|
||||
{ x: "2018-05-19", y: 12 },
|
||||
{ x: "2018-05-20", y: 23 },
|
||||
{ x: "2018-05-21", y: 50 },
|
||||
{ x: "2018-05-22", y: 39 },
|
||||
{ x: "2018-05-23", y: 51 },
|
||||
{ x: "2018-05-24", y: 48 },
|
||||
{ x: "2018-05-25", y: 37 },
|
||||
{ x: "2018-05-26", y: 17 },
|
||||
{ x: "2018-05-27", y: 6 },
|
||||
{ x: "2018-05-28", y: 20 },
|
||||
{ x: "2018-05-29", y: 37 },
|
||||
{ x: "2018-05-30", y: 37 },
|
||||
{ x: "2018-05-31", y: 37 },
|
||||
{ x: "2018-06-01", y: 38 },
|
||||
{ x: "2018-06-02", y: 23 },
|
||||
{ x: "2018-06-03", y: 18 },
|
||||
{ x: "2018-06-04", y: 39 },
|
||||
{ x: "2018-06-05", y: 26 },
|
||||
{ x: "2018-06-06", y: 39 },
|
||||
{ x: "2018-06-07", y: 52 },
|
||||
{ x: "2018-06-08", y: 35 },
|
||||
{ x: "2018-06-09", y: 19 },
|
||||
{ x: "2018-06-10", y: 15 },
|
||||
{ x: "2018-06-11", y: 31 },
|
||||
{ x: "2018-06-12", y: 38 },
|
||||
{ x: "2018-06-13", y: 30 },
|
||||
{ x: "2018-06-14", y: 45 },
|
||||
{ x: "2018-06-15", y: 37 },
|
||||
{ x: "2018-06-16", y: 12 }
|
||||
],
|
||||
prev_start_date: "2018-05-17T00:00:00Z",
|
||||
prev_end_date: "2018-06-17T00:00:00Z",
|
||||
category_id: null,
|
||||
group_id: null,
|
||||
prev30Days: 0,
|
||||
labels: null,
|
||||
report_key: ""
|
||||
prev30Days: null,
|
||||
dates_filtering: true,
|
||||
report_key: "reports:signups::20180616:20180716::[:prev_period]:",
|
||||
labels: [
|
||||
{ type: "date", properties: ["x"], title: "Day" },
|
||||
{ type: "number", properties: ["y"], title: "Count" }
|
||||
],
|
||||
processing: false,
|
||||
average: false,
|
||||
percent: false,
|
||||
higher_is_better: true,
|
||||
category_filtering: false,
|
||||
group_filtering: true,
|
||||
modes: ["table", "chart"],
|
||||
prev_period: 961
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
export default {
|
||||
"/admin/reports/signups_timeout": {
|
||||
report: {
|
||||
type: "signups",
|
||||
title: "Signups",
|
||||
xaxis: "Day",
|
||||
yaxis: "Number of signups",
|
||||
description: "New account registrations for this period",
|
||||
data: null,
|
||||
start_date: "2018-06-16T00:00:00Z",
|
||||
end_date: "2018-07-16T23:59:59Z",
|
||||
prev_data: null,
|
||||
prev_start_date: "2018-05-17T00:00:00Z",
|
||||
prev_end_date: "2018-06-17T00:00:00Z",
|
||||
category_id: null,
|
||||
group_id: null,
|
||||
prev30Days: null,
|
||||
dates_filtering: true,
|
||||
report_key: "reports:signups_timeout::20180616:20180716::[:prev_period]:",
|
||||
labels: [
|
||||
{ type: "date", properties: ["x"], title: "Day" },
|
||||
{ type: "number", properties: ["y"], title: "Count" }
|
||||
],
|
||||
processing: false,
|
||||
average: false,
|
||||
percent: false,
|
||||
higher_is_better: true,
|
||||
category_filtering: false,
|
||||
group_filtering: true,
|
||||
modes: ["table", "chart"],
|
||||
prev_period: 961,
|
||||
timeout: true
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,18 +1,7 @@
|
||||
export default {
|
||||
"/admin/reports/top_referred_topics": {
|
||||
report: {
|
||||
type: "top_referred_topics",
|
||||
title: "Trending search",
|
||||
xaxis: "",
|
||||
yaxis: "",
|
||||
data: null,
|
||||
total: null,
|
||||
start_date: "2018-03-26T00:00:00.000Z",
|
||||
end_date: "2018-04-25T23:59:59.999Z",
|
||||
category_id: null,
|
||||
group_id: null,
|
||||
prev30Days: null,
|
||||
labels: ["Topic", "Visits"]
|
||||
report_key: "top_referred_topics"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
export default {
|
||||
"/admin/reports/topics": {
|
||||
report: {
|
||||
type: "topics",
|
||||
title: "Topics",
|
||||
xaxis: "Day",
|
||||
yaxis: "Number of new topics",
|
||||
data: null,
|
||||
total: null,
|
||||
start_date: "2018-03-26T00:00:00.000Z",
|
||||
end_date: "2018-04-25T23:59:59.999Z",
|
||||
category_id: null,
|
||||
group_id: null,
|
||||
prev30Days: 0,
|
||||
labels: null,
|
||||
report_key: ""
|
||||
report_key: "topics"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
export default {
|
||||
"/admin/reports/trending_search": {
|
||||
report: {
|
||||
type: "trending_search",
|
||||
title: "Trending search",
|
||||
xaxis: "",
|
||||
yaxis: "",
|
||||
data: null,
|
||||
total: null,
|
||||
start_date: "2018-03-26T00:00:00.000Z",
|
||||
end_date: "2018-04-25T23:59:59.999Z",
|
||||
category_id: null,
|
||||
group_id: null,
|
||||
prev30Days: null,
|
||||
labels: ["Term", "Searches", "Unique"],
|
||||
report_key: ""
|
||||
report_key: "trending_search"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,21 +1,7 @@
|
||||
export default {
|
||||
"/admin/reports/users_by_trust_level": {
|
||||
report: {
|
||||
type: "users_by_trust_level",
|
||||
title: "Users per Trust Level",
|
||||
xaxis: "Trust Level",
|
||||
yaxis: "Number of Users",
|
||||
description:
|
||||
"translation missing: en.reports.users_by_trust_level.description",
|
||||
data: null,
|
||||
total: null,
|
||||
start_date: "2018-03-30T00:00:00.000Z",
|
||||
end_date: "2018-04-29T23:59:59.999Z",
|
||||
category_id: null,
|
||||
group_id: null,
|
||||
prev30Days: null,
|
||||
labels: null,
|
||||
report_key: ""
|
||||
report_key: "users_by_trust_level"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
export default {
|
||||
"/admin/reports/users_by_type": {
|
||||
report: {
|
||||
type: "users_by_type",
|
||||
title: "Users per Type",
|
||||
xaxis: "Type",
|
||||
yaxis: "Number of Users",
|
||||
description: "translation missing: en.reports.users_by_type.description",
|
||||
data: null,
|
||||
total: null,
|
||||
start_date: "2018-03-30T00:00:00.000Z",
|
||||
end_date: "2018-04-29T23:59:59.999Z",
|
||||
category_id: null,
|
||||
group_id: null,
|
||||
prev30Days: null,
|
||||
labels: null,
|
||||
report_key: ""
|
||||
report_key: "users_by_type"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -391,3 +391,74 @@ QUnit.test("average", assert => {
|
||||
report.set("average", false);
|
||||
assert.ok(report.get("lastSevenDaysCount") === 35);
|
||||
});
|
||||
|
||||
QUnit.test("computed labels", assert => {
|
||||
const data = [
|
||||
{
|
||||
username: "joffrey",
|
||||
user_url: "/admin/users/1/joffrey",
|
||||
flag_count: 1876,
|
||||
time_read: 287362,
|
||||
note: "This is a long note"
|
||||
}
|
||||
];
|
||||
|
||||
const labels = [
|
||||
{
|
||||
type: "link",
|
||||
properties: ["username", "user_url"],
|
||||
title: "Username"
|
||||
},
|
||||
{ properties: ["flag_count"], title: "Flag count" },
|
||||
{ type: "seconds", properties: ["time_read"], title: "Time read" },
|
||||
{ type: "text", properties: ["note"], title: "Note" }
|
||||
];
|
||||
|
||||
const report = Report.create({
|
||||
type: "topics",
|
||||
labels,
|
||||
data
|
||||
});
|
||||
|
||||
const row = report.get("data.0");
|
||||
const computedLabels = report.get("computedLabels");
|
||||
|
||||
const usernameLabel = computedLabels[0];
|
||||
assert.equal(usernameLabel.property, "username");
|
||||
assert.equal(usernameLabel.sort_property, "username");
|
||||
assert.equal(usernameLabel.title, "Username");
|
||||
const computedUsernameLabel = usernameLabel.compute(row);
|
||||
assert.equal(
|
||||
computedUsernameLabel.formatedValue,
|
||||
'<a href="/admin/users/1/joffrey">joffrey</a>'
|
||||
);
|
||||
assert.equal(computedUsernameLabel.type, "link");
|
||||
assert.equal(computedUsernameLabel.value, "joffrey");
|
||||
|
||||
const flagCountLabel = computedLabels[1];
|
||||
assert.equal(flagCountLabel.property, "flag_count");
|
||||
assert.equal(flagCountLabel.sort_property, "flag_count");
|
||||
assert.equal(flagCountLabel.title, "Flag count");
|
||||
const computedFlagCountLabel = flagCountLabel.compute(row);
|
||||
assert.equal(computedFlagCountLabel.formatedValue, "1.9k");
|
||||
assert.equal(computedFlagCountLabel.type, "number");
|
||||
assert.equal(computedFlagCountLabel.value, 1876);
|
||||
|
||||
const timeReadLabel = computedLabels[2];
|
||||
assert.equal(timeReadLabel.property, "time_read");
|
||||
assert.equal(timeReadLabel.sort_property, "time_read");
|
||||
assert.equal(timeReadLabel.title, "Time read");
|
||||
const computedTimeReadLabel = timeReadLabel.compute(row);
|
||||
assert.equal(computedTimeReadLabel.formatedValue, "3d");
|
||||
assert.equal(computedTimeReadLabel.type, "seconds");
|
||||
assert.equal(computedTimeReadLabel.value, 287362);
|
||||
|
||||
const noteLabel = computedLabels[3];
|
||||
assert.equal(noteLabel.property, "note");
|
||||
assert.equal(noteLabel.sort_property, "note");
|
||||
assert.equal(noteLabel.title, "Note");
|
||||
const computedNoteLabel = noteLabel.compute(row);
|
||||
assert.equal(computedNoteLabel.formatedValue, "This is a long note");
|
||||
assert.equal(computedNoteLabel.type, "text");
|
||||
assert.equal(computedNoteLabel.value, "This is a long note");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user