mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FEATURE: Poll breakdown 2.0 (#10345)
The poll breakdown modal replaces the grouped pie charts feature.
Includes:
* MODAL: Untangle `onSelectPanel`
Previously modal-tab component would call on click the onSelectPanel callback with itself (modal-tab) as `this` which severely limited its usefulness. Now showModal binds the callback to its controller.
"The PR includes a fix/change to d-modal (b7f6ec6
) that hasn't been extracted to a separate PR because it's not currently possible to test a change like this in abstract, i.e. with dynamically created controllers/components in tests. The percentage/count toggle test for the poll breakdown feature is essentially a test for that d-modal modification."
This commit is contained in:
parent
76c02cac65
commit
cd4f251891
@ -20,6 +20,10 @@ export default Component.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
click() {
|
click() {
|
||||||
this.onSelectPanel(this.panel);
|
this.set("selectedPanel", this.panel);
|
||||||
|
|
||||||
|
if (this.onSelectPanel) {
|
||||||
|
this.onSelectPanel(this.panel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -50,7 +50,10 @@ export default function(name, opts) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (controller.actions.onSelectPanel) {
|
if (controller.actions.onSelectPanel) {
|
||||||
modalController.set("onSelectPanel", controller.actions.onSelectPanel);
|
modalController.set(
|
||||||
|
"onSelectPanel",
|
||||||
|
controller.actions.onSelectPanel.bind(controller)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
modalController.set(
|
modalController.set(
|
||||||
|
@ -18,10 +18,6 @@ export default Mixin.create({
|
|||||||
closeModal() {
|
closeModal() {
|
||||||
this.modal.send("closeModal");
|
this.modal.send("closeModal");
|
||||||
this.set("panels", []);
|
this.set("panels", []);
|
||||||
},
|
|
||||||
|
|
||||||
onSelectPanel(panel) {
|
|
||||||
this.set("selectedPanel", panel);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -148,7 +148,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:not(.history-modal) {
|
&:not(.history-modal) {
|
||||||
.modal-body:not(.reorder-categories):not(.poll-ui-builder) {
|
.modal-body:not(.reorder-categories):not(.poll-ui-builder):not(.poll-breakdown) {
|
||||||
max-height: 80vh !important;
|
max-height: 80vh !important;
|
||||||
@media screen and (max-height: 500px) {
|
@media screen and (max-height: 500px) {
|
||||||
max-height: 65vh !important;
|
max-height: 65vh !important;
|
||||||
|
@ -69,6 +69,9 @@ task 'javascript:update' do
|
|||||||
}, {
|
}, {
|
||||||
source: 'chart.js/dist/Chart.min.js',
|
source: 'chart.js/dist/Chart.min.js',
|
||||||
public: true
|
public: true
|
||||||
|
}, {
|
||||||
|
source: 'chartjs-plugin-datalabels/dist/chartjs-plugin-datalabels.min.js',
|
||||||
|
public: true
|
||||||
}, {
|
}, {
|
||||||
source: 'magnific-popup/dist/jquery.magnific-popup.min.js',
|
source: 'magnific-popup/dist/jquery.magnific-popup.min.js',
|
||||||
public: true
|
public: true
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"bootbox": "3.2.0",
|
"bootbox": "3.2.0",
|
||||||
"bootstrap": "v3.4.1",
|
"bootstrap": "v3.4.1",
|
||||||
"chart.js": "2.9.3",
|
"chart.js": "2.9.3",
|
||||||
|
"chartjs-plugin-datalabels": "^0.7.0",
|
||||||
"eslint-plugin-lodash": "^6.0.0",
|
"eslint-plugin-lodash": "^6.0.0",
|
||||||
"favcount": "https://github.com/chrishunt/favcount",
|
"favcount": "https://github.com/chrishunt/favcount",
|
||||||
"handlebars": "^4.7.0",
|
"handlebars": "^4.7.0",
|
||||||
|
@ -0,0 +1,182 @@
|
|||||||
|
import I18n from "I18n";
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import { mapBy } from "@ember/object/computed";
|
||||||
|
import { htmlSafe } from "@ember/template";
|
||||||
|
import { PIE_CHART_TYPE } from "discourse/plugins/poll/controllers/poll-ui-builder";
|
||||||
|
import { getColors } from "discourse/plugins/poll/lib/chart-colors";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
// Arguments:
|
||||||
|
group: null,
|
||||||
|
options: null,
|
||||||
|
displayMode: null,
|
||||||
|
highlightedOption: null,
|
||||||
|
setHighlightedOption: null,
|
||||||
|
|
||||||
|
classNames: "poll-breakdown-chart-container",
|
||||||
|
|
||||||
|
_optionToSlice: null,
|
||||||
|
_previousHighlightedSliceIndex: null,
|
||||||
|
_previousDisplayMode: null,
|
||||||
|
|
||||||
|
data: mapBy("options", "votes"),
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._super(...arguments);
|
||||||
|
this._optionToSlice = {};
|
||||||
|
},
|
||||||
|
|
||||||
|
didInsertElement() {
|
||||||
|
this._super(...arguments);
|
||||||
|
|
||||||
|
const canvas = this.element.querySelector("canvas");
|
||||||
|
this._chart = new window.Chart(canvas.getContext("2d"), this.chartConfig);
|
||||||
|
},
|
||||||
|
|
||||||
|
didReceiveAttrs() {
|
||||||
|
this._super(...arguments);
|
||||||
|
|
||||||
|
if (this._chart) {
|
||||||
|
this._updateDisplayMode();
|
||||||
|
this._updateHighlight();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
willDestroy() {
|
||||||
|
this._super(...arguments);
|
||||||
|
|
||||||
|
if (this._chart) {
|
||||||
|
this._chart.destroy();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("optionColors", "index")
|
||||||
|
colorStyle(optionColors, index) {
|
||||||
|
return htmlSafe(`background: ${optionColors[index]};`);
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("data", "displayMode")
|
||||||
|
chartConfig(data, displayMode) {
|
||||||
|
const transformedData = [];
|
||||||
|
let counter = 0;
|
||||||
|
|
||||||
|
this._optionToSlice = {};
|
||||||
|
|
||||||
|
data.forEach((votes, index) => {
|
||||||
|
if (votes > 0) {
|
||||||
|
transformedData.push(votes);
|
||||||
|
this._optionToSlice[index] = counter++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalVotes = transformedData.reduce((sum, votes) => sum + votes, 0);
|
||||||
|
const colors = getColors(data.length).filter(
|
||||||
|
(color, index) => data[index] > 0
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: PIE_CHART_TYPE,
|
||||||
|
plugins: [window.ChartDataLabels],
|
||||||
|
data: {
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: transformedData,
|
||||||
|
backgroundColor: colors,
|
||||||
|
// TODO: It's a workaround for Chart.js' terrible hover styling.
|
||||||
|
// It will break on non-white backgrounds.
|
||||||
|
// Should be updated after #10341 lands
|
||||||
|
hoverBorderColor: "#fff"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
plugins: {
|
||||||
|
datalabels: {
|
||||||
|
color: "#333",
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.5)",
|
||||||
|
borderRadius: 2,
|
||||||
|
font: {
|
||||||
|
family: getComputedStyle(document.body).fontFamily,
|
||||||
|
size: 16
|
||||||
|
},
|
||||||
|
padding: {
|
||||||
|
top: 2,
|
||||||
|
right: 6,
|
||||||
|
bottom: 2,
|
||||||
|
left: 6
|
||||||
|
},
|
||||||
|
formatter(votes) {
|
||||||
|
if (displayMode !== "percentage") {
|
||||||
|
return votes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const percent = I18n.toNumber((votes / totalVotes) * 100.0, {
|
||||||
|
precision: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
return `${percent}%`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responsive: true,
|
||||||
|
aspectRatio: 1.1,
|
||||||
|
animation: { duration: 0 },
|
||||||
|
tooltips: false,
|
||||||
|
onHover: (event, activeElements) => {
|
||||||
|
if (!activeElements.length) {
|
||||||
|
this.setHighlightedOption(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sliceIndex = activeElements[0]._index;
|
||||||
|
const optionIndex = Object.keys(this._optionToSlice).find(
|
||||||
|
option => this._optionToSlice[option] === sliceIndex
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clear the array to avoid issues in Chart.js
|
||||||
|
activeElements.length = 0;
|
||||||
|
|
||||||
|
this.setHighlightedOption(Number(optionIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateDisplayMode() {
|
||||||
|
if (this.displayMode !== this._previousDisplayMode) {
|
||||||
|
const config = this.chartConfig;
|
||||||
|
this._chart.data.datasets = config.data.datasets;
|
||||||
|
this._chart.options = config.options;
|
||||||
|
|
||||||
|
this._chart.update();
|
||||||
|
this._previousDisplayMode = this.displayMode;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateHighlight() {
|
||||||
|
const meta = this._chart.getDatasetMeta(0);
|
||||||
|
|
||||||
|
if (this._previousHighlightedSliceIndex !== null) {
|
||||||
|
const slice = meta.data[this._previousHighlightedSliceIndex];
|
||||||
|
meta.controller.removeHoverStyle(slice);
|
||||||
|
this._chart.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.highlightedOption === null) {
|
||||||
|
this._previousHighlightedSliceIndex = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sliceIndex = this._optionToSlice[this.highlightedOption];
|
||||||
|
if (typeof sliceIndex === "undefined") {
|
||||||
|
this._previousHighlightedSliceIndex = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const slice = meta.data[sliceIndex];
|
||||||
|
this._previousHighlightedSliceIndex = sliceIndex;
|
||||||
|
meta.controller.setHoverStyle(slice);
|
||||||
|
this._chart.draw();
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,61 @@
|
|||||||
|
import I18n from "I18n";
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { equal } from "@ember/object/computed";
|
||||||
|
import { htmlSafe } from "@ember/template";
|
||||||
|
import { propertyEqual } from "discourse/lib/computed";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
import { getColors } from "discourse/plugins/poll/lib/chart-colors";
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
// Arguments:
|
||||||
|
option: null,
|
||||||
|
index: null,
|
||||||
|
totalVotes: null,
|
||||||
|
optionsCount: null,
|
||||||
|
displayMode: null,
|
||||||
|
highlightedOption: null,
|
||||||
|
onMouseOver: null,
|
||||||
|
onMouseOut: null,
|
||||||
|
|
||||||
|
tagName: "",
|
||||||
|
|
||||||
|
highlighted: propertyEqual("highlightedOption", "index"),
|
||||||
|
showPercentage: equal("displayMode", "percentage"),
|
||||||
|
|
||||||
|
@discourseComputed("option.votes", "totalVotes")
|
||||||
|
percent(votes, total) {
|
||||||
|
return I18n.toNumber((votes / total) * 100.0, { precision: 1 });
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("optionsCount")
|
||||||
|
optionColors(optionsCount) {
|
||||||
|
return getColors(optionsCount);
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("highlighted")
|
||||||
|
colorBackgroundStyle(highlighted) {
|
||||||
|
if (highlighted) {
|
||||||
|
// TODO: Use CSS variables (#10341)
|
||||||
|
return htmlSafe("background: rgba(0, 0, 0, 0.1);");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("highlighted", "optionColors", "index")
|
||||||
|
colorPreviewStyle(highlighted, optionColors, index) {
|
||||||
|
const color = highlighted
|
||||||
|
? window.Chart.helpers.getHoverColor(optionColors[index])
|
||||||
|
: optionColors[index];
|
||||||
|
|
||||||
|
return htmlSafe(`background: ${color};`);
|
||||||
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
onHover(active) {
|
||||||
|
if (active) {
|
||||||
|
this.onMouseOver();
|
||||||
|
} else {
|
||||||
|
this.onMouseOut();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,83 @@
|
|||||||
|
import I18n from "I18n";
|
||||||
|
import Controller from "@ember/controller";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { classify } from "@ember/string";
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
import loadScript from "discourse/lib/load-script";
|
||||||
|
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
|
export default Controller.extend(ModalFunctionality, {
|
||||||
|
model: null,
|
||||||
|
charts: null,
|
||||||
|
groupedBy: null,
|
||||||
|
highlightedOption: null,
|
||||||
|
displayMode: "percentage",
|
||||||
|
|
||||||
|
@discourseComputed("model.groupableUserFields")
|
||||||
|
groupableUserFields(fields) {
|
||||||
|
return fields.map(field => {
|
||||||
|
const transformed = field.split("_").filter(Boolean);
|
||||||
|
|
||||||
|
if (transformed.length > 1) {
|
||||||
|
transformed[0] = classify(transformed[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { id: field, label: transformed.join(" ") };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
@discourseComputed("model.poll.options")
|
||||||
|
totalVotes(options) {
|
||||||
|
return options.reduce((sum, option) => sum + option.votes, 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow() {
|
||||||
|
this.set("charts", null);
|
||||||
|
this.set("displayMode", "percentage");
|
||||||
|
this.set("groupedBy", this.model.groupableUserFields[0]);
|
||||||
|
|
||||||
|
loadScript("/javascripts/Chart.min.js")
|
||||||
|
.then(() => loadScript("/javascripts/chartjs-plugin-datalabels.min.js"))
|
||||||
|
.then(() => {
|
||||||
|
window.Chart.plugins.unregister(window.ChartDataLabels);
|
||||||
|
this.fetchGroupedPollData();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchGroupedPollData() {
|
||||||
|
return ajax("/polls/grouped_poll_results.json", {
|
||||||
|
data: {
|
||||||
|
post_id: this.model.post.id,
|
||||||
|
poll_name: this.model.poll.name,
|
||||||
|
user_field_name: this.groupedBy
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
if (error) {
|
||||||
|
popupAjaxError(error);
|
||||||
|
} else {
|
||||||
|
bootbox.alert(I18n.t("poll.error_while_fetching_voters"));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(result => {
|
||||||
|
if (this.isDestroying || this.isDestroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set("charts", result.grouped_results);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
setGrouping(value) {
|
||||||
|
this.set("groupedBy", value);
|
||||||
|
this.fetchGroupedPollData();
|
||||||
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
onSelectPanel(panel) {
|
||||||
|
this.set("displayMode", panel.id);
|
||||||
|
}
|
||||||
|
});
|
@ -1,7 +1,7 @@
|
|||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
import Controller from "@ember/controller";
|
import Controller from "@ember/controller";
|
||||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
|
||||||
import EmberObject from "@ember/object";
|
import EmberObject from "@ember/object";
|
||||||
|
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
export const BAR_CHART_TYPE = "bar";
|
export const BAR_CHART_TYPE = "bar";
|
||||||
export const PIE_CHART_TYPE = "pie";
|
export const PIE_CHART_TYPE = "pie";
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
<label class="poll-breakdown-chart-label">{{@group}}</label>
|
||||||
|
<canvas class="poll-breakdown-chart-chart"></canvas>
|
@ -0,0 +1,17 @@
|
|||||||
|
<li
|
||||||
|
class="poll-breakdown-option"
|
||||||
|
style={{this.colorBackgroundStyle}}
|
||||||
|
{{on "mouseover" @onMouseOver}}
|
||||||
|
{{on "mouseout" @onMouseOut}}
|
||||||
|
>
|
||||||
|
<span class="poll-breakdown-option-color" style={{this.colorPreviewStyle}}></span>
|
||||||
|
|
||||||
|
<span class="poll-breakdown-option-count">
|
||||||
|
{{#if showPercentage}}
|
||||||
|
{{this.percent}}%
|
||||||
|
{{else}}
|
||||||
|
{{@option.votes}}
|
||||||
|
{{/if}}
|
||||||
|
</span>
|
||||||
|
<span class="poll-breakdown-option-text">{{{@option.html}}}</span>
|
||||||
|
</li>
|
@ -0,0 +1,49 @@
|
|||||||
|
{{#d-modal-body title="poll.breakdown.title"}}
|
||||||
|
<div class="poll-breakdown-sidebar">
|
||||||
|
{{!-- TODO: replace with the (optional) poll title --}}
|
||||||
|
<p class="poll-breakdown-title">{{this.model.post.topic.title}}</p>
|
||||||
|
|
||||||
|
<div class="poll-breakdown-total-votes">{{i18n "poll.breakdown.votes" count=this.model.poll.voters}}</div>
|
||||||
|
|
||||||
|
<ul class="poll-breakdown-options">
|
||||||
|
{{#each this.model.poll.options as |option index|}}
|
||||||
|
{{poll-breakdown-option
|
||||||
|
option=option
|
||||||
|
index=index
|
||||||
|
totalVotes=this.totalVotes
|
||||||
|
optionsCount=this.model.poll.options.length
|
||||||
|
displayMode=this.displayMode
|
||||||
|
highlightedOption=this.highlightedOption
|
||||||
|
onMouseOver=(fn (mut this.highlightedOption) index)
|
||||||
|
onMouseOut=(fn (mut this.highlightedOption) null)
|
||||||
|
}}
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="poll-breakdown-body">
|
||||||
|
<div class="poll-breakdown-body-header">
|
||||||
|
<label class="poll-breakdown-body-header-label">{{i18n "poll.breakdown.breakdown"}}</label>
|
||||||
|
|
||||||
|
{{combo-box
|
||||||
|
content=this.groupableUserFields
|
||||||
|
value=this.groupedBy
|
||||||
|
nameProperty="label"
|
||||||
|
class="poll-breakdown-dropdown"
|
||||||
|
onChange=(action this.setGrouping)
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="poll-breakdown-charts">
|
||||||
|
{{#each this.charts as |chart|}}
|
||||||
|
{{poll-breakdown-chart
|
||||||
|
group=(get chart "group")
|
||||||
|
options=(get chart "options")
|
||||||
|
displayMode=this.displayMode
|
||||||
|
highlightedOption=this.highlightedOption
|
||||||
|
setHighlightedOption=(fn (mut this.highlightedOption))
|
||||||
|
}}
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/d-modal-body}}
|
@ -1,6 +1,6 @@
|
|||||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
|
||||||
import showModal from "discourse/lib/show-modal";
|
import showModal from "discourse/lib/show-modal";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
function initializePollUIBuilder(api) {
|
function initializePollUIBuilder(api) {
|
||||||
api.modifyClass("controller:composer", {
|
api.modifyClass("controller:composer", {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import EmberObject from "@ember/object";
|
import EmberObject from "@ember/object";
|
||||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
import { observes } from "discourse-common/utils/decorators";
|
|
||||||
import { getRegister } from "discourse-common/lib/get-owner";
|
|
||||||
import WidgetGlue from "discourse/widgets/glue";
|
import WidgetGlue from "discourse/widgets/glue";
|
||||||
|
import { getRegister } from "discourse-common/lib/get-owner";
|
||||||
|
import { observes } from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
function initializePolls(api) {
|
function initializePolls(api) {
|
||||||
const register = getRegister(api);
|
const register = getRegister(api);
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
import { createWidget } from "discourse/widgets/widget";
|
|
||||||
import { h } from "virtual-dom";
|
import { h } from "virtual-dom";
|
||||||
import { iconNode } from "discourse-common/lib/icon-library";
|
|
||||||
import RawHtml from "discourse/widgets/raw-html";
|
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
import evenRound from "discourse/plugins/poll/lib/even-round";
|
|
||||||
import { avatarFor } from "discourse/widgets/post";
|
|
||||||
import round from "discourse/lib/round";
|
|
||||||
import { relativeAge } from "discourse/lib/formatter";
|
import { relativeAge } from "discourse/lib/formatter";
|
||||||
import loadScript from "discourse/lib/load-script";
|
import loadScript from "discourse/lib/load-script";
|
||||||
import { getColors } from "../lib/chart-colors";
|
import round from "discourse/lib/round";
|
||||||
import { classify } from "@ember/string";
|
import showModal from "discourse/lib/show-modal";
|
||||||
import { PIE_CHART_TYPE } from "../controllers/poll-ui-builder";
|
import { avatarFor } from "discourse/widgets/post";
|
||||||
|
import RawHtml from "discourse/widgets/raw-html";
|
||||||
|
import { createWidget } from "discourse/widgets/widget";
|
||||||
|
import { iconNode } from "discourse-common/lib/icon-library";
|
||||||
|
import { PIE_CHART_TYPE } from "discourse/plugins/poll/controllers/poll-ui-builder";
|
||||||
|
import { getColors } from "discourse/plugins/poll/lib/chart-colors";
|
||||||
|
import evenRound from "discourse/plugins/poll/lib/even-round";
|
||||||
|
|
||||||
function optionHtml(option) {
|
function optionHtml(option) {
|
||||||
const $node = $(`<span>${option.html}</span>`);
|
const $node = $(`<span>${option.html}</span>`);
|
||||||
@ -453,120 +453,6 @@ createWidget("discourse-poll-info", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function transformUserFieldToLabel(fieldName) {
|
|
||||||
let transformed = fieldName.split("_").filter(Boolean);
|
|
||||||
if (transformed.length > 1) {
|
|
||||||
transformed[0] = classify(transformed[0]);
|
|
||||||
}
|
|
||||||
return transformed.join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
createWidget("discourse-poll-grouped-pies", {
|
|
||||||
tagName: "div.poll-grouped-pies",
|
|
||||||
buildAttributes(attrs) {
|
|
||||||
return {
|
|
||||||
id: `poll-results-grouped-pie-charts-${attrs.id}`
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
html(attrs) {
|
|
||||||
const fields = Object.assign({}, attrs.groupableUserFields);
|
|
||||||
const fieldSelectId = `field-select-${attrs.id}`;
|
|
||||||
attrs.groupedBy = attrs.groupedBy || fields[0];
|
|
||||||
|
|
||||||
let contents = [];
|
|
||||||
|
|
||||||
const btn = this.attach("button", {
|
|
||||||
className: "btn-default poll-group-by-toggle",
|
|
||||||
label: "poll.ungroup-results.label",
|
|
||||||
title: "poll.ungroup-results.title",
|
|
||||||
icon: "far-eye-slash",
|
|
||||||
action: "toggleGroupedPieCharts"
|
|
||||||
});
|
|
||||||
const select = h(
|
|
||||||
`select#${fieldSelectId}.poll-group-by-selector`,
|
|
||||||
{ value: attrs.groupBy },
|
|
||||||
attrs.groupableUserFields.map(field => {
|
|
||||||
return h("option", { value: field }, transformUserFieldToLabel(field));
|
|
||||||
})
|
|
||||||
);
|
|
||||||
contents.push(h("div.poll-grouped-pies-controls", [btn, select]));
|
|
||||||
|
|
||||||
ajax("/polls/grouped_poll_results.json", {
|
|
||||||
data: {
|
|
||||||
post_id: attrs.post.id,
|
|
||||||
poll_name: attrs.poll.name,
|
|
||||||
user_field_name: attrs.groupedBy
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
if (error) {
|
|
||||||
popupAjaxError(error);
|
|
||||||
} else {
|
|
||||||
bootbox.alert(I18n.t("poll.error_while_fetching_voters"));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(result => {
|
|
||||||
let groupBySelect = document.getElementById(fieldSelectId);
|
|
||||||
if (!groupBySelect) return;
|
|
||||||
|
|
||||||
groupBySelect.value = attrs.groupedBy;
|
|
||||||
const parent = document.getElementById(
|
|
||||||
`poll-results-grouped-pie-charts-${attrs.id}`
|
|
||||||
);
|
|
||||||
|
|
||||||
for (
|
|
||||||
let chartIdx = 0;
|
|
||||||
chartIdx < result.grouped_results.length;
|
|
||||||
chartIdx++
|
|
||||||
) {
|
|
||||||
const data = result.grouped_results[chartIdx].options.mapBy("votes");
|
|
||||||
const labels = result.grouped_results[chartIdx].options.mapBy("html");
|
|
||||||
const chartConfig = pieChartConfig(data, labels, {
|
|
||||||
aspectRatio: 1.2
|
|
||||||
});
|
|
||||||
const canvasId = `pie-${attrs.id}-${chartIdx}`;
|
|
||||||
let el = document.querySelector(`#${canvasId}`);
|
|
||||||
if (!el) {
|
|
||||||
const container = document.createElement("div");
|
|
||||||
container.classList.add("poll-grouped-pie-container");
|
|
||||||
|
|
||||||
const label = document.createElement("label");
|
|
||||||
label.classList.add("poll-pie-label");
|
|
||||||
label.textContent = result.grouped_results[chartIdx].group;
|
|
||||||
|
|
||||||
const canvas = document.createElement("canvas");
|
|
||||||
canvas.classList.add(`poll-grouped-pie-${attrs.id}`);
|
|
||||||
canvas.id = canvasId;
|
|
||||||
|
|
||||||
container.appendChild(label);
|
|
||||||
container.appendChild(canvas);
|
|
||||||
parent.appendChild(container);
|
|
||||||
// eslint-disable-next-line
|
|
||||||
new Chart(canvas.getContext("2d"), chartConfig);
|
|
||||||
} else {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
Chart.helpers.each(Chart.instances, function(instance) {
|
|
||||||
if (instance.chart.canvas.id === canvasId && el.$chartjs) {
|
|
||||||
instance.destroy();
|
|
||||||
// eslint-disable-next-line
|
|
||||||
new Chart(el.getContext("2d"), chartConfig);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return contents;
|
|
||||||
},
|
|
||||||
|
|
||||||
click(e) {
|
|
||||||
let select = $(e.target).closest("select");
|
|
||||||
if (select.length) {
|
|
||||||
this.sendWidgetAction("refreshCharts", select[0].value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function clearPieChart(id) {
|
function clearPieChart(id) {
|
||||||
let el = document.querySelector(`#poll-results-chart-${id}`);
|
let el = document.querySelector(`#poll-results-chart-${id}`);
|
||||||
el && el.parentNode.removeChild(el);
|
el && el.parentNode.removeChild(el);
|
||||||
@ -607,27 +493,23 @@ createWidget("discourse-poll-pie-chart", {
|
|||||||
return contents;
|
return contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
let btn;
|
if (attrs.groupableUserFields.length) {
|
||||||
let chart;
|
const button = this.attach("button", {
|
||||||
if (attrs.groupResults && attrs.groupableUserFields.length > 0) {
|
className: "btn-default poll-show-breakdown",
|
||||||
chart = this.attach("discourse-poll-grouped-pies", attrs);
|
label: "poll.group-results.label",
|
||||||
clearPieChart(attrs.id);
|
title: "poll.group-results.title",
|
||||||
} else {
|
icon: "far-eye",
|
||||||
if (attrs.groupableUserFields.length) {
|
action: "showBreakdown"
|
||||||
btn = this.attach("button", {
|
});
|
||||||
className: "btn-default poll-group-by-toggle",
|
|
||||||
label: "poll.group-results.label",
|
|
||||||
title: "poll.group-results.title",
|
|
||||||
icon: "far-eye",
|
|
||||||
action: "toggleGroupedPieCharts"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
chart = this.attach("discourse-poll-pie-canvas", attrs);
|
contents.push(button);
|
||||||
}
|
}
|
||||||
contents.push(btn);
|
|
||||||
|
const chart = this.attach("discourse-poll-pie-canvas", attrs);
|
||||||
contents.push(chart);
|
contents.push(chart);
|
||||||
|
|
||||||
contents.push(h(`div#poll-results-legend-${attrs.id}.pie-chart-legends`));
|
contents.push(h(`div#poll-results-legend-${attrs.id}.pie-chart-legends`));
|
||||||
|
|
||||||
return contents;
|
return contents;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1072,19 +954,13 @@ export default createWidget("discourse-poll", {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleGroupedPieCharts() {
|
showBreakdown() {
|
||||||
this.attrs.groupResults = !this.attrs.groupResults;
|
showModal("poll-breakdown", {
|
||||||
},
|
model: this.attrs,
|
||||||
|
panels: [
|
||||||
refreshCharts(newGroupedByValue) {
|
{ id: "percentage", title: "poll.breakdown.percentage" },
|
||||||
let el = document.getElementById(
|
{ id: "count", title: "poll.breakdown.count" }
|
||||||
`poll-results-grouped-pie-charts-${this.attrs.id}`
|
]
|
||||||
);
|
});
|
||||||
Array.from(el.getElementsByClassName("poll-grouped-pie-container")).forEach(
|
|
||||||
container => {
|
|
||||||
el.removeChild(container);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
this.attrs.groupedBy = newGroupedByValue;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
116
plugins/poll/assets/stylesheets/common/poll-breakdown.scss
Normal file
116
plugins/poll/assets/stylesheets/common/poll-breakdown.scss
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
.poll-breakdown-modal {
|
||||||
|
.modal-inner-container {
|
||||||
|
max-width: unset;
|
||||||
|
width: 90vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-tabs {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
display: grid;
|
||||||
|
height: 80vh;
|
||||||
|
grid: auto-flow / 1fr 2fr;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-breakdown-sidebar {
|
||||||
|
background: var(--primary-very-low);
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-breakdown-title {
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-breakdown-total-votes {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-breakdown-options {
|
||||||
|
display: grid;
|
||||||
|
list-style: none;
|
||||||
|
margin: 1.5rem 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-breakdown-option {
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
column-gap: 0.66rem;
|
||||||
|
cursor: default;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2.5rem 1fr;
|
||||||
|
row-gap: 0.1rem;
|
||||||
|
justify-content: start;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-breakdown-option-color {
|
||||||
|
align-self: end;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||||
|
grid-column: 1;
|
||||||
|
height: 0.6rem;
|
||||||
|
justify-self: center;
|
||||||
|
width: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-breakdown-option-count {
|
||||||
|
align-self: start;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
grid-column: 1;
|
||||||
|
justify-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-breakdown-option-text {
|
||||||
|
grid-column: 2;
|
||||||
|
grid-row: 1/3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-breakdown-body {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-breakdown-body-header {
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid var(--primary-low);
|
||||||
|
display: flex;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-breakdown-body-header-label {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-breakdown-dropdown {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-breakdown-charts {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(33.3%, 0.33fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-breakdown-chart-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 1rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poll-breakdown-chart-label {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
@ -149,41 +149,22 @@ div.poll {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.poll-results-chart {
|
.poll-results-chart {
|
||||||
height: 310px;
|
height: 320px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.poll-group-by-toggle {
|
.poll-show-breakdown {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.poll-group-by-selector {
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.poll-grouped-pie-container {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
padding: 15px 0;
|
|
||||||
|
|
||||||
.poll-pie-label {
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div.poll.pie {
|
div.poll.pie {
|
||||||
.poll-container {
|
.poll-container {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 310px;
|
height: 320px;
|
||||||
max-height: 310px;
|
max-height: 320px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
.poll-grouped-pie-container {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.poll-info {
|
.poll-info {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -49,11 +49,8 @@ div.poll {
|
|||||||
div.poll.pie {
|
div.poll.pie {
|
||||||
.poll-container {
|
.poll-container {
|
||||||
width: calc(100% - 190px);
|
width: calc(100% - 190px);
|
||||||
|
|
||||||
.poll-grouped-pie-container {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.poll-info {
|
.poll-info {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 150px;
|
width: 150px;
|
||||||
|
@ -28,15 +28,15 @@ div.poll {
|
|||||||
margin-left: 0.25em;
|
margin-left: 0.25em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.poll-show-breakdown {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div.poll.pie {
|
div.poll.pie {
|
||||||
.poll-container {
|
.poll-container {
|
||||||
width: calc(100% - 30px);
|
width: calc(100% - 30px);
|
||||||
border-bottom: 1px solid var(--primary-low);
|
border-bottom: 1px solid var(--primary-low);
|
||||||
|
|
||||||
.poll-grouped-pie-container {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,10 +52,6 @@ en:
|
|||||||
title: "Group votes by user field"
|
title: "Group votes by user field"
|
||||||
label: "Show breakdown"
|
label: "Show breakdown"
|
||||||
|
|
||||||
ungroup-results:
|
|
||||||
title: "Combine all votes"
|
|
||||||
label: "Hide breakdown"
|
|
||||||
|
|
||||||
export-results:
|
export-results:
|
||||||
title: "Export the poll results"
|
title: "Export the poll results"
|
||||||
label: "Export"
|
label: "Export"
|
||||||
@ -74,6 +70,13 @@ en:
|
|||||||
closes_in: "Closes in <strong>%{timeLeft}</strong>."
|
closes_in: "Closes in <strong>%{timeLeft}</strong>."
|
||||||
age: "Closed <strong>%{age}</strong>"
|
age: "Closed <strong>%{age}</strong>"
|
||||||
|
|
||||||
|
breakdown:
|
||||||
|
title: "Poll results"
|
||||||
|
votes: "%{count} votes"
|
||||||
|
breakdown: "Breakdown"
|
||||||
|
percentage: "Percentage"
|
||||||
|
count: "Count"
|
||||||
|
|
||||||
error_while_toggling_status: "Sorry, there was an error toggling the status of this poll."
|
error_while_toggling_status: "Sorry, there was an error toggling the status of this poll."
|
||||||
error_while_casting_votes: "Sorry, there was an error casting your votes."
|
error_while_casting_votes: "Sorry, there was an error casting your votes."
|
||||||
error_while_fetching_voters: "Sorry, there was an error displaying the voters."
|
error_while_fetching_voters: "Sorry, there was an error displaying the voters."
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
register_asset "stylesheets/common/poll.scss"
|
register_asset "stylesheets/common/poll.scss"
|
||||||
register_asset "stylesheets/common/poll-ui-builder.scss"
|
register_asset "stylesheets/common/poll-ui-builder.scss"
|
||||||
|
register_asset "stylesheets/common/poll-breakdown.scss"
|
||||||
register_asset "stylesheets/desktop/poll.scss", :desktop
|
register_asset "stylesheets/desktop/poll.scss", :desktop
|
||||||
register_asset "stylesheets/mobile/poll.scss", :mobile
|
register_asset "stylesheets/mobile/poll.scss", :mobile
|
||||||
register_asset "stylesheets/mobile/poll-ui-builder.scss", :mobile
|
register_asset "stylesheets/mobile/poll-ui-builder.scss", :mobile
|
||||||
|
@ -0,0 +1,119 @@
|
|||||||
|
import { acceptance } from "helpers/qunit-helpers";
|
||||||
|
import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer";
|
||||||
|
import { Promise } from "rsvp";
|
||||||
|
|
||||||
|
acceptance("Poll breakdown", {
|
||||||
|
loggedIn: true,
|
||||||
|
settings: { poll_enabled: true, poll_groupable_user_fields: "something" },
|
||||||
|
beforeEach() {
|
||||||
|
clearPopupMenuOptionsCallback();
|
||||||
|
},
|
||||||
|
pretend(server, helper) {
|
||||||
|
server.get("/polls/grouped_poll_results.json", () => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
resolve(
|
||||||
|
helper.response({
|
||||||
|
grouped_results: [
|
||||||
|
{
|
||||||
|
group: "Engineering",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
digest: "687a1ccf3c6a260f9aeeb7f68a1d463c",
|
||||||
|
html: "This Is",
|
||||||
|
votes: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
digest: "9377906763a1221d31d656ea0c4a4495",
|
||||||
|
html: "A test for sure",
|
||||||
|
votes: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
digest: "ecf47c65a85a0bb20029072b1b721977",
|
||||||
|
html: "Why not give it some more",
|
||||||
|
votes: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group: "Marketing",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
digest: "687a1ccf3c6a260f9aeeb7f68a1d463c",
|
||||||
|
html: "This Is",
|
||||||
|
votes: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
digest: "9377906763a1221d31d656ea0c4a4495",
|
||||||
|
html: "A test for sure",
|
||||||
|
votes: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
digest: "ecf47c65a85a0bb20029072b1b721977",
|
||||||
|
html: "Why not give it some more",
|
||||||
|
votes: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Displaying the poll breakdown modal", async assert => {
|
||||||
|
await visit("/t/-/topic_with_pie_chart_poll");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find(".poll-show-breakdown").text(),
|
||||||
|
"Show breakdown",
|
||||||
|
"shows the breakdown button when poll_groupable_user_fields is non-empty"
|
||||||
|
);
|
||||||
|
|
||||||
|
await click(".poll-show-breakdown:first");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find(".poll-breakdown-total-votes")[0].textContent.trim(),
|
||||||
|
"2 votes",
|
||||||
|
"display the correct total vote count"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find(".poll-breakdown-chart-container").length,
|
||||||
|
2,
|
||||||
|
"renders a chart for each of the groups in group_results response"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
find(".poll-breakdown-chart-container > canvas")[0].$chartjs,
|
||||||
|
"$chartjs is defined on the pie charts"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Changing the display mode from percentage to count", async assert => {
|
||||||
|
await visit("/t/-/topic_with_pie_chart_poll");
|
||||||
|
await click(".poll-show-breakdown:first");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find(".poll-breakdown-option-count:first")[0].textContent.trim(),
|
||||||
|
"40.0%",
|
||||||
|
"displays the correct vote percentage"
|
||||||
|
);
|
||||||
|
|
||||||
|
await click(".modal-tabs .count");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find(".poll-breakdown-option-count:first")[0].textContent.trim(),
|
||||||
|
"2",
|
||||||
|
"displays the correct vote count"
|
||||||
|
);
|
||||||
|
|
||||||
|
await click(".modal-tabs .percentage");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find(".poll-breakdown-option-count:last")[0].textContent.trim(),
|
||||||
|
"20.0%",
|
||||||
|
"displays the percentage again"
|
||||||
|
);
|
||||||
|
});
|
@ -1,68 +1,11 @@
|
|||||||
import { acceptance } from "helpers/qunit-helpers";
|
import { acceptance } from "helpers/qunit-helpers";
|
||||||
import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer";
|
|
||||||
import { Promise } from "rsvp";
|
|
||||||
|
|
||||||
acceptance("Rendering polls with pie charts - desktop", {
|
acceptance("Rendering polls with pie charts - desktop", {
|
||||||
loggedIn: true,
|
loggedIn: true,
|
||||||
settings: { poll_enabled: true, poll_groupable_user_fields: "something" },
|
settings: { poll_enabled: true, poll_groupable_user_fields: "something" }
|
||||||
beforeEach() {
|
|
||||||
clearPopupMenuOptionsCallback();
|
|
||||||
},
|
|
||||||
pretend(server, helper) {
|
|
||||||
server.get("/polls/grouped_poll_results.json", () => {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
resolve(
|
|
||||||
helper.response({
|
|
||||||
grouped_results: [
|
|
||||||
{
|
|
||||||
group: "Engineering",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
digest: "687a1ccf3c6a260f9aeeb7f68a1d463c",
|
|
||||||
html: "This Is",
|
|
||||||
votes: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
digest: "9377906763a1221d31d656ea0c4a4495",
|
|
||||||
html: "A test for sure",
|
|
||||||
votes: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
digest: "ecf47c65a85a0bb20029072b1b721977",
|
|
||||||
html: "Why not give it some more",
|
|
||||||
votes: 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
group: "Marketing",
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
digest: "687a1ccf3c6a260f9aeeb7f68a1d463c",
|
|
||||||
html: "This Is",
|
|
||||||
votes: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
digest: "9377906763a1221d31d656ea0c4a4495",
|
|
||||||
html: "A test for sure",
|
|
||||||
votes: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
digest: "ecf47c65a85a0bb20029072b1b721977",
|
|
||||||
html: "Why not give it some more",
|
|
||||||
votes: 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Polls", async assert => {
|
test("Displays the pie chart", async assert => {
|
||||||
await visit("/t/-/topic_with_pie_chart_poll");
|
await visit("/t/-/topic_with_pie_chart_poll");
|
||||||
|
|
||||||
const poll = find(".poll")[0];
|
const poll = find(".poll")[0];
|
||||||
@ -90,39 +33,4 @@ test("Polls", async assert => {
|
|||||||
1,
|
1,
|
||||||
"Renders the chart div instead of bar container"
|
"Renders the chart div instead of bar container"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
find(".poll-group-by-toggle").text(),
|
|
||||||
"Show breakdown",
|
|
||||||
"Shows the breakdown button when poll_groupable_user_fields is non-empty"
|
|
||||||
);
|
|
||||||
|
|
||||||
await click(".poll-group-by-toggle:first");
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
find(".poll-group-by-toggle").text(),
|
|
||||||
"Hide breakdown",
|
|
||||||
"Shows the combine breakdown button after toggle is clicked"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Double click to make sure the state toggles back to combined view
|
|
||||||
await click(".toggle-results:first");
|
|
||||||
await click(".toggle-results:first");
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
find(".poll-group-by-toggle").text(),
|
|
||||||
"Hide breakdown",
|
|
||||||
"Returns to the grouped view, after toggling results shown"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
find(".poll-grouped-pie-container").length,
|
|
||||||
2,
|
|
||||||
"Renders a chart for each of the groups in group_results response"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.ok(
|
|
||||||
find(".poll-grouped-pie-container > canvas")[0].$chartjs,
|
|
||||||
"$chartjs is defined on the pie charts"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
7
public/javascripts/chartjs-plugin-datalabels.min.js
vendored
Normal file
7
public/javascripts/chartjs-plugin-datalabels.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -623,6 +623,11 @@ chartjs-color@^2.1.0:
|
|||||||
chartjs-color-string "^0.6.0"
|
chartjs-color-string "^0.6.0"
|
||||||
color-convert "^1.9.3"
|
color-convert "^1.9.3"
|
||||||
|
|
||||||
|
chartjs-plugin-datalabels@^0.7.0:
|
||||||
|
version "0.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-0.7.0.tgz#f72e44edb2db45ef68913e9320bcc50398a205e6"
|
||||||
|
integrity sha512-PKVUX14nYhH0wcdCpgOoC39Gbzvn6cZ7O9n+bwc02yKD9FTnJ7/TSrBcfebmolFZp1Rcicr9xbT0a5HUbigS7g==
|
||||||
|
|
||||||
chrome-launcher@^0.12.0:
|
chrome-launcher@^0.12.0:
|
||||||
version "0.12.0"
|
version "0.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/chrome-launcher/-/chrome-launcher-0.12.0.tgz#08db81ef0f7b283c331df2c350e780c38bd0ce3a"
|
resolved "https://registry.yarnpkg.com/chrome-launcher/-/chrome-launcher-0.12.0.tgz#08db81ef0f7b283c331df2c350e780c38bd0ce3a"
|
||||||
|
Loading…
Reference in New Issue
Block a user