UX: support for multiple datasets in one chart
This commit is contained in:
parent
51ee31b3eb
commit
9947c38e1c
|
@ -1,21 +1,22 @@
|
|||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import Report from "admin/models/report";
|
||||
import AsyncReport from "admin/mixins/async-report";
|
||||
|
||||
export default Ember.Component.extend(AsyncReport, {
|
||||
classNames: ["dashboard-table", "dashboard-inline-table", "fixed"],
|
||||
isLoading: true,
|
||||
help: null,
|
||||
helpPage: null,
|
||||
title: null,
|
||||
loadingTitle: null,
|
||||
|
||||
loadReport(report_json) {
|
||||
this._setPropertiesFromReport(Report.create(report_json));
|
||||
return Report.create(report_json);
|
||||
},
|
||||
|
||||
fetchReport() {
|
||||
this.set("isLoading", true);
|
||||
this._super();
|
||||
|
||||
let payload = { data: { async: true } };
|
||||
let payload = { data: { async: true, facets: ["total", "prev30Days"] } };
|
||||
|
||||
if (this.get("startDate")) {
|
||||
payload.data.start_date = this.get("startDate").format("YYYY-MM-DD[T]HH:mm:ss.SSSZZ");
|
||||
|
@ -29,14 +30,15 @@ export default Ember.Component.extend(AsyncReport, {
|
|||
payload.data.limit = this.get("limit");
|
||||
}
|
||||
|
||||
ajax(this.get("dataSource"), payload)
|
||||
.then((response) => {
|
||||
this.set('reportKey', response.report.report_key);
|
||||
this.loadReport(response.report);
|
||||
}).finally(() => {
|
||||
if (!Ember.isEmpty(this.get("report.data"))) {
|
||||
this.set("isLoading", false);
|
||||
};
|
||||
this.set("reports", Ember.Object.create());
|
||||
this.set("reportKeys", []);
|
||||
|
||||
return Ember.RSVP.Promise.all(this.get("dataSources").map(dataSource => {
|
||||
return ajax(dataSource, payload)
|
||||
.then(response => {
|
||||
this.set(`reports.${response.report.report_key}`, this.loadReport(response.report));
|
||||
this.get("reportKeys").pushObject(response.report.report_key);
|
||||
});
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { ajax } from "discourse/lib/ajax";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
import AsyncReport from "admin/mixins/async-report";
|
||||
import Report from "admin/models/report";
|
||||
import { number } from 'discourse/lib/formatter';
|
||||
|
@ -26,41 +25,20 @@ function collapseWeekly(data, average) {
|
|||
|
||||
export default Ember.Component.extend(AsyncReport, {
|
||||
classNames: ["dashboard-mini-chart"],
|
||||
classNameBindings: ["trend", "oneDataPoint"],
|
||||
isLoading: true,
|
||||
trend: Ember.computed.alias("report.trend"),
|
||||
oneDataPoint: false,
|
||||
backgroundColor: "rgba(200,220,240,0.3)",
|
||||
borderColor: "#08C",
|
||||
average: false,
|
||||
percent: false,
|
||||
total: 0,
|
||||
|
||||
@computed("dataSourceName")
|
||||
dataSource(dataSourceName) {
|
||||
if (dataSourceName) {
|
||||
return `/admin/reports/${dataSourceName}`;
|
||||
}
|
||||
init() {
|
||||
this._super();
|
||||
|
||||
this._colorsPool = ["rgb(0,136,204)", "rgb(235,83,148)"];
|
||||
},
|
||||
|
||||
@computed("trend")
|
||||
trendIcon(trend) {
|
||||
switch (trend) {
|
||||
case "trending-up":
|
||||
return "angle-up";
|
||||
case "trending-down":
|
||||
return "angle-down";
|
||||
case "high-trending-up":
|
||||
return "angle-double-up";
|
||||
case "high-trending-down":
|
||||
return "angle-double-down";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
pickColorAtIndex(index) {
|
||||
return this._colorsPool[index] || this._colorsPool[0];
|
||||
},
|
||||
|
||||
fetchReport() {
|
||||
this.set("isLoading", true);
|
||||
this._super();
|
||||
|
||||
let payload = {
|
||||
data: { async: true, facets: ["prev_period"] }
|
||||
|
@ -79,56 +57,56 @@ export default Ember.Component.extend(AsyncReport, {
|
|||
this._chart = null;
|
||||
}
|
||||
|
||||
this.set("report", null);
|
||||
this.set("reports", Ember.Object.create());
|
||||
this.set("reportKeys", []);
|
||||
|
||||
ajax(this.get("dataSource"), payload)
|
||||
.then((response) => {
|
||||
this.set('reportKey', response.report.report_key);
|
||||
this.loadReport(response.report);
|
||||
})
|
||||
.finally(() => {
|
||||
if (this.get("oneDataPoint")) {
|
||||
this.set("isLoading", false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Ember.isEmpty(this.get("report.data"))) {
|
||||
this.set("isLoading", false);
|
||||
this.renderReport();
|
||||
}
|
||||
return Ember.RSVP.Promise.all(this.get("dataSources").map(dataSource => {
|
||||
return ajax(dataSource, payload)
|
||||
.then(response => {
|
||||
this.set(`reports.${response.report.report_key}`, this.loadReport(response.report));
|
||||
this.get("reportKeys").pushObject(response.report.report_key);
|
||||
});
|
||||
}));
|
||||
},
|
||||
|
||||
loadReport(report) {
|
||||
if (_.isArray(report.data)) {
|
||||
loadReport(report, previousReport) {
|
||||
Report.fillMissingDates(report);
|
||||
|
||||
if (report.data && report.data.length > 40) {
|
||||
report.data = collapseWeekly(report.data, this.get("average"));
|
||||
report.data = collapseWeekly(report.data, report.average);
|
||||
}
|
||||
|
||||
const model = Report.create(report);
|
||||
this._setPropertiesFromReport(model);
|
||||
if (previousReport && previousReport.color.length) {
|
||||
report.color = previousReport.color;
|
||||
} else {
|
||||
const dataSourceNameIndex = this.get("dataSourceNames").split(",").indexOf(report.type);
|
||||
report.color = this.pickColorAtIndex(dataSourceNameIndex);
|
||||
}
|
||||
|
||||
return Report.create(report);
|
||||
},
|
||||
|
||||
renderReport() {
|
||||
if (!this.element || this.isDestroying || this.isDestroyed) { return; }
|
||||
if (this.get("oneDataPoint")) return;
|
||||
this._super();
|
||||
|
||||
Ember.run.schedule("afterRender", () => {
|
||||
const $chartCanvas = this.$(".chart-canvas");
|
||||
|
||||
if (!$chartCanvas.length) return;
|
||||
const context = $chartCanvas[0].getContext("2d");
|
||||
|
||||
const reports = _.values(this.get("reports"));
|
||||
|
||||
const labels = Ember.makeArray(reports.get("firstObject.data")).map(d => d.x);
|
||||
|
||||
const data = {
|
||||
labels: this.get("labels"),
|
||||
datasets: [{
|
||||
data: Ember.makeArray(this.get("values")),
|
||||
backgroundColor: this.get("backgroundColor"),
|
||||
borderColor: this.get("borderColor")
|
||||
}]
|
||||
labels,
|
||||
datasets: reports.map(report => {
|
||||
return {
|
||||
data: Ember.makeArray(report.data).map(d => d.y),
|
||||
backgroundColor: "rgba(200,220,240,0.3)",
|
||||
borderColor: report.color
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
if (this._chart) {
|
||||
|
@ -138,15 +116,6 @@ export default Ember.Component.extend(AsyncReport, {
|
|||
});
|
||||
},
|
||||
|
||||
_setPropertiesFromReport(report) {
|
||||
const oneDataPoint = (this.get("startDate") && this.get("endDate")) &&
|
||||
this.get("startDate").isSame(this.get("endDate"), "day");
|
||||
|
||||
report.set("average", this.get("average"));
|
||||
report.set("percent", this.get("percent"));
|
||||
this.setProperties({ oneDataPoint, report });
|
||||
},
|
||||
|
||||
_buildChartConfig(data) {
|
||||
return {
|
||||
type: "line",
|
||||
|
@ -171,6 +140,7 @@ export default Ember.Component.extend(AsyncReport, {
|
|||
}],
|
||||
xAxes: [{
|
||||
display: true,
|
||||
gridLines: { display: false },
|
||||
type: "time",
|
||||
time: {
|
||||
parser: "YYYY-MM-DD"
|
||||
|
|
|
@ -1,77 +1,101 @@
|
|||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
|
||||
export default Ember.Mixin.create({
|
||||
classNameBindings: ["isLoading"],
|
||||
|
||||
report: null,
|
||||
reports: null,
|
||||
reportKeys: null,
|
||||
isLoading: false,
|
||||
dataSourceNames: "",
|
||||
|
||||
init() {
|
||||
this._super();
|
||||
|
||||
this._channel = this.get("dataSource");
|
||||
this.set("reports", Ember.Object.create());
|
||||
this.set("reportKeys", []);
|
||||
|
||||
this._channels = this.get("dataSources");
|
||||
this._callback = (report) => {
|
||||
if (report.report_key = this.get("reportKey")) {
|
||||
if (this.get("reportKeys").includes(report.report_key)) {
|
||||
Em.run.next(() => {
|
||||
if (report.report_key = this.get("reportKey")) {
|
||||
this.loadReport(report);
|
||||
this.set("isLoading", false);
|
||||
if (this.get("reportKeys").includes(report.report_key)) {
|
||||
const previousReport = this.get(`reports.${report.report_key}`);
|
||||
this.set(`reports.${report.report_key}`, this.loadReport(report, previousReport));
|
||||
this.renderReport();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// in case we did not subscribe in time ensure we always grab the
|
||||
// last thing on the channel
|
||||
this.messageBus.subscribe(this._channel, this._callback, -2);
|
||||
this.subscribe(-2);
|
||||
},
|
||||
|
||||
subscribe(position) {
|
||||
this._channels.forEach(channel => {
|
||||
this.messageBus.subscribe(channel, this._callback, position);
|
||||
});
|
||||
},
|
||||
|
||||
unsubscribe() {
|
||||
this._channels.forEach(channel => {
|
||||
this.messageBus.unsubscribe(channel, this._callback);
|
||||
});
|
||||
},
|
||||
|
||||
@computed("dataSourceNames")
|
||||
dataSources(dataSourceNames) {
|
||||
return dataSourceNames.split(",").map(source => `/admin/reports/${source}`);
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
this._super();
|
||||
this.messageBus.unsubscribe(this._channel, this._callback);
|
||||
|
||||
this.unsubscribe();
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super();
|
||||
|
||||
Ember.run.later(this, function() {
|
||||
this.fetchReport();
|
||||
this.fetchReport()
|
||||
.finally(() => {
|
||||
this.renderReport();
|
||||
});
|
||||
}, 500);
|
||||
},
|
||||
|
||||
didUpdateAttrs() {
|
||||
this._super();
|
||||
this.fetchReport();
|
||||
this.fetchReport()
|
||||
.finally(() => {
|
||||
this.renderReport();
|
||||
});
|
||||
},
|
||||
|
||||
renderReport() {},
|
||||
renderReport() {
|
||||
if (!this.element || this.isDestroying || this.isDestroyed) return;
|
||||
|
||||
const reports = _.values(this.get("reports"));
|
||||
|
||||
if (!reports.length) return;
|
||||
|
||||
const title = reports.map(report => report.title).join(", ");
|
||||
|
||||
if (reports.map(report => report.processing).includes(true)) {
|
||||
const loading = I18n.t("conditional_loading_section.loading");
|
||||
this.set("loadingTitle", `${loading}\n\n${title}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.setProperties({ title, isLoading: false});
|
||||
},
|
||||
|
||||
loadReport() {},
|
||||
|
||||
fetchReport() {},
|
||||
|
||||
@computed("dataSourceName")
|
||||
dataSource(dataSourceName) {
|
||||
return `/admin/reports/${dataSourceName}`;
|
||||
fetchReport() {
|
||||
this.set("isLoading", true);
|
||||
this.set("loadingTitle", I18n.t("conditional_loading_section.loading"));
|
||||
},
|
||||
|
||||
@computed("report")
|
||||
labels(report) {
|
||||
if (!report) return;
|
||||
if (report.labels) {
|
||||
return Ember.makeArray(report.labels);
|
||||
} else {
|
||||
return Ember.makeArray(report.data).map(r => r.x);
|
||||
}
|
||||
},
|
||||
|
||||
@computed("report")
|
||||
values(report) {
|
||||
if (!report) return;
|
||||
return Ember.makeArray(report.data).map(r => r.y);
|
||||
},
|
||||
|
||||
_setPropertiesFromReport(report) {
|
||||
if (!this.element || this.isDestroying || this.isDestroyed) { return; }
|
||||
this.setProperties({ report });
|
||||
}
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@ import computed from 'ember-addons/ember-computed-decorators';
|
|||
|
||||
const Report = Discourse.Model.extend({
|
||||
average: false,
|
||||
percent: false,
|
||||
|
||||
@computed("type", "start_date", "end_date")
|
||||
reportUrl(type, start_date, end_date) {
|
||||
|
@ -101,7 +102,23 @@ const Report = Discourse.Model.extend({
|
|||
|
||||
@computed('data', 'currentTotal')
|
||||
currentAverage(data, total) {
|
||||
return data.length === 0 ? 0 : parseFloat((total / parseFloat(data.length)).toFixed(1));
|
||||
return Ember.makeArray(data).length === 0 ? 0 : parseFloat((total / parseFloat(data.length)).toFixed(1));
|
||||
},
|
||||
|
||||
@computed("trend")
|
||||
trendIcon(trend) {
|
||||
switch (trend) {
|
||||
case "trending-up":
|
||||
return "angle-up";
|
||||
case "trending-down":
|
||||
return "angle-down";
|
||||
case "high-trending-up":
|
||||
return "angle-double-up";
|
||||
case "high-trending-down":
|
||||
return "angle-double-down";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
@computed('prev_period', 'currentTotal', 'currentAverage')
|
||||
|
|
|
@ -1,26 +1,33 @@
|
|||
{{#conditional-loading-section isLoading=isLoading title=report.title}}
|
||||
{{#conditional-loading-section isLoading=isLoading title=loadingTitle}}
|
||||
<div class="table-title">
|
||||
<h3>{{report.title}}</h3>
|
||||
<h3>{{title}}</h3>
|
||||
|
||||
{{#if help}}
|
||||
<a href="{{helpPage}}">{{i18n help}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#each-in reports as |key report|}}
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{{#each labels as |label|}}
|
||||
{{#if report.labels}}
|
||||
{{#each report.labels as |label|}}
|
||||
<th>{{label}}</th>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
{{#each report.data as |data|}}
|
||||
<th>{{data.x}}</th>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#unless hasBlock}}
|
||||
{{#each values as |value|}}
|
||||
{{#each report.data as |data|}}
|
||||
<tr>
|
||||
<td>{{number value}}</td>
|
||||
<td>{{number data.y}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
|
@ -29,4 +36,5 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{/each-in}}
|
||||
{{/conditional-loading-section}}
|
||||
|
|
|
@ -1,35 +1,33 @@
|
|||
{{#conditional-loading-section isLoading=isLoading title=report.title}}
|
||||
<div class="chart-title">
|
||||
<h3 title={{report.description}}>
|
||||
{{#conditional-loading-section isLoading=isLoading title=loadingTitle}}
|
||||
<div class="dashboard-mini-statuses">
|
||||
{{#each-in reports as |key report|}}
|
||||
<div class="dashboard-mini-status" title="{{report.trendTitle}}">
|
||||
<span class="indicator" style="background-color:{{report.color}}"></span>
|
||||
<div class="legend">
|
||||
<h4 class="title">
|
||||
<a href="{{report.reportUrl}}">
|
||||
{{report.title}}
|
||||
</a>
|
||||
</h3>
|
||||
</h4>
|
||||
|
||||
<div class="chart-trend {{trend}}">
|
||||
{{#if average}}
|
||||
<span title="{{report.trendTitle}}">
|
||||
{{report.currentAverage}}{{if percent "%"}}
|
||||
</span>
|
||||
<div class="trend">
|
||||
<span class="value" title="{{report.trendTitle}}">
|
||||
{{#if report.average}}
|
||||
{{report.currentAverage}}
|
||||
{{else}}
|
||||
<span title="{{report.trendTitle}}">
|
||||
{{number report.currentTotal noTitle="true"}}
|
||||
</span>
|
||||
{{/if}}
|
||||
|
||||
{{#if trendIcon}}
|
||||
{{d-icon trendIcon}}
|
||||
</span>
|
||||
{{#if report.trendIcon}}
|
||||
{{d-icon report.trendIcon}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/each-in}}
|
||||
</div>
|
||||
|
||||
<div class="chart-container">
|
||||
{{#if oneDataPoint}}
|
||||
<span class="data-point">
|
||||
{{number values.lastObject.y}}
|
||||
</span>
|
||||
{{else}}
|
||||
<div class="chart-canvas-container">
|
||||
<canvas class="chart-canvas"></canvas>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/conditional-loading-section}}
|
||||
|
|
|
@ -21,38 +21,29 @@
|
|||
<div class="section-body">
|
||||
<div class="charts">
|
||||
{{dashboard-mini-chart
|
||||
dataSourceName="signups"
|
||||
dataSourceNames="signups"
|
||||
startDate=startDate
|
||||
endDate=endDate}}
|
||||
|
||||
{{dashboard-mini-chart
|
||||
dataSourceName="topics"
|
||||
dataSourceNames="topics,posts"
|
||||
startDate=startDate
|
||||
endDate=endDate}}
|
||||
|
||||
{{dashboard-mini-chart
|
||||
dataSourceName="posts"
|
||||
dataSourceNames="dau_by_mau"
|
||||
startDate=startDate
|
||||
endDate=endDate}}
|
||||
|
||||
{{dashboard-mini-chart
|
||||
dataSourceName="dau_by_mau"
|
||||
average=true
|
||||
percent=true
|
||||
dataSourceNames="daily_engaged_users"
|
||||
startDate=startDate
|
||||
endDate=endDate}}
|
||||
|
||||
{{dashboard-mini-chart
|
||||
dataSourceName="daily_engaged_users"
|
||||
average=true
|
||||
dataSourceNames="new_contributors"
|
||||
startDate=startDate
|
||||
endDate=endDate}}
|
||||
|
||||
{{dashboard-mini-chart
|
||||
dataSourceName="new_contributors"
|
||||
startDate=startDate
|
||||
endDate=endDate}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -86,19 +77,7 @@
|
|||
{{/conditional-loading-section}}
|
||||
</div>
|
||||
|
||||
{{#dashboard-inline-table dataSourceName="users_by_type" lastRefreshedAt=lastRefreshedAt as |context|}}
|
||||
<tr>
|
||||
{{#each context.report.data as |data|}}
|
||||
<td>
|
||||
<a href="/admin/users/list/{{data.key}}">
|
||||
{{number data.y}}
|
||||
</a>
|
||||
</td>
|
||||
{{/each}}
|
||||
</tr>
|
||||
{{/dashboard-inline-table}}
|
||||
|
||||
{{#dashboard-inline-table dataSourceName="users_by_trust_level" lastRefreshedAt=lastRefreshedAt as |context|}}
|
||||
{{#dashboard-inline-table dataSourceNames="users_by_trust_level,users_by_type" lastRefreshedAt=lastRefreshedAt as |context|}}
|
||||
<tr>
|
||||
{{#each context.report.data as |data|}}
|
||||
<td>
|
||||
|
@ -115,7 +94,9 @@
|
|||
<div class="durability">
|
||||
{{#if currentUser.admin}}
|
||||
<div class="backups">
|
||||
<h3 class="durability-title"><a href="/admin/backups">{{i18n "admin.dashboard.backups"}}</a></h3>
|
||||
<h3 class="durability-title">
|
||||
<a href="/admin/backups">{{d-icon "archive"}} {{i18n "admin.dashboard.backups"}}</a>
|
||||
</h3>
|
||||
<p>
|
||||
{{diskSpace.backups_used}} ({{i18n "admin.dashboard.space_free" size=diskSpace.backups_free}})
|
||||
<br />
|
||||
|
@ -125,7 +106,7 @@
|
|||
{{/if}}
|
||||
|
||||
<div class="uploads">
|
||||
<h3 class="durability-title">{{i18n "admin.dashboard.uploads"}}</h3>
|
||||
<h3 class="durability-title">{{d-icon "upload"}} {{i18n "admin.dashboard.uploads"}}</h3>
|
||||
<p>
|
||||
{{diskSpace.uploads_used}} ({{i18n "admin.dashboard.space_free" size=diskSpace.uploads_free}})
|
||||
</p>
|
||||
|
@ -153,7 +134,7 @@
|
|||
|
||||
<div class="section-column">
|
||||
{{#dashboard-inline-table
|
||||
dataSourceName="top_referred_topics"
|
||||
dataSourceNames="top_referred_topics"
|
||||
lastRefreshedAt=lastRefreshedAt
|
||||
limit=8
|
||||
as |context|}}
|
||||
|
@ -173,7 +154,7 @@
|
|||
|
||||
{{#dashboard-inline-table
|
||||
limit=8
|
||||
dataSourceName="trending_search"
|
||||
dataSourceNames="trending_search"
|
||||
isEnabled=logSearchQueriesEnabled
|
||||
disabledLabel="admin.dashboard.reports.trending_search.disabled"
|
||||
startDate=lastWeek
|
||||
|
|
|
@ -1,14 +1,7 @@
|
|||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
classNames: ["conditional-loading-section"],
|
||||
|
||||
classNameBindings: ["isLoading"],
|
||||
|
||||
isLoading: false,
|
||||
|
||||
@computed("title")
|
||||
computedTitle(title) {
|
||||
return title || I18n.t("conditional_loading_section.loading");
|
||||
}
|
||||
isLoading: false
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{{#if isLoading}}
|
||||
<span class="title">{{computedTitle}}</span>
|
||||
<span class="title">{{title}}</span>
|
||||
<div class="spinner {{size}}"></div>
|
||||
{{else}}
|
||||
{{yield}}
|
||||
|
|
|
@ -155,14 +155,69 @@
|
|||
|
||||
.charts {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.dashboard-mini-chart {
|
||||
max-width: calc(100% * (1/3));
|
||||
width: 100%;
|
||||
.dashboard-mini-statuses {
|
||||
margin-bottom: 1em;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.dashboard-mini-status {
|
||||
flex-direction: row;
|
||||
margin-right: 1em;
|
||||
display: flex;
|
||||
|
||||
.indicator {
|
||||
margin-right: .5em;
|
||||
width: .33em;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.title {
|
||||
a {color: black;}
|
||||
|
||||
font-size: $font-down-2;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.trend {
|
||||
flex-direction: row;
|
||||
|
||||
.d-icon {
|
||||
font-weight: 700;
|
||||
|
||||
&.d-icon-angle-down, &.d-icon-angle-double-down {
|
||||
color: $danger;
|
||||
}
|
||||
|
||||
&.d-icon-angle-up, &.d-icon-angle-double-up {
|
||||
color: rgb(17, 141, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-mini-chart {
|
||||
max-width: calc(100% * 1/3);
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
flex-basis: 100%;
|
||||
display: flex;
|
||||
margin-bottom: 1em;
|
||||
|
||||
.conditional-loading-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@include small-width {
|
||||
max-width: 100%;
|
||||
|
@ -207,25 +262,6 @@
|
|||
color: $danger;
|
||||
}
|
||||
}
|
||||
|
||||
&.one-data-point {
|
||||
.chart-container {
|
||||
min-height: 150px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.data-point {
|
||||
width: 100%;
|
||||
font-size: 6em;
|
||||
font-weight: bold;
|
||||
border-radius: 3px;
|
||||
background: rgba(200,220,240,0.3);
|
||||
text-align: center;
|
||||
padding: .5em 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include small-width {
|
||||
|
@ -234,11 +270,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
position: relative;
|
||||
padding: 0 1em 0 0;
|
||||
}
|
||||
|
||||
.chart-trend {
|
||||
font-size: $font-up-3;
|
||||
display: flex;
|
||||
|
@ -248,6 +279,11 @@
|
|||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.chart-canvas-container {
|
||||
position: relative;
|
||||
padding: 0 1em 0 0;
|
||||
}
|
||||
|
||||
.chart-canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
|
|
@ -15,6 +15,9 @@ module Jobs
|
|||
report.group_id = args['group_id'] if args['group_id']
|
||||
report.facets = args['facets'].map(&:to_sym) if args['facets']
|
||||
report.limit = args['limit'].to_i if args['limit']
|
||||
report.processing = false
|
||||
report.average = args[:average] || false
|
||||
report.percent = args[:percent] || false
|
||||
|
||||
Report.send("report_#{type}", report)
|
||||
json = report.as_json
|
||||
|
|
|
@ -4,7 +4,7 @@ class Report
|
|||
|
||||
attr_accessor :type, :data, :total, :prev30Days, :start_date,
|
||||
:end_date, :category_id, :group_id, :labels, :async,
|
||||
:prev_period, :facets, :limit
|
||||
:prev_period, :facets, :limit, :processing, :average, :percent
|
||||
|
||||
def self.default_days
|
||||
30
|
||||
|
@ -51,7 +51,10 @@ class Report
|
|||
group_id: group_id,
|
||||
prev30Days: self.prev30Days,
|
||||
report_key: Report.cache_key(self),
|
||||
labels: labels
|
||||
labels: labels,
|
||||
processing: self.processing,
|
||||
average: self.average,
|
||||
percent: self.percent
|
||||
}.tap do |json|
|
||||
json[:total] = total if total
|
||||
json[:prev_period] = prev_period if prev_period
|
||||
|
@ -80,6 +83,9 @@ class Report
|
|||
report.async = opts[:async] || false
|
||||
report.facets = opts[:facets] || [:total, :prev30Days]
|
||||
report.limit = opts[:limit] if opts[:limit]
|
||||
report.processing = false
|
||||
report.average = opts[:average] || false
|
||||
report.percent = opts[:percent] || false
|
||||
report_method = :"report_#{type}"
|
||||
|
||||
if respond_to?(report_method)
|
||||
|
@ -89,6 +95,7 @@ class Report
|
|||
return cached_report
|
||||
else
|
||||
Jobs.enqueue(:retrieve_report, opts.merge(report_type: type))
|
||||
report.processing = true
|
||||
end
|
||||
else
|
||||
send(report_method, report)
|
||||
|
@ -176,6 +183,8 @@ class Report
|
|||
end
|
||||
|
||||
def self.report_daily_engaged_users(report)
|
||||
report.average = true
|
||||
|
||||
report.data = []
|
||||
|
||||
data = UserAction.count_daily_engaged_users(report.start_date, report.end_date)
|
||||
|
@ -205,6 +214,9 @@ class Report
|
|||
end
|
||||
|
||||
def self.report_dau_by_mau(report)
|
||||
report.average = true
|
||||
report.percent = true
|
||||
|
||||
data_points = UserVisit.count_by_active_users(report.start_date, report.end_date)
|
||||
|
||||
report.data = []
|
||||
|
|
Loading…
Reference in New Issue