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:
Krzysztof Kotlarek 2024-03-20 14:23:18 +11:00 committed by GitHub
parent 34a14112a7
commit 043117ca13
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 206 additions and 94 deletions

View File

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

View File

@ -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");
}
}

View File

@ -158,6 +158,7 @@ export default function () {
"adminReports",
{ path: "/reports", resetNamespace: true },
function () {
this.route("index", { path: "/" });
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`

View File

@ -1,33 +1 @@
<ConditionalLoadingSpinner @condition={{this.isLoading}}>
<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>
<AdminReports />

View File

@ -0,0 +1 @@
<AdminReports />

View File

@ -0,0 +1,2 @@
<h2>{{i18n "admin.new_features.title"}}</h2>
<DashboardNewFeatures />

View File

@ -13,7 +13,7 @@ export const ADMIN_NAV_MAP = [
},
{
name: "admin_whats_new",
route: "admin.dashboardNewFeatures",
route: "admin.whatsNew",
label: "admin.account.sidebar_link.whats_new",
icon: "gift",
},
@ -25,7 +25,7 @@ export const ADMIN_NAV_MAP = [
links: [
{
name: "admin_all_reports",
route: "admin.dashboardReports",
route: "adminReports.index",
label: "admin.reports.sidebar_link.all",
icon: "chart-bar",
},

View File

@ -1,9 +1,13 @@
import { visit } from "@ember/test-helpers";
import { click, fillIn, visit } from "@ember/test-helpers";
import { test } from "qunit";
import { AUTO_GROUPS } from "discourse/lib/constants";
import { withPluginApi } from "discourse/lib/plugin-api";
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) {
needs.user({
@ -92,6 +96,36 @@ acceptance("Admin Sidebar - Sections", function (needs) {
"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) {

View File

@ -93,31 +93,25 @@ acceptance("Dashboard", function (needs) {
await visit("/admin");
await click(".dashboard .navigation-item.reports .navigation-link");
assert.strictEqual(
count(".dashboard .reports-index.section .reports-list .report"),
1
);
assert.strictEqual(count(".dashboard .admin-reports-list__report"), 1);
await fillIn(".dashboard .filter-reports-input", "flags");
await fillIn(".dashboard .admin-reports-header__filter", "flags");
assert.strictEqual(
count(".dashboard .reports-index.section .reports-list .report"),
0
);
assert.strictEqual(count(".dashboard .admin-reports-list__report"), 0);
await click(".dashboard .navigation-item.security .navigation-link");
await click(".dashboard .navigation-item.reports .navigation-link");
assert.strictEqual(
count(".dashboard .reports-index.section .reports-list .report"),
count(".dashboard .admin-reports-list__report"),
1,
"navigating back and forth resets filter"
);
await fillIn(".dashboard .filter-reports-input", "activities");
await fillIn(".dashboard .admin-reports-header__filter", "activities");
assert.strictEqual(
count(".dashboard .reports-index.section .reports-list .report"),
count(".dashboard .admin-reports-list__report"),
1,
"filter is case insensitive"
);

View File

@ -13,17 +13,21 @@ acceptance("Reports", function (needs) {
test("Visit reports page", async function (assert) {
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(
report.querySelector(".report-title").innerHTML.trim(),
report
.querySelector(".admin-reports-list__report-title")
.innerHTML.trim(),
"My report"
);
assert.strictEqual(
report.querySelector(".report-description").innerHTML.trim(),
report
.querySelector(".admin-reports-list__report-description")
.innerHTML.trim(),
"List of my activities"
);
});

View File

@ -202,3 +202,51 @@
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);
}
}
}

View File

@ -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 {
display: flex;
flex-wrap: wrap;

View File

@ -4813,6 +4813,7 @@ en:
title: "Discourse Admin"
moderator: "Moderator"
back_to_forum: "Back to Forum"
filter_reports: Filter reports
tags:
remove_muted_tags_from_latest:
@ -4827,6 +4828,8 @@ en:
sidebar_link:
all: "All"
new_features:
title: "What's new"
dashboard:
title: "Dashboard"
last_updated: "Dashboard updated:"
@ -4884,7 +4887,6 @@ en:
exception_error: Sorry, an error occurred while executing the query
too_many_requests: Youve performed this action too many times. Please wait before trying again.
not_found_error: Sorry, this report doesnt exist
filter_reports: Filter reports
custom_date_range: Custom date range
reports:

View File

@ -325,6 +325,7 @@ Discourse::Application.routes.draw do
get "dashboard/security" => "dashboard#security"
get "dashboard/reports" => "dashboard#reports"
get "dashboard/whats-new" => "dashboard#new_features"
get "/whats-new" => "dashboard#new_features"
resources :dashboard, only: [:index] do
collection { get "problems" }