diff --git a/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 b/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 index b17b5a2345b..c1cd77b63b0 100644 --- a/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-logs-staff-action-logs.js.es6 @@ -1,51 +1,63 @@ import { exportEntity } from "discourse/lib/export-csv"; import { outputExportResult } from "discourse/lib/export-result"; 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({ loading: false, filters: null, + userHistoryActions: [], + model: null, + nextPage: 0, + lastPage: null, + filtersExists: Ember.computed.gt("filterCount", 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"), + showTable: Ember.computed.gt("model.length", 0), @computed("filters.action_name") actionFilter(name) { - if (name) { - return I18n.t("admin.logs.staff_actions.actions." + name); - } else { - return null; - } + return name ? I18n.t("admin.logs.staff_actions.actions." + name) : 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() { + if (this.lastPage && this.nextPage >= this.lastPage) { + return; + } + this.set("loading", true); - var filters = this.filters, - params = {}, - count = 0; + const page = this.nextPage; + let filters = this.filters; + let params = { page }; + let count = 0; // Don't send null values - Object.keys(filters).forEach(function(k) { - var val = filters.get(k); + Object.keys(filters).forEach(k => { + let val = filters.get(k); if (val) { params[k] = val; count += 1; @@ -55,42 +67,49 @@ export default Ember.Controller.extend({ StaffActionLog.findAll(params) .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) { - let actionTypes = result.user_history_actions.map(action => { - return { - id: 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); + this.set( + "userHistoryActions", + result.user_history_actions + .map(action => ({ + id: action.id, + action_id: action.action_id, + name: I18n.t("admin.logs.staff_actions.actions." + action.id), + name_raw: action.id + })) + .sort((a, b) => (a.name > b.name ? 1 : -1)) + ); } }) - .finally(() => { - this.set("loading", false); - }); + .finally(() => this.set("loading", false)); }, scheduleRefresh() { 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: { - clearFilter: function(key) { - var changed = {}; + filterActionIdChanged(filterActionId) { + 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 if (key === "actionFilter") { @@ -109,7 +128,7 @@ export default Ember.Controller.extend({ this.resetFilters(); }, - filterByAction: function(logItem) { + filterByAction(logItem) { this._changeFilters({ action_name: logItem.get("action_name"), 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 }); }, - filterByTargetUser: function(target_user) { + filterByTargetUser(target_user) { this._changeFilters({ target_user: target_user.username }); }, - filterBySubject: function(subject) { + filterBySubject(subject) { this._changeFilters({ subject: subject }); }, - exportStaffActionLogs: function() { + exportStaffActionLogs() { exportEntity("staff_action").then(outputExportResult); + }, + + loadMore() { + this._refresh(); } } }); diff --git a/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs b/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs index dc7438e1ec7..0c202595154 100644 --- a/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs +++ b/app/assets/javascripts/admin/templates/logs/staff-action-logs.hbs @@ -30,7 +30,7 @@ {{/if}} {{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}} {{d-button class="btn-default" action=(action "exportStaffActionLogs") label="admin.export_csv.button_text" icon="download"}} @@ -38,67 +38,71 @@
{{#staff-actions}} - {{#conditional-loading-spinner condition=loading}} - +{{#load-more selector=".staff-logs tr" action=(action "loadMore")}} + {{#if showTable}} +
- - - - - - - - + + + + + + + + - + + {{#each model as |item|}} + + + + - - - + + + + + {{/each}} + - {{#if item.target_user}} - {{#link-to 'adminUser' item.target_user}}{{avatar item.target_user imageSize="tiny"}}{{/link-to}} - {{item.target_user.username}} - {{/if}} - {{#if item.subject}} - {{item.subject}} - {{/if}} - - - - - - - {{else}} - {{i18n 'search.no_results'}} - {{/each}} - -
{{i18n 'admin.logs.staff_actions.staff_user'}}{{i18n 'admin.logs.action'}}{{i18n 'admin.logs.staff_actions.subject'}}{{i18n 'admin.logs.staff_actions.when'}}{{i18n 'admin.logs.staff_actions.details'}}{{i18n 'admin.logs.staff_actions.context'}}
{{i18n 'admin.logs.staff_actions.staff_user'}}{{i18n 'admin.logs.action'}}{{i18n 'admin.logs.staff_actions.subject'}}{{i18n 'admin.logs.staff_actions.when'}}{{i18n 'admin.logs.staff_actions.details'}}{{i18n 'admin.logs.staff_actions.context'}}
+
+ {{#if item.acting_user}} + {{#link-to 'adminUser' item.acting_user}}{{avatar item.acting_user imageSize="tiny"}}{{/link-to}} + {{item.acting_user.username}} + {{else}} + + {{d-icon "far-trash-alt"}} + + {{/if}} +
+
+ {{item.actionName}} + +
- {{#each model as |item|}} -
-
- {{#if item.acting_user}} - {{#link-to 'adminUser' item.acting_user}}{{avatar item.acting_user imageSize="tiny"}}{{/link-to}} - {{item.acting_user.username}} - {{else}} - - {{d-icon "far-trash-alt"}} - - {{/if}} -
-
- {{item.actionName}} - -
+ {{#if item.target_user}} + {{#link-to 'adminUser' item.target_user}}{{avatar item.target_user imageSize="tiny"}}{{/link-to}} + {{item.target_user.username}} + {{/if}} + {{#if item.subject}} + {{item.subject}} + {{/if}} +
+
{{age-with-tooltip item.created_at}} + {{{item.formattedDetails}}} + {{#if item.useCustomModalForDetails}} + {{d-icon "info-circle"}} {{i18n 'admin.logs.staff_actions.show'}} + {{/if}} + {{#if item.useModalForDetails}} + {{d-icon "info-circle"}} {{i18n 'admin.logs.staff_actions.show'}} + {{/if}} + {{item.context}}
{{age-with-tooltip item.created_at}} - {{{item.formattedDetails}}} - {{#if item.useCustomModalForDetails}} - {{d-icon "info-circle"}} {{i18n 'admin.logs.staff_actions.show'}} - {{/if}} - {{#if item.useModalForDetails}} - {{d-icon "info-circle"}} {{i18n 'admin.logs.staff_actions.show'}} - {{/if}} - {{item.context}}
- {{/conditional-loading-spinner}} + + {{else if loading}} + {{conditional-loading-spinner condition=loading}} + {{else}} + {{i18n 'search.no_results'}} + {{/if}} +{{/load-more}} {{/staff-actions}} diff --git a/app/controllers/admin/staff_action_logs_controller.rb b/app/controllers/admin/staff_action_logs_controller.rb index f51d60d3170..28e83dfe328 100644 --- a/app/controllers/admin/staff_action_logs_controller.rb +++ b/app/controllers/admin/staff_action_logs_controller.rb @@ -3,7 +3,7 @@ class Admin::StaffActionLogsController < Admin::AdminController 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 render json: StaffActionLogsSerializer.new({ diff --git a/app/models/user_history.rb b/app/models/user_history.rb index 7beaf1e537a..4012f1541cf 100644 --- a/app/models/user_history.rb +++ b/app/models/user_history.rb @@ -216,7 +216,16 @@ class UserHistory < ActiveRecord::Base opts[:action_id] = self.actions[opts[:action_name].to_sym] if opts[:action_name] 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 end diff --git a/spec/requests/admin/staff_action_logs_controller_spec.rb b/spec/requests/admin/staff_action_logs_controller_spec.rb index 16d59031f0d..7ced305d033 100644 --- a/spec/requests/admin/staff_action_logs_controller_spec.rb +++ b/spec/requests/admin/staff_action_logs_controller_spec.rb @@ -31,6 +31,26 @@ describe Admin::StaffActionLogsController do ) 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 let(:plugin_extended_action) { :confirmed_ham } before { UserHistory.stubs(:staff_actions).returns([plugin_extended_action]) }