mirror of
https://github.com/discourse/discourse.git
synced 2025-03-20 18:16:37 +08:00
FEATURE: Make staff action logs page support infinite loading
This commit is contained in:
parent
b510006ca8
commit
e0c821ebb0
@ -1,51 +1,63 @@
|
|||||||
import { exportEntity } from "discourse/lib/export-csv";
|
import { exportEntity } from "discourse/lib/export-csv";
|
||||||
import { outputExportResult } from "discourse/lib/export-result";
|
import { outputExportResult } from "discourse/lib/export-result";
|
||||||
import StaffActionLog from "admin/models/staff-action-log";
|
import StaffActionLog from "admin/models/staff-action-log";
|
||||||
import computed from "ember-addons/ember-computed-decorators";
|
import {
|
||||||
|
default as computed,
|
||||||
|
on
|
||||||
|
} from "ember-addons/ember-computed-decorators";
|
||||||
|
|
||||||
export default Ember.Controller.extend({
|
export default Ember.Controller.extend({
|
||||||
loading: false,
|
loading: false,
|
||||||
filters: null,
|
filters: null,
|
||||||
|
userHistoryActions: [],
|
||||||
|
model: null,
|
||||||
|
nextPage: 0,
|
||||||
|
lastPage: null,
|
||||||
|
|
||||||
filtersExists: Ember.computed.gt("filterCount", 0),
|
filtersExists: Ember.computed.gt("filterCount", 0),
|
||||||
|
showTable: Ember.computed.gt("model.length", 0),
|
||||||
init() {
|
|
||||||
this._super(...arguments);
|
|
||||||
|
|
||||||
this.userHistoryActions = [];
|
|
||||||
},
|
|
||||||
|
|
||||||
filterActionIdChanged: function() {
|
|
||||||
const filterActionId = this.filterActionId;
|
|
||||||
if (filterActionId) {
|
|
||||||
this._changeFilters({
|
|
||||||
action_name: filterActionId,
|
|
||||||
action_id: this.userHistoryActions.findBy("id", filterActionId)
|
|
||||||
.action_id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}.observes("filterActionId"),
|
|
||||||
|
|
||||||
@computed("filters.action_name")
|
@computed("filters.action_name")
|
||||||
actionFilter(name) {
|
actionFilter(name) {
|
||||||
if (name) {
|
return name ? I18n.t("admin.logs.staff_actions.actions." + name) : null;
|
||||||
return I18n.t("admin.logs.staff_actions.actions." + name);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
showInstructions: Ember.computed.gt("model.length", 0),
|
@on("init")
|
||||||
|
resetFilters() {
|
||||||
|
this.setProperties({
|
||||||
|
filters: Ember.Object.create(),
|
||||||
|
model: [],
|
||||||
|
nextPage: 0,
|
||||||
|
lastPage: null
|
||||||
|
});
|
||||||
|
this.scheduleRefresh();
|
||||||
|
},
|
||||||
|
|
||||||
|
_changeFilters(props) {
|
||||||
|
this.filters.setProperties(props);
|
||||||
|
this.setProperties({
|
||||||
|
model: [],
|
||||||
|
nextPage: 0,
|
||||||
|
lastPage: null
|
||||||
|
});
|
||||||
|
this.scheduleRefresh();
|
||||||
|
},
|
||||||
|
|
||||||
_refresh() {
|
_refresh() {
|
||||||
|
if (this.lastPage && this.nextPage >= this.lastPage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.set("loading", true);
|
this.set("loading", true);
|
||||||
|
|
||||||
var filters = this.filters,
|
const page = this.nextPage;
|
||||||
params = {},
|
let filters = this.filters;
|
||||||
count = 0;
|
let params = { page };
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
// Don't send null values
|
// Don't send null values
|
||||||
Object.keys(filters).forEach(function(k) {
|
Object.keys(filters).forEach(k => {
|
||||||
var val = filters.get(k);
|
let val = filters.get(k);
|
||||||
if (val) {
|
if (val) {
|
||||||
params[k] = val;
|
params[k] = val;
|
||||||
count += 1;
|
count += 1;
|
||||||
@ -55,42 +67,49 @@ export default Ember.Controller.extend({
|
|||||||
|
|
||||||
StaffActionLog.findAll(params)
|
StaffActionLog.findAll(params)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
this.set("model", result.staff_action_logs);
|
this.setProperties({
|
||||||
|
model: this.model.concat(result.staff_action_logs),
|
||||||
|
nextPage: page + 1
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.staff_action_logs.length === 0) {
|
||||||
|
this.set("lastPage", page);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.userHistoryActions.length === 0) {
|
if (this.userHistoryActions.length === 0) {
|
||||||
let actionTypes = result.user_history_actions.map(action => {
|
this.set(
|
||||||
return {
|
"userHistoryActions",
|
||||||
id: action.id,
|
result.user_history_actions
|
||||||
action_id: action.action_id,
|
.map(action => ({
|
||||||
name: I18n.t("admin.logs.staff_actions.actions." + action.id),
|
id: action.id,
|
||||||
name_raw: action.id
|
action_id: action.action_id,
|
||||||
};
|
name: I18n.t("admin.logs.staff_actions.actions." + action.id),
|
||||||
});
|
name_raw: action.id
|
||||||
actionTypes = _.sortBy(actionTypes, row => row.name);
|
}))
|
||||||
this.set("userHistoryActions", actionTypes);
|
.sort((a, b) => (a.name > b.name ? 1 : -1))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => this.set("loading", false));
|
||||||
this.set("loading", false);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
scheduleRefresh() {
|
scheduleRefresh() {
|
||||||
Ember.run.scheduleOnce("afterRender", this, this._refresh);
|
Ember.run.scheduleOnce("afterRender", this, this._refresh);
|
||||||
},
|
},
|
||||||
|
|
||||||
resetFilters: function() {
|
|
||||||
this.set("filters", Ember.Object.create());
|
|
||||||
this.scheduleRefresh();
|
|
||||||
}.on("init"),
|
|
||||||
|
|
||||||
_changeFilters: function(props) {
|
|
||||||
this.filters.setProperties(props);
|
|
||||||
this.scheduleRefresh();
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
clearFilter: function(key) {
|
filterActionIdChanged(filterActionId) {
|
||||||
var changed = {};
|
if (filterActionId) {
|
||||||
|
this._changeFilters({
|
||||||
|
action_name: filterActionId,
|
||||||
|
action_id: this.userHistoryActions.findBy("id", filterActionId)
|
||||||
|
.action_id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
clearFilter(key) {
|
||||||
|
let changed = {};
|
||||||
|
|
||||||
// Special case, clear all action related stuff
|
// Special case, clear all action related stuff
|
||||||
if (key === "actionFilter") {
|
if (key === "actionFilter") {
|
||||||
@ -109,7 +128,7 @@ export default Ember.Controller.extend({
|
|||||||
this.resetFilters();
|
this.resetFilters();
|
||||||
},
|
},
|
||||||
|
|
||||||
filterByAction: function(logItem) {
|
filterByAction(logItem) {
|
||||||
this._changeFilters({
|
this._changeFilters({
|
||||||
action_name: logItem.get("action_name"),
|
action_name: logItem.get("action_name"),
|
||||||
action_id: logItem.get("action"),
|
action_id: logItem.get("action"),
|
||||||
@ -117,20 +136,24 @@ export default Ember.Controller.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
filterByStaffUser: function(acting_user) {
|
filterByStaffUser(acting_user) {
|
||||||
this._changeFilters({ acting_user: acting_user.username });
|
this._changeFilters({ acting_user: acting_user.username });
|
||||||
},
|
},
|
||||||
|
|
||||||
filterByTargetUser: function(target_user) {
|
filterByTargetUser(target_user) {
|
||||||
this._changeFilters({ target_user: target_user.username });
|
this._changeFilters({ target_user: target_user.username });
|
||||||
},
|
},
|
||||||
|
|
||||||
filterBySubject: function(subject) {
|
filterBySubject(subject) {
|
||||||
this._changeFilters({ subject: subject });
|
this._changeFilters({ subject: subject });
|
||||||
},
|
},
|
||||||
|
|
||||||
exportStaffActionLogs: function() {
|
exportStaffActionLogs() {
|
||||||
exportEntity("staff_action").then(outputExportResult);
|
exportEntity("staff_action").then(outputExportResult);
|
||||||
|
},
|
||||||
|
|
||||||
|
loadMore() {
|
||||||
|
this._refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{i18n "admin.logs.staff_actions.filter"}} {{combo-box content=userHistoryActions value=filterActionId none="admin.logs.staff_actions.all"}}
|
{{i18n "admin.logs.staff_actions.filter"}} {{combo-box content=userHistoryActions value=filterActionId none="admin.logs.staff_actions.all" onSelect=(action "filterActionIdChanged")}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{d-button class="btn-default" action=(action "exportStaffActionLogs") label="admin.export_csv.button_text" icon="download"}}
|
{{d-button class="btn-default" action=(action "exportStaffActionLogs") label="admin.export_csv.button_text" icon="download"}}
|
||||||
@ -38,67 +38,71 @@
|
|||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
{{#staff-actions}}
|
{{#staff-actions}}
|
||||||
{{#conditional-loading-spinner condition=loading}}
|
|
||||||
|
|
||||||
<table class='table staff-logs grid'>
|
{{#load-more selector=".staff-logs tr" action=(action "loadMore")}}
|
||||||
|
{{#if showTable}}
|
||||||
|
<table class='table staff-logs grid'>
|
||||||
|
|
||||||
<thead>
|
<thead>
|
||||||
<th>{{i18n 'admin.logs.staff_actions.staff_user'}}</th>
|
<th>{{i18n 'admin.logs.staff_actions.staff_user'}}</th>
|
||||||
<th>{{i18n 'admin.logs.action'}}</th>
|
<th>{{i18n 'admin.logs.action'}}</th>
|
||||||
<th>{{i18n 'admin.logs.staff_actions.subject'}}</th>
|
<th>{{i18n 'admin.logs.staff_actions.subject'}}</th>
|
||||||
<th>{{i18n 'admin.logs.staff_actions.when'}}</th>
|
<th>{{i18n 'admin.logs.staff_actions.when'}}</th>
|
||||||
<th>{{i18n 'admin.logs.staff_actions.details'}}</th>
|
<th>{{i18n 'admin.logs.staff_actions.details'}}</th>
|
||||||
<th>{{i18n 'admin.logs.staff_actions.context'}}</th>
|
<th>{{i18n 'admin.logs.staff_actions.context'}}</th>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
|
{{#each model as |item|}}
|
||||||
|
<tr class='admin-list-item'>
|
||||||
|
<td class="staff-users">
|
||||||
|
<div class="staff-user">
|
||||||
|
{{#if item.acting_user}}
|
||||||
|
{{#link-to 'adminUser' item.acting_user}}{{avatar item.acting_user imageSize="tiny"}}{{/link-to}}
|
||||||
|
<a {{action "filterByStaffUser" item.acting_user}}>{{item.acting_user.username}}</a>
|
||||||
|
{{else}}
|
||||||
|
<span class="deleted-user" title="{{i18n 'admin.user.deleted'}}">
|
||||||
|
{{d-icon "far-trash-alt"}}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="col value action">
|
||||||
|
<a {{action "filterByAction" item}}>{{item.actionName}}</a>
|
||||||
|
</td>
|
||||||
|
<td class="col value subject">
|
||||||
|
<div class="subject">
|
||||||
|
|
||||||
{{#each model as |item|}}
|
{{#if item.target_user}}
|
||||||
<tr class='admin-list-item'>
|
{{#link-to 'adminUser' item.target_user}}{{avatar item.target_user imageSize="tiny"}}{{/link-to}}
|
||||||
<td class="staff-users">
|
<a {{action "filterByTargetUser" item.target_user}}>{{item.target_user.username}}</a>
|
||||||
<div class="staff-user">
|
{{/if}}
|
||||||
{{#if item.acting_user}}
|
{{#if item.subject}}
|
||||||
{{#link-to 'adminUser' item.acting_user}}{{avatar item.acting_user imageSize="tiny"}}{{/link-to}}
|
<a {{action "filterBySubject" item.subject}} title={{item.subject}}>{{item.subject}}</a>
|
||||||
<a {{action "filterByStaffUser" item.acting_user}}>{{item.acting_user.username}}</a>
|
{{/if}}
|
||||||
{{else}}
|
</div>
|
||||||
<span class="deleted-user" title="{{i18n 'admin.user.deleted'}}">
|
</td>
|
||||||
{{d-icon "far-trash-alt"}}
|
<td class="col value created-at">{{age-with-tooltip item.created_at}}</td>
|
||||||
</span>
|
<td class="col value details">
|
||||||
{{/if}}
|
{{{item.formattedDetails}}}
|
||||||
</div>
|
{{#if item.useCustomModalForDetails}}
|
||||||
</td>
|
<a {{action "showCustomDetailsModal" item}}>{{d-icon "info-circle"}} {{i18n 'admin.logs.staff_actions.show'}}</a>
|
||||||
<td class="col value action">
|
{{/if}}
|
||||||
<a {{action "filterByAction" item}}>{{item.actionName}}</a>
|
{{#if item.useModalForDetails}}
|
||||||
</td>
|
<a {{action "showDetailsModal" item}}>{{d-icon "info-circle"}} {{i18n 'admin.logs.staff_actions.show'}}</a>
|
||||||
<td class="col value subject">
|
{{/if}}
|
||||||
<div class="subject">
|
</td>
|
||||||
|
<td class="col value context">{{item.context}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
|
||||||
{{#if item.target_user}}
|
</table>
|
||||||
{{#link-to 'adminUser' item.target_user}}{{avatar item.target_user imageSize="tiny"}}{{/link-to}}
|
{{else if loading}}
|
||||||
<a {{action "filterByTargetUser" item.target_user}}>{{item.target_user.username}}</a>
|
{{conditional-loading-spinner condition=loading}}
|
||||||
{{/if}}
|
{{else}}
|
||||||
{{#if item.subject}}
|
{{i18n 'search.no_results'}}
|
||||||
<a {{action "filterBySubject" item.subject}} title={{item.subject}}>{{item.subject}}</a>
|
{{/if}}
|
||||||
{{/if}}
|
{{/load-more}}
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="col value created-at">{{age-with-tooltip item.created_at}}</td>
|
|
||||||
<td class="col value details">
|
|
||||||
{{{item.formattedDetails}}}
|
|
||||||
{{#if item.useCustomModalForDetails}}
|
|
||||||
<a {{action "showCustomDetailsModal" item}}>{{d-icon "info-circle"}} {{i18n 'admin.logs.staff_actions.show'}}</a>
|
|
||||||
{{/if}}
|
|
||||||
{{#if item.useModalForDetails}}
|
|
||||||
<a {{action "showDetailsModal" item}}>{{d-icon "info-circle"}} {{i18n 'admin.logs.staff_actions.show'}}</a>
|
|
||||||
{{/if}}
|
|
||||||
</td>
|
|
||||||
<td class="col value context">{{item.context}}</td>
|
|
||||||
</tr>
|
|
||||||
{{else}}
|
|
||||||
{{i18n 'search.no_results'}}
|
|
||||||
{{/each}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{{/conditional-loading-spinner}}
|
|
||||||
|
|
||||||
{{/staff-actions}}
|
{{/staff-actions}}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
class Admin::StaffActionLogsController < Admin::AdminController
|
class Admin::StaffActionLogsController < Admin::AdminController
|
||||||
|
|
||||||
def index
|
def index
|
||||||
filters = params.slice(*UserHistory.staff_filters)
|
filters = params.slice(*UserHistory.staff_filters + [:page, :limit])
|
||||||
|
|
||||||
staff_action_logs = UserHistory.staff_action_records(current_user, filters).to_a
|
staff_action_logs = UserHistory.staff_action_records(current_user, filters).to_a
|
||||||
render json: StaffActionLogsSerializer.new({
|
render json: StaffActionLogsSerializer.new({
|
||||||
|
@ -216,7 +216,16 @@ class UserHistory < ActiveRecord::Base
|
|||||||
opts[:action_id] = self.actions[opts[:action_name].to_sym] if opts[:action_name]
|
opts[:action_id] = self.actions[opts[:action_name].to_sym] if opts[:action_name]
|
||||||
end
|
end
|
||||||
|
|
||||||
query = self.with_filters(opts.slice(*staff_filters)).only_staff_actions.limit(200).order('id DESC').includes(:acting_user, :target_user)
|
page = (opts[:page] || 0).to_i
|
||||||
|
page_size = (opts[:limit] || 200).to_i
|
||||||
|
|
||||||
|
query = self
|
||||||
|
.with_filters(opts.slice(*staff_filters))
|
||||||
|
.only_staff_actions
|
||||||
|
.limit(page_size)
|
||||||
|
.offset(page * page_size)
|
||||||
|
.order('id DESC')
|
||||||
|
.includes(:acting_user, :target_user)
|
||||||
query = query.where(admin_only: false) unless viewer && viewer.admin?
|
query = query.where(admin_only: false) unless viewer && viewer.admin?
|
||||||
query
|
query
|
||||||
end
|
end
|
||||||
|
@ -31,6 +31,26 @@ describe Admin::StaffActionLogsController do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'generates logs with pages' do
|
||||||
|
1.upto(4).each do |idx|
|
||||||
|
StaffActionLogger.new(Discourse.system_user).log_site_setting_change("title", "value #{idx - 1}", "value #{idx}")
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/admin/logs/staff_action_logs.json", params: { limit: 3 }
|
||||||
|
|
||||||
|
json = JSON.parse(response.body)
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(json["staff_action_logs"].length).to eq(3)
|
||||||
|
expect(json["staff_action_logs"][0]["new_value"]).to eq("value 4")
|
||||||
|
|
||||||
|
get "/admin/logs/staff_action_logs.json", params: { limit: 3, page: 1 }
|
||||||
|
|
||||||
|
json = JSON.parse(response.body)
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(json["staff_action_logs"].length).to eq(1)
|
||||||
|
expect(json["staff_action_logs"][0]["new_value"]).to eq("value 1")
|
||||||
|
end
|
||||||
|
|
||||||
context 'When staff actions are extended' do
|
context 'When staff actions are extended' do
|
||||||
let(:plugin_extended_action) { :confirmed_ham }
|
let(:plugin_extended_action) { :confirmed_ham }
|
||||||
before { UserHistory.stubs(:staff_actions).returns([plugin_extended_action]) }
|
before { UserHistory.stubs(:staff_actions).returns([plugin_extended_action]) }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user