FIX: makes dashboard more resilient to errors (#6217)
This commit is an attempt to limit cases where the dashboard will generate a full exception page and also make it easier to track the error.
This commit is contained in:
parent
7d8286e7ad
commit
2b2a506a7b
|
@ -110,7 +110,9 @@ export default Ember.Component.extend({
|
||||||
unregisterTooltip($(".info[data-tooltip]"));
|
unregisterTooltip($(".info[data-tooltip]"));
|
||||||
},
|
},
|
||||||
|
|
||||||
showTimeoutError: Ember.computed.alias("model.timeout"),
|
showError: Ember.computed.or("showTimeoutError", "showExceptionError"),
|
||||||
|
showTimeoutError: Ember.computed.equal("model.error", "timeout"),
|
||||||
|
showExceptionError: Ember.computed.equal("model.error", "exception"),
|
||||||
|
|
||||||
hasData: Ember.computed.notEmpty("model.data"),
|
hasData: Ember.computed.notEmpty("model.data"),
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,9 @@ export default Ember.Controller.extend(PeriodComputationMixin, {
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
dashboardFetchedAt: new Date(),
|
dashboardFetchedAt: new Date(),
|
||||||
model: adminDashboardNextModel,
|
model: adminDashboardNextModel,
|
||||||
reports: adminDashboardNextModel.reports.map(x => Report.create(x))
|
reports: Ember.makeArray(adminDashboardNextModel.reports).map(x =>
|
||||||
|
Report.create(x)
|
||||||
|
)
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { renderAvatar } from "discourse/helpers/user-avatar";
|
||||||
|
|
||||||
// Change this line each time report format change
|
// Change this line each time report format change
|
||||||
// and you want to ensure cache is reset
|
// and you want to ensure cache is reset
|
||||||
export const SCHEMA_VERSION = 1;
|
export const SCHEMA_VERSION = 2;
|
||||||
|
|
||||||
const Report = Discourse.Model.extend({
|
const Report = Discourse.Model.extend({
|
||||||
average: false,
|
average: false,
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
{{#if isEnabled}}
|
{{#if isEnabled}}
|
||||||
{{#conditional-loading-section isLoading=isLoading}}
|
{{#conditional-loading-section isLoading=isLoading}}
|
||||||
{{#if showTimeoutError}}
|
|
||||||
<div class="alert alert-error">
|
|
||||||
{{i18n "admin.dashboard.timeout_error"}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{#if showHeader}}
|
{{#if showHeader}}
|
||||||
<div class="report-header">
|
<div class="report-header">
|
||||||
{{#if showTitle}}
|
{{#if showTitle}}
|
||||||
|
@ -66,17 +60,29 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<div class="report-body">
|
<div class="report-body">
|
||||||
{{#unless showTimeoutError}}
|
{{#unless showError}}
|
||||||
{{#if hasData}}
|
{{#if hasData}}
|
||||||
{{#if currentMode}}
|
{{#if currentMode}}
|
||||||
{{component modeComponent model=model options=options}}
|
{{component modeComponent model=model options=options}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="alert alert-info no-data-alert">
|
<div class="alert alert-info no-data">
|
||||||
{{d-icon "pie-chart"}}
|
{{d-icon "pie-chart"}}
|
||||||
{{i18n 'admin.dashboard.reports.no_data'}}
|
{{i18n 'admin.dashboard.reports.no_data'}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{else}}
|
||||||
|
{{#if showTimeoutError}}
|
||||||
|
<div class="alert alert-error report-error timeout">
|
||||||
|
{{i18n "admin.dashboard.timeout_error"}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if showExceptionError}}
|
||||||
|
<div class="alert alert-error report-error exception">
|
||||||
|
{{i18n "admin.dashboard.exception_error"}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
|
||||||
{{#if showFilteringUI}}
|
{{#if showFilteringUI}}
|
||||||
|
|
|
@ -1,12 +1,22 @@
|
||||||
.admin-report {
|
.admin-report {
|
||||||
.no-data-alert {
|
.report-error,
|
||||||
background: $secondary;
|
.no-data {
|
||||||
border: 1px solid $primary-low;
|
width: 100%;
|
||||||
color: $primary-low-mid;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 3em;
|
padding: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-error {
|
||||||
|
color: $danger;
|
||||||
|
border: 1px solid $danger;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data {
|
||||||
|
background: $secondary;
|
||||||
|
border: 1px solid $primary-low;
|
||||||
|
color: $primary-low-mid;
|
||||||
|
|
||||||
.d-icon-pie-chart {
|
.d-icon-pie-chart {
|
||||||
color: currentColor;
|
color: currentColor;
|
||||||
|
|
|
@ -5,10 +5,6 @@ class AdminDashboardNextData
|
||||||
@opts = opts
|
@opts = opts
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.fetch_stats
|
|
||||||
self.class.new.as_json
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.fetch_stats
|
def self.fetch_stats
|
||||||
new.as_json
|
new.as_json
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,14 +3,14 @@ require_dependency 'topic_subtype'
|
||||||
class Report
|
class Report
|
||||||
# Change this line each time report format change
|
# Change this line each time report format change
|
||||||
# and you want to ensure cache is reset
|
# and you want to ensure cache is reset
|
||||||
SCHEMA_VERSION = 1
|
SCHEMA_VERSION = 2
|
||||||
|
|
||||||
attr_accessor :type, :data, :total, :prev30Days, :start_date,
|
attr_accessor :type, :data, :total, :prev30Days, :start_date,
|
||||||
:end_date, :category_id, :group_id, :labels, :async,
|
:end_date, :category_id, :group_id, :labels, :async,
|
||||||
:prev_period, :facets, :limit, :processing, :average, :percent,
|
:prev_period, :facets, :limit, :processing, :average, :percent,
|
||||||
:higher_is_better, :icon, :modes, :category_filtering,
|
:higher_is_better, :icon, :modes, :category_filtering,
|
||||||
:group_filtering, :prev_data, :prev_start_date, :prev_end_date,
|
:group_filtering, :prev_data, :prev_start_date, :prev_end_date,
|
||||||
:dates_filtering, :timeout
|
:dates_filtering, :error
|
||||||
|
|
||||||
def self.default_days
|
def self.default_days
|
||||||
30
|
30
|
||||||
|
@ -113,7 +113,7 @@ class Report
|
||||||
group_filtering: self.group_filtering,
|
group_filtering: self.group_filtering,
|
||||||
modes: self.modes,
|
modes: self.modes,
|
||||||
}.tap do |json|
|
}.tap do |json|
|
||||||
json[:timeout] = self.timeout if self.timeout
|
json[:error] = self.error if self.error
|
||||||
json[:total] = self.total if self.total
|
json[:total] = self.total if self.total
|
||||||
json[:prev_period] = self.prev_period if self.prev_period
|
json[:prev_period] = self.prev_period if self.prev_period
|
||||||
json[:prev30Days] = self.prev30Days if self.prev30Days
|
json[:prev30Days] = self.prev30Days if self.prev30Days
|
||||||
|
@ -160,6 +160,7 @@ class Report
|
||||||
def self.find(type, opts = nil)
|
def self.find(type, opts = nil)
|
||||||
clear_cache
|
clear_cache
|
||||||
|
|
||||||
|
begin
|
||||||
report = _get(type, opts)
|
report = _get(type, opts)
|
||||||
report_method = :"report_#{type}"
|
report_method = :"report_#{type}"
|
||||||
|
|
||||||
|
@ -170,6 +171,14 @@ class Report
|
||||||
else
|
else
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
rescue Exception => e
|
||||||
|
report.error = :exception
|
||||||
|
|
||||||
|
# given reports can be added by plugins we don’t want dashboard failures
|
||||||
|
# on report computation, however we do want to log which report is provoking
|
||||||
|
# an error
|
||||||
|
Rails.logger.error("Error while computing report `#{report.type}`: #{e.message}")
|
||||||
|
end
|
||||||
|
|
||||||
report
|
report
|
||||||
end
|
end
|
||||||
|
@ -596,7 +605,7 @@ class Report
|
||||||
|
|
||||||
options = { end_date: report.end_date, start_date: report.start_date, limit: report.limit || 8 }
|
options = { end_date: report.end_date, start_date: report.start_date, limit: report.limit || 8 }
|
||||||
result = nil
|
result = nil
|
||||||
report.timeout = wrap_slow_query do
|
report.error = wrap_slow_query do
|
||||||
result = IncomingLinksReport.find(:top_referred_topics, options)
|
result = IncomingLinksReport.find(:top_referred_topics, options)
|
||||||
report.data = result.data
|
report.data = result.data
|
||||||
end
|
end
|
||||||
|
@ -622,7 +631,7 @@ class Report
|
||||||
|
|
||||||
options = { end_date: report.end_date, start_date: report.start_date, limit: report.limit || 8 }
|
options = { end_date: report.end_date, start_date: report.start_date, limit: report.limit || 8 }
|
||||||
result = nil
|
result = nil
|
||||||
report.timeout = wrap_slow_query do
|
report.error = wrap_slow_query do
|
||||||
result = IncomingLinksReport.find(:top_traffic_sources, options)
|
result = IncomingLinksReport.find(:top_traffic_sources, options)
|
||||||
report.data = result.data
|
report.data = result.data
|
||||||
end
|
end
|
||||||
|
|
|
@ -2792,6 +2792,7 @@ en:
|
||||||
moderation_tab: "Moderation"
|
moderation_tab: "Moderation"
|
||||||
disabled: Disabled
|
disabled: Disabled
|
||||||
timeout_error: Sorry, query is taking too long, please pick a shorter interval
|
timeout_error: Sorry, query is taking too long, please pick a shorter interval
|
||||||
|
exception_error: Sorry, an error occurred while executing the query
|
||||||
|
|
||||||
reports:
|
reports:
|
||||||
trend_title: "%{percent} change. Currently %{current}, was %{prev} in previous period."
|
trend_title: "%{percent} change. Currently %{current}, was %{prev} in previous period."
|
||||||
|
|
|
@ -475,6 +475,8 @@ describe Report do
|
||||||
let(:post) { Fabricate(:post) }
|
let(:post) { Fabricate(:post) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
freeze_time
|
||||||
|
|
||||||
PostAction.act(flagger, post, PostActionType.types[:spam], message: 'bad')
|
PostAction.act(flagger, post, PostActionType.types[:spam], message: 'bad')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -507,6 +509,8 @@ describe Report do
|
||||||
let(:post) { Fabricate(:post) }
|
let(:post) { Fabricate(:post) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
freeze_time
|
||||||
|
|
||||||
post.revise(editor, raw: 'updated body', edit_reason: 'not cool')
|
post.revise(editor, raw: 'updated body', edit_reason: 'not cool')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -674,4 +678,38 @@ describe Report do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "exception report" do
|
||||||
|
before(:each) do
|
||||||
|
class Report
|
||||||
|
def self.report_exception_test(report)
|
||||||
|
report.data = x
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a report with an exception error" do
|
||||||
|
report = Report.find("exception_test")
|
||||||
|
expect(report.error).to eq(:exception)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "timeout report" do
|
||||||
|
before(:each) do
|
||||||
|
freeze_time
|
||||||
|
|
||||||
|
class Report
|
||||||
|
def self.report_timeout_test(report)
|
||||||
|
report.error = wrap_slow_query(1) do
|
||||||
|
ActiveRecord::Base.connection.execute("SELECT pg_sleep(5)")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns a report with a timeout error" do
|
||||||
|
report = Report.find("timeout_test")
|
||||||
|
expect(report.error).to eq(:timeout)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -112,7 +112,7 @@ componentTest("timeout", {
|
||||||
template: "{{admin-report dataSourceName='signups_timeout'}}",
|
template: "{{admin-report dataSourceName='signups_timeout'}}",
|
||||||
|
|
||||||
test(assert) {
|
test(assert) {
|
||||||
assert.ok(exists(".alert-error"), "it displays a timeout error");
|
assert.ok(exists(".alert-error.timeout"), "it displays a timeout error");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -120,6 +120,14 @@ componentTest("no data", {
|
||||||
template: "{{admin-report dataSourceName='posts'}}",
|
template: "{{admin-report dataSourceName='posts'}}",
|
||||||
|
|
||||||
test(assert) {
|
test(assert) {
|
||||||
assert.ok(exists(".no-data-alert"), "it displays a no data alert");
|
assert.ok(exists(".no-data"), "it displays a no data alert");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
componentTest("exception", {
|
||||||
|
template: "{{admin-report dataSourceName='signups_exception'}}",
|
||||||
|
|
||||||
|
test(assert) {
|
||||||
|
assert.ok(exists(".alert-error.exception"), "it displays an error");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import signups from "fixtures/signups";
|
||||||
|
|
||||||
|
const signupsExceptionKey = "/admin/reports/signups_exception";
|
||||||
|
const signupsKey = "/admin/reports/signups";
|
||||||
|
|
||||||
|
let fixture = {};
|
||||||
|
|
||||||
|
fixture[signupsExceptionKey] = JSON.parse(JSON.stringify(signups[signupsKey]));
|
||||||
|
fixture[signupsExceptionKey].report.error = "exception";
|
||||||
|
|
||||||
|
export default fixture;
|
|
@ -1,35 +1,11 @@
|
||||||
export default {
|
import signups from "fixtures/signups";
|
||||||
"/admin/reports/signups_timeout": {
|
|
||||||
report: {
|
const signupsTimeoutKey = "/admin/reports/signups_timeout";
|
||||||
type: "signups",
|
const signupsKey = "/admin/reports/signups";
|
||||||
title: "Signups",
|
|
||||||
xaxis: "Day",
|
let fixture = {};
|
||||||
yaxis: "Number of signups",
|
|
||||||
description: "New account registrations for this period",
|
fixture[signupsTimeoutKey] = JSON.parse(JSON.stringify(signups[signupsKey]));
|
||||||
data: null,
|
fixture[signupsTimeoutKey].report.error = "timeout";
|
||||||
start_date: "2018-06-16T00:00:00Z",
|
|
||||||
end_date: "2018-07-16T23:59:59Z",
|
export default fixture;
|
||||||
prev_data: null,
|
|
||||||
prev_start_date: "2018-05-17T00:00:00Z",
|
|
||||||
prev_end_date: "2018-06-17T00:00:00Z",
|
|
||||||
category_id: null,
|
|
||||||
group_id: null,
|
|
||||||
prev30Days: null,
|
|
||||||
dates_filtering: true,
|
|
||||||
report_key: "reports:signups_timeout::20180616:20180716::[:prev_period]:",
|
|
||||||
labels: [
|
|
||||||
{ type: "date", properties: ["x"], title: "Day" },
|
|
||||||
{ type: "number", properties: ["y"], title: "Count" }
|
|
||||||
],
|
|
||||||
processing: false,
|
|
||||||
average: false,
|
|
||||||
percent: false,
|
|
||||||
higher_is_better: true,
|
|
||||||
category_filtering: false,
|
|
||||||
group_filtering: true,
|
|
||||||
modes: ["table", "chart"],
|
|
||||||
prev_period: 961,
|
|
||||||
timeout: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
Loading…
Reference in New Issue