mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FEATURE: separate admin page for whats new and reports (#26216)
Currently, a new sidebar link for what's new and reports is going to the main dashboard page and activates the proper tab. It might be problematic, especially, when the instance has a lot of problems. In that case, it would be difficult for admin to find reports or what’s new which is rendered at the bottom of the page. Therefore separate pages for reports and what's new were created. Reports were moved to a component that is shared between a separate page and the dashboard.
This commit is contained in:
parent
34a14112a7
commit
043117ca13
@ -0,0 +1,92 @@
|
|||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import { Input } from "@ember/component";
|
||||||
|
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
|
||||||
|
import { LinkTo } from "@ember/routing";
|
||||||
|
import { service } from "@ember/service";
|
||||||
|
import { htmlSafe } from "@ember/template";
|
||||||
|
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
import dIcon from "discourse-common/helpers/d-icon";
|
||||||
|
import i18n from "discourse-common/helpers/i18n";
|
||||||
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
|
export default class AdminReports extends Component {
|
||||||
|
@service siteSettings;
|
||||||
|
@tracked reports = null;
|
||||||
|
@tracked filter = "";
|
||||||
|
@tracked isLoading = false;
|
||||||
|
|
||||||
|
@bind
|
||||||
|
loadReports() {
|
||||||
|
this.isLoading = true;
|
||||||
|
ajax("/admin/reports")
|
||||||
|
.then((json) => {
|
||||||
|
this.reports = json.reports;
|
||||||
|
})
|
||||||
|
.finally(() => (this.isLoading = false));
|
||||||
|
}
|
||||||
|
|
||||||
|
get filteredReports() {
|
||||||
|
if (!this.reports) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let filteredReports = this.reports;
|
||||||
|
if (this.filter) {
|
||||||
|
const lowerCaseFilter = this.filter.toLowerCase();
|
||||||
|
filteredReports = filteredReports.filter((report) => {
|
||||||
|
return (
|
||||||
|
(report.title || "").toLowerCase().includes(lowerCaseFilter) ||
|
||||||
|
(report.description || "").toLowerCase().includes(lowerCaseFilter)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const hiddenReports = (this.siteSettings.dashboard_hidden_reports || "")
|
||||||
|
.split("|")
|
||||||
|
.filter(Boolean);
|
||||||
|
filteredReports = filteredReports.filter(
|
||||||
|
(report) => !hiddenReports.includes(report.type)
|
||||||
|
);
|
||||||
|
|
||||||
|
return filteredReports;
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div {{didInsert this.loadReports}}>
|
||||||
|
<ConditionalLoadingSpinner @condition={{this.isLoading}}>
|
||||||
|
<div class="admin-reports-header">
|
||||||
|
<h2>{{i18n "admin.reports.title"}}</h2>
|
||||||
|
<Input
|
||||||
|
class="admin-reports-header__filter"
|
||||||
|
placeholder={{i18n "admin.filter_reports"}}
|
||||||
|
@value={{this.filter}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-info">
|
||||||
|
{{dIcon "book"}}
|
||||||
|
{{htmlSafe (i18n "admin.reports.meta_doc")}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="admin-reports-list">
|
||||||
|
{{#each this.filteredReports as |report|}}
|
||||||
|
<li class="admin-reports-list__report">
|
||||||
|
<LinkTo @route="adminReports.show" @model={{report.type}}>
|
||||||
|
<h3
|
||||||
|
class="admin-reports-list__report-title"
|
||||||
|
>{{report.title}}</h3>
|
||||||
|
{{#if report.description}}
|
||||||
|
<p class="admin-reports-list__report-description">
|
||||||
|
{{report.description}}
|
||||||
|
</p>
|
||||||
|
{{/if}}
|
||||||
|
</LinkTo>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
</ConditionalLoadingSpinner>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
}
|
@ -1,10 +0,0 @@
|
|||||||
import { service } from "@ember/service";
|
|
||||||
import DiscourseRoute from "discourse/routes/discourse";
|
|
||||||
|
|
||||||
export default class AdminReportsIndexRoute extends DiscourseRoute {
|
|
||||||
@service router;
|
|
||||||
|
|
||||||
beforeModel() {
|
|
||||||
this.router.transitionTo("admin.dashboardReports");
|
|
||||||
}
|
|
||||||
}
|
|
@ -158,6 +158,7 @@ export default function () {
|
|||||||
"adminReports",
|
"adminReports",
|
||||||
{ path: "/reports", resetNamespace: true },
|
{ path: "/reports", resetNamespace: true },
|
||||||
function () {
|
function () {
|
||||||
|
this.route("index", { path: "/" });
|
||||||
this.route("show", { path: ":type" });
|
this.route("show", { path: ":type" });
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -223,6 +224,11 @@ export default function () {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.route("admin.whatsNew", {
|
||||||
|
path: "/whats-new",
|
||||||
|
resetNamespace: true,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// EXPERIMENTAL: These admin routes are hidden behind an `admin_sidebar_enabled_groups`
|
// EXPERIMENTAL: These admin routes are hidden behind an `admin_sidebar_enabled_groups`
|
||||||
|
@ -1,33 +1 @@
|
|||||||
<ConditionalLoadingSpinner @condition={{this.isLoading}}>
|
<AdminReports />
|
||||||
<div class="reports-index section">
|
|
||||||
<div class="section-title">
|
|
||||||
<h2>{{i18n "admin.reports.title"}}</h2>
|
|
||||||
<Input
|
|
||||||
class="filter-reports-input"
|
|
||||||
placeholder={{i18n "admin.dashboard.filter_reports"}}
|
|
||||||
autofocus={{true}}
|
|
||||||
{{on "input" (with-event-value this.filterReports)}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="alert alert-info">
|
|
||||||
{{d-icon "book"}}
|
|
||||||
{{html-safe (i18n "admin.reports.meta_doc")}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="reports-list">
|
|
||||||
{{#each this.filteredReports as |report|}}
|
|
||||||
<li class="report">
|
|
||||||
<LinkTo @route="adminReports.show" @model={{report.type}}>
|
|
||||||
<h3 class="report-title">{{report.title}}</h3>
|
|
||||||
{{#if report.description}}
|
|
||||||
<p class="report-description">
|
|
||||||
{{report.description}}
|
|
||||||
</p>
|
|
||||||
{{/if}}
|
|
||||||
</LinkTo>
|
|
||||||
</li>
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</ConditionalLoadingSpinner>
|
|
@ -0,0 +1 @@
|
|||||||
|
<AdminReports />
|
@ -0,0 +1,2 @@
|
|||||||
|
<h2>{{i18n "admin.new_features.title"}}</h2>
|
||||||
|
<DashboardNewFeatures />
|
@ -13,7 +13,7 @@ export const ADMIN_NAV_MAP = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "admin_whats_new",
|
name: "admin_whats_new",
|
||||||
route: "admin.dashboardNewFeatures",
|
route: "admin.whatsNew",
|
||||||
label: "admin.account.sidebar_link.whats_new",
|
label: "admin.account.sidebar_link.whats_new",
|
||||||
icon: "gift",
|
icon: "gift",
|
||||||
},
|
},
|
||||||
@ -25,7 +25,7 @@ export const ADMIN_NAV_MAP = [
|
|||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
name: "admin_all_reports",
|
name: "admin_all_reports",
|
||||||
route: "admin.dashboardReports",
|
route: "adminReports.index",
|
||||||
label: "admin.reports.sidebar_link.all",
|
label: "admin.reports.sidebar_link.all",
|
||||||
icon: "chart-bar",
|
icon: "chart-bar",
|
||||||
},
|
},
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
import { visit } from "@ember/test-helpers";
|
import { click, fillIn, visit } from "@ember/test-helpers";
|
||||||
import { test } from "qunit";
|
import { test } from "qunit";
|
||||||
import { AUTO_GROUPS } from "discourse/lib/constants";
|
import { AUTO_GROUPS } from "discourse/lib/constants";
|
||||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
import PreloadStore from "discourse/lib/preload-store";
|
import PreloadStore from "discourse/lib/preload-store";
|
||||||
import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
|
import {
|
||||||
|
acceptance,
|
||||||
|
count,
|
||||||
|
exists,
|
||||||
|
} from "discourse/tests/helpers/qunit-helpers";
|
||||||
|
|
||||||
acceptance("Admin Sidebar - Sections", function (needs) {
|
acceptance("Admin Sidebar - Sections", function (needs) {
|
||||||
needs.user({
|
needs.user({
|
||||||
@ -92,6 +96,36 @@ acceptance("Admin Sidebar - Sections", function (needs) {
|
|||||||
"the admin plugin route is added to the plugins section"
|
"the admin plugin route is added to the plugins section"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Visit reports page", async function (assert) {
|
||||||
|
await visit("/admin");
|
||||||
|
await click(".sidebar-section-link[data-link-name='admin_all_reports']");
|
||||||
|
|
||||||
|
assert.strictEqual(count(".admin-reports-list__report"), 1);
|
||||||
|
|
||||||
|
await fillIn(".admin-reports-header__filter", "flags");
|
||||||
|
|
||||||
|
assert.strictEqual(count(".admin-reports-list__report"), 0);
|
||||||
|
|
||||||
|
await click(
|
||||||
|
".sidebar-section-link[data-link-name='admin_about_your_site']"
|
||||||
|
);
|
||||||
|
await click(".sidebar-section-link[data-link-name='admin_all_reports']");
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
count(".admin-reports-list__report"),
|
||||||
|
1,
|
||||||
|
"navigating back and forth resets filter"
|
||||||
|
);
|
||||||
|
|
||||||
|
await fillIn(".admin-reports-header__filter", "activities");
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
count(".admin-reports-list__report"),
|
||||||
|
1,
|
||||||
|
"filter is case insensitive"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
acceptance("Admin Sidebar - Sections - Plugin API", function (needs) {
|
acceptance("Admin Sidebar - Sections - Plugin API", function (needs) {
|
||||||
|
@ -93,31 +93,25 @@ acceptance("Dashboard", function (needs) {
|
|||||||
await visit("/admin");
|
await visit("/admin");
|
||||||
await click(".dashboard .navigation-item.reports .navigation-link");
|
await click(".dashboard .navigation-item.reports .navigation-link");
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(count(".dashboard .admin-reports-list__report"), 1);
|
||||||
count(".dashboard .reports-index.section .reports-list .report"),
|
|
||||||
1
|
|
||||||
);
|
|
||||||
|
|
||||||
await fillIn(".dashboard .filter-reports-input", "flags");
|
await fillIn(".dashboard .admin-reports-header__filter", "flags");
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(count(".dashboard .admin-reports-list__report"), 0);
|
||||||
count(".dashboard .reports-index.section .reports-list .report"),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
await click(".dashboard .navigation-item.security .navigation-link");
|
await click(".dashboard .navigation-item.security .navigation-link");
|
||||||
await click(".dashboard .navigation-item.reports .navigation-link");
|
await click(".dashboard .navigation-item.reports .navigation-link");
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
count(".dashboard .reports-index.section .reports-list .report"),
|
count(".dashboard .admin-reports-list__report"),
|
||||||
1,
|
1,
|
||||||
"navigating back and forth resets filter"
|
"navigating back and forth resets filter"
|
||||||
);
|
);
|
||||||
|
|
||||||
await fillIn(".dashboard .filter-reports-input", "activities");
|
await fillIn(".dashboard .admin-reports-header__filter", "activities");
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
count(".dashboard .reports-index.section .reports-list .report"),
|
count(".dashboard .admin-reports-list__report"),
|
||||||
1,
|
1,
|
||||||
"filter is case insensitive"
|
"filter is case insensitive"
|
||||||
);
|
);
|
||||||
|
@ -13,17 +13,21 @@ acceptance("Reports", function (needs) {
|
|||||||
test("Visit reports page", async function (assert) {
|
test("Visit reports page", async function (assert) {
|
||||||
await visit("/admin/reports");
|
await visit("/admin/reports");
|
||||||
|
|
||||||
assert.strictEqual(count(".reports-list .report"), 1);
|
assert.strictEqual(count(".admin-reports-list__report"), 1);
|
||||||
|
|
||||||
const report = query(".reports-list .report:first-child");
|
const report = query(".admin-reports-list__report:first-child");
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
report.querySelector(".report-title").innerHTML.trim(),
|
report
|
||||||
|
.querySelector(".admin-reports-list__report-title")
|
||||||
|
.innerHTML.trim(),
|
||||||
"My report"
|
"My report"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
report.querySelector(".report-description").innerHTML.trim(),
|
report
|
||||||
|
.querySelector(".admin-reports-list__report-description")
|
||||||
|
.innerHTML.trim(),
|
||||||
"List of my activities"
|
"List of my activities"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -202,3 +202,51 @@
|
|||||||
flex: 1 0 auto;
|
flex: 1 0 auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.admin-reports-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-bottom: 1px solid var(--primary-low);
|
||||||
|
padding-bottom: 0.5em;
|
||||||
|
|
||||||
|
@media screen and (max-width: 400px) {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0 0.5em 0 0;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-reports-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0 -1.5%;
|
||||||
|
&__report {
|
||||||
|
margin: 1.5%;
|
||||||
|
border: 1px solid var(--primary-low);
|
||||||
|
flex: 1 1 28%;
|
||||||
|
transition: box-shadow 0.25s;
|
||||||
|
min-width: 225px;
|
||||||
|
max-width: 550px;
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 1em;
|
||||||
|
.report-description {
|
||||||
|
color: var(--primary-high);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -502,36 +502,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-reports {
|
|
||||||
.reports-list {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
list-style-type: none;
|
|
||||||
margin: 0 -1.5%;
|
|
||||||
}
|
|
||||||
.report {
|
|
||||||
margin: 1.5%;
|
|
||||||
border: 1px solid var(--primary-low);
|
|
||||||
flex: 1 1 28%;
|
|
||||||
transition: box-shadow 0.25s;
|
|
||||||
min-width: 225px;
|
|
||||||
max-width: 550px;
|
|
||||||
a {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 1em;
|
|
||||||
.report-description {
|
|
||||||
color: var(--primary-high);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
box-shadow: var(--shadow-card);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.version-checks {
|
.version-checks {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -4813,6 +4813,7 @@ en:
|
|||||||
title: "Discourse Admin"
|
title: "Discourse Admin"
|
||||||
moderator: "Moderator"
|
moderator: "Moderator"
|
||||||
back_to_forum: "Back to Forum"
|
back_to_forum: "Back to Forum"
|
||||||
|
filter_reports: Filter reports
|
||||||
|
|
||||||
tags:
|
tags:
|
||||||
remove_muted_tags_from_latest:
|
remove_muted_tags_from_latest:
|
||||||
@ -4827,6 +4828,8 @@ en:
|
|||||||
sidebar_link:
|
sidebar_link:
|
||||||
all: "All"
|
all: "All"
|
||||||
|
|
||||||
|
new_features:
|
||||||
|
title: "What's new"
|
||||||
dashboard:
|
dashboard:
|
||||||
title: "Dashboard"
|
title: "Dashboard"
|
||||||
last_updated: "Dashboard updated:"
|
last_updated: "Dashboard updated:"
|
||||||
@ -4884,7 +4887,6 @@ en:
|
|||||||
exception_error: Sorry, an error occurred while executing the query
|
exception_error: Sorry, an error occurred while executing the query
|
||||||
too_many_requests: You’ve performed this action too many times. Please wait before trying again.
|
too_many_requests: You’ve performed this action too many times. Please wait before trying again.
|
||||||
not_found_error: Sorry, this report doesn’t exist
|
not_found_error: Sorry, this report doesn’t exist
|
||||||
filter_reports: Filter reports
|
|
||||||
custom_date_range: Custom date range
|
custom_date_range: Custom date range
|
||||||
|
|
||||||
reports:
|
reports:
|
||||||
|
@ -325,6 +325,7 @@ Discourse::Application.routes.draw do
|
|||||||
get "dashboard/security" => "dashboard#security"
|
get "dashboard/security" => "dashboard#security"
|
||||||
get "dashboard/reports" => "dashboard#reports"
|
get "dashboard/reports" => "dashboard#reports"
|
||||||
get "dashboard/whats-new" => "dashboard#new_features"
|
get "dashboard/whats-new" => "dashboard#new_features"
|
||||||
|
get "/whats-new" => "dashboard#new_features"
|
||||||
|
|
||||||
resources :dashboard, only: [:index] do
|
resources :dashboard, only: [:index] do
|
||||||
collection { get "problems" }
|
collection { get "problems" }
|
||||||
|
Loading…
Reference in New Issue
Block a user