mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 08:43:25 +08:00
DEV: Allow custom site activity items in the new /about page (#28400)
This commit introduces a new frontend API to add custom items to the "Site activity" section in the new /about page. The new API is called `addAboutPageActivity` and it works along side the `register_stat` serve-side API which serializes the data that the frontend API consumes. More details of how the two APIs work together is in the JSDoc comment above the API function definition. Internal topic: t/128545/9.
This commit is contained in:
parent
ccb1861ada
commit
db6eff7be9
|
@ -9,6 +9,16 @@ import i18n from "discourse-common/helpers/i18n";
|
|||
import escape from "discourse-common/lib/escape";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
const pluginActivitiesFuncs = [];
|
||||
|
||||
export function addAboutPageActivity(name, func) {
|
||||
pluginActivitiesFuncs.push({ name, func });
|
||||
}
|
||||
|
||||
export function clearAboutPageActivities() {
|
||||
pluginActivitiesFuncs.clear();
|
||||
}
|
||||
|
||||
export default class AboutPage extends Component {
|
||||
get moderatorsCount() {
|
||||
return this.args.model.moderators.length;
|
||||
|
@ -57,7 +67,7 @@ export default class AboutPage extends Component {
|
|||
}
|
||||
|
||||
get siteActivities() {
|
||||
return [
|
||||
const list = [
|
||||
{
|
||||
icon: "scroll",
|
||||
class: "topics",
|
||||
|
@ -104,6 +114,8 @@ export default class AboutPage extends Component {
|
|||
period: I18n.t("about.activities.periods.all_time"),
|
||||
},
|
||||
];
|
||||
|
||||
return list.concat(this.siteActivitiesFromPlugins());
|
||||
}
|
||||
|
||||
get contactInfo() {
|
||||
|
@ -139,6 +151,33 @@ export default class AboutPage extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
siteActivitiesFromPlugins() {
|
||||
const stats = this.args.model.stats;
|
||||
const statKeys = Object.keys(stats);
|
||||
|
||||
const configs = [];
|
||||
for (const { name, func } of pluginActivitiesFuncs) {
|
||||
let present = false;
|
||||
const periods = {};
|
||||
for (const stat of statKeys) {
|
||||
const prefix = `${name}_`;
|
||||
if (stat.startsWith(prefix)) {
|
||||
present = true;
|
||||
const period = stat.replace(prefix, "");
|
||||
periods[period] = stats[stat];
|
||||
}
|
||||
}
|
||||
if (!present) {
|
||||
continue;
|
||||
}
|
||||
const config = func(periods);
|
||||
if (config) {
|
||||
configs.push(config);
|
||||
}
|
||||
}
|
||||
return configs;
|
||||
}
|
||||
|
||||
<template>
|
||||
<section class="about__header">
|
||||
{{#if @model.banner_image}}
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
// docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version
|
||||
// using the format described at https://keepachangelog.com/en/1.0.0/.
|
||||
|
||||
export const PLUGIN_API_VERSION = "1.36.0";
|
||||
export const PLUGIN_API_VERSION = "1.37.0";
|
||||
|
||||
import $ from "jquery";
|
||||
import { h } from "virtual-dom";
|
||||
import { addAboutPageActivity } from "discourse/components/about-page";
|
||||
import { addBulkDropdownButton } from "discourse/components/bulk-select-topics-dropdown";
|
||||
import {
|
||||
addApiImageWrapperButtonClickEvent,
|
||||
|
@ -3238,6 +3239,45 @@ class PluginApi {
|
|||
registerAdminPluginConfigNav(pluginId, mode, links);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a custom site activity item in the new /about page. Requires using
|
||||
* the `register_stat` server-side API to serialize the needed data to the
|
||||
* frontend.
|
||||
*
|
||||
* ```
|
||||
* api.addAboutPageActivity("released_songs", (periods) => {
|
||||
* return {
|
||||
* icon: "guitar",
|
||||
* class: "released-songs",
|
||||
* activityText: `${periods["last_year"]} released songs`,
|
||||
* period: "in the last year",
|
||||
* };
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* The above example would require the `register_stat` server-side API to be
|
||||
* used like this:
|
||||
*
|
||||
* ```ruby
|
||||
* register_stat("released_songs", expose_via_api: true) do
|
||||
* {
|
||||
* last_year: Songs.where("released_at > ?", 1.year.ago).count,
|
||||
* last_month: Songs.where("released_at > ?", 1.month.ago).count,
|
||||
* }
|
||||
* end
|
||||
* ```
|
||||
*
|
||||
* @callback activityItemConfig
|
||||
* @param {Object} periods - an object containing the periods that the block given to the `register_stat` server-side API returns.
|
||||
* @returns {Object} - configuration object for the site activity item. The object must contain the following properties: `icon`, `class`, `activityText` and `period`.
|
||||
*
|
||||
* @param {string} name - a string that matches the string given to the `register_stat` server-side API.
|
||||
* @param {activityItemConfig} func - a callback that returns an object containing properties for the custom site activity item.
|
||||
*/
|
||||
addAboutPageActivity(name, func) {
|
||||
addAboutPageActivity(name, func);
|
||||
}
|
||||
|
||||
#deprecatedHeaderWidgetOverride(widgetName, override) {
|
||||
if (DEPRECATED_HEADER_WIDGETS.includes(widgetName)) {
|
||||
this.container.lookup("service:header").anyWidgetHeaderOverrides = true;
|
||||
|
|
|
@ -12,6 +12,7 @@ import MessageBus from "message-bus-client";
|
|||
import { resetCache as resetOneboxCache } from "pretty-text/oneboxer";
|
||||
import QUnit, { module, skip, test } from "qunit";
|
||||
import sinon from "sinon";
|
||||
import { clearAboutPageActivities } from "discourse/components/about-page";
|
||||
import {
|
||||
cleanUpComposerUploadHandler,
|
||||
cleanUpComposerUploadMarkdownResolver,
|
||||
|
@ -251,6 +252,7 @@ export function testCleanup(container, app) {
|
|||
resetAdminPluginConfigNav();
|
||||
resetTransformers();
|
||||
rollbackAllPrepends();
|
||||
clearAboutPageActivities();
|
||||
}
|
||||
|
||||
function cleanupCssGeneratorTags() {
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
import { render } from "@ember/test-helpers";
|
||||
import { module, test } from "qunit";
|
||||
import AboutPage from "discourse/components/about-page";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
|
||||
function createModelObject({
|
||||
title = "My Forums",
|
||||
admins = [],
|
||||
moderators = [],
|
||||
stats = {},
|
||||
}) {
|
||||
return {
|
||||
title,
|
||||
admins,
|
||||
moderators,
|
||||
stats,
|
||||
};
|
||||
}
|
||||
|
||||
module("Integration | Component | about-page", function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test("custom site activities registered via the plugin API", async function (assert) {
|
||||
withPluginApi("1.37.0", (api) => {
|
||||
api.addAboutPageActivity("my_custom_activity", (periods) => {
|
||||
return {
|
||||
icon: "eye",
|
||||
class: "custom-activity",
|
||||
activityText: `${periods["3_weeks"]} my custom activity`,
|
||||
period: "in the last 3 weeks",
|
||||
};
|
||||
});
|
||||
|
||||
api.addAboutPageActivity("another_custom_activity", () => null);
|
||||
});
|
||||
|
||||
const model = createModelObject({
|
||||
stats: {
|
||||
my_custom_activity_3_weeks: 342,
|
||||
my_custom_activity_1_year: 123,
|
||||
another_custom_activity_1_day: 994,
|
||||
},
|
||||
});
|
||||
|
||||
await render(<template><AboutPage @model={{model}} /></template>);
|
||||
assert
|
||||
.dom(".about__activities-item.custom-activity")
|
||||
.exists("my_custom_activity is rendered");
|
||||
assert
|
||||
.dom(".about__activities-item.custom-activity .d-icon-eye")
|
||||
.exists("icon for my_custom_activity is rendered");
|
||||
assert
|
||||
.dom(
|
||||
".about__activities-item.custom-activity .about__activities-item-count"
|
||||
)
|
||||
.hasText("342 my custom activity");
|
||||
assert
|
||||
.dom(
|
||||
".about__activities-item.custom-activity .about__activities-item-period"
|
||||
)
|
||||
.hasText("in the last 3 weeks");
|
||||
});
|
||||
});
|
|
@ -7,6 +7,10 @@ in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.37.0] - 2024-08-19
|
||||
|
||||
- Added `addAboutPageActivity` which allows plugins/TCs to register a custom site activity item in the new /about page. Requires the server-side `register_stat` plugin API.
|
||||
|
||||
## [1.36.0] - 2024-08-06
|
||||
|
||||
- Added `addLogSearchLinkClickedCallbacks` which allows plugins/TCs to register a callback when a search link is clicked and before a search log is created
|
||||
|
|
|
@ -1121,6 +1121,9 @@ class Plugin::Instance
|
|||
# group of stats is shown on the site About page in the Site Statistics
|
||||
# table. Some stats may be needed purely for reporting purposes and thus
|
||||
# do not need to be shown in the UI to admins/users.
|
||||
#
|
||||
# TODO(osama): deprecate show_in_ui when experimental_redesigned_about_page_groups
|
||||
# is removed
|
||||
def register_stat(name, show_in_ui: false, expose_via_api: false, &block)
|
||||
# We do not want to register and display the same group multiple times.
|
||||
return if DiscoursePluginRegistry.stats.any? { |stat| stat.name == name }
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { number } from "discourse/lib/formatter";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import { getOwnerWithFallback } from "discourse-common/lib/get-owner";
|
||||
import { replaceIcon } from "discourse-common/lib/icon-library";
|
||||
|
@ -163,6 +164,21 @@ export default {
|
|||
document.body.classList.remove("chat-drawer-active");
|
||||
}
|
||||
});
|
||||
|
||||
api.addAboutPageActivity("chat_messages", (periods) => {
|
||||
const count = periods["7_days"];
|
||||
if (count) {
|
||||
return {
|
||||
icon: "comment-dots",
|
||||
class: "chat-messages",
|
||||
activityText: I18n.t("about.activities.chat_messages", {
|
||||
count,
|
||||
formatted_number: number(count),
|
||||
}),
|
||||
period: I18n.t("about.activities.periods.last_7_days"),
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -33,6 +33,10 @@ en:
|
|||
chat_messages_count: "Chat messages"
|
||||
chat_channels_count: "Chat channels"
|
||||
chat_users_count: "Chat users"
|
||||
activities:
|
||||
chat_messages:
|
||||
one: "%{formatted_number} chat message"
|
||||
other: "%{formatted_number} chat messages"
|
||||
|
||||
chat:
|
||||
text_copied: Text copied to clipboard
|
||||
|
|
|
@ -18,6 +18,7 @@ register_asset "stylesheets/mobile/index.scss", :mobile
|
|||
|
||||
register_svg_icon "comments"
|
||||
register_svg_icon "comment-slash"
|
||||
register_svg_icon "comment-dots"
|
||||
register_svg_icon "lock"
|
||||
register_svg_icon "clipboard"
|
||||
register_svg_icon "file-audio"
|
||||
|
|
30
plugins/chat/spec/system/about_page_site_acitivity_spec.rb
Normal file
30
plugins/chat/spec/system/about_page_site_acitivity_spec.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
describe "Chat messages site activity in the about page", type: :system do
|
||||
fab!(:current_user) { Fabricate(:user) }
|
||||
fab!(:group) { Fabricate(:group, users: [current_user]) }
|
||||
|
||||
let(:about_page) { PageObjects::Pages::About.new }
|
||||
|
||||
before do
|
||||
chat_system_bootstrap
|
||||
SiteSetting.experimental_redesigned_about_page_groups = group.id.to_s
|
||||
sign_in(current_user)
|
||||
|
||||
Fabricate(:chat_message, created_at: 5.hours.ago)
|
||||
Fabricate(:chat_message, created_at: 2.days.ago)
|
||||
Fabricate(:chat_message, created_at: 6.days.ago)
|
||||
Fabricate(:chat_message, created_at: 9.days.ago)
|
||||
end
|
||||
|
||||
it "displays the number of chat messages in the last 7 days" do
|
||||
about_page.visit
|
||||
|
||||
expect(about_page.site_activities.custom("chat-messages")).to have_custom_count(
|
||||
I18n.t("js.about.activities.chat_messages", count: 3, formatted_number: "3"),
|
||||
)
|
||||
expect(about_page.site_activities.custom("chat-messages")).to have_custom_period(
|
||||
I18n.t("js.about.activities.periods.last_7_days"),
|
||||
)
|
||||
end
|
||||
end
|
|
@ -43,6 +43,14 @@ module PageObjects
|
|||
translation_key: "about.activities.likes",
|
||||
)
|
||||
end
|
||||
|
||||
# used by plugins
|
||||
def custom(name, translation_key: nil)
|
||||
AboutPageSiteActivityItem.new(
|
||||
container.find(".about__activities-item.#{name}"),
|
||||
translation_key:,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,6 +28,16 @@ module PageObjects
|
|||
period_element.has_text?(I18n.t("js.about.activities.periods.all_time"))
|
||||
end
|
||||
|
||||
# used by plugins
|
||||
def has_custom_count?(text)
|
||||
container.find(".about__activities-item-count").has_text?(text)
|
||||
end
|
||||
|
||||
# used by plugins
|
||||
def has_custom_period?(text)
|
||||
period_element.has_text?(text)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def period_element
|
||||
|
|
Loading…
Reference in New Issue
Block a user