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:
Jarek Radosz 2020-08-06 17:57:06 +02:00 committed by GitHub
parent 76c02cac65
commit cd4f251891
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 708 additions and 294 deletions

View File

@ -20,6 +20,10 @@ export default Component.extend({
},
click() {
this.onSelectPanel(this.panel);
this.set("selectedPanel", this.panel);
if (this.onSelectPanel) {
this.onSelectPanel(this.panel);
}
}
});

View File

@ -50,7 +50,10 @@ export default function(name, opts) {
});
if (controller.actions.onSelectPanel) {
modalController.set("onSelectPanel", controller.actions.onSelectPanel);
modalController.set(
"onSelectPanel",
controller.actions.onSelectPanel.bind(controller)
);
}
modalController.set(

View File

@ -18,10 +18,6 @@ export default Mixin.create({
closeModal() {
this.modal.send("closeModal");
this.set("panels", []);
},
onSelectPanel(panel) {
this.set("selectedPanel", panel);
}
}
});

View File

@ -148,7 +148,7 @@
}
&: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;
@media screen and (max-height: 500px) {
max-height: 65vh !important;

View File

@ -69,6 +69,9 @@ task 'javascript:update' do
}, {
source: 'chart.js/dist/Chart.min.js',
public: true
}, {
source: 'chartjs-plugin-datalabels/dist/chartjs-plugin-datalabels.min.js',
public: true
}, {
source: 'magnific-popup/dist/jquery.magnific-popup.min.js',
public: true

View File

@ -13,6 +13,7 @@
"bootbox": "3.2.0",
"bootstrap": "v3.4.1",
"chart.js": "2.9.3",
"chartjs-plugin-datalabels": "^0.7.0",
"eslint-plugin-lodash": "^6.0.0",
"favcount": "https://github.com/chrishunt/favcount",
"handlebars": "^4.7.0",

View File

@ -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();
}
});

View File

@ -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();
}
}
});

View File

@ -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);
}
});

View File

@ -1,7 +1,7 @@
import I18n from "I18n";
import Controller from "@ember/controller";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import EmberObject from "@ember/object";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
export const BAR_CHART_TYPE = "bar";
export const PIE_CHART_TYPE = "pie";

View File

@ -0,0 +1,2 @@
<label class="poll-breakdown-chart-label">{{@group}}</label>
<canvas class="poll-breakdown-chart-chart"></canvas>

View File

@ -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>

View File

@ -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}}

View File

@ -1,6 +1,6 @@
import { withPluginApi } from "discourse/lib/plugin-api";
import discourseComputed from "discourse-common/utils/decorators";
import showModal from "discourse/lib/show-modal";
import discourseComputed from "discourse-common/utils/decorators";
function initializePollUIBuilder(api) {
api.modifyClass("controller:composer", {

View File

@ -1,8 +1,8 @@
import EmberObject from "@ember/object";
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 { getRegister } from "discourse-common/lib/get-owner";
import { observes } from "discourse-common/utils/decorators";
function initializePolls(api) {
const register = getRegister(api);

View File

@ -1,18 +1,18 @@
import I18n from "I18n";
import { createWidget } from "discourse/widgets/widget";
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 { 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 loadScript from "discourse/lib/load-script";
import { getColors } from "../lib/chart-colors";
import { classify } from "@ember/string";
import { PIE_CHART_TYPE } from "../controllers/poll-ui-builder";
import round from "discourse/lib/round";
import showModal from "discourse/lib/show-modal";
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) {
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) {
let el = document.querySelector(`#poll-results-chart-${id}`);
el && el.parentNode.removeChild(el);
@ -607,27 +493,23 @@ createWidget("discourse-poll-pie-chart", {
return contents;
}
let btn;
let chart;
if (attrs.groupResults && attrs.groupableUserFields.length > 0) {
chart = this.attach("discourse-poll-grouped-pies", attrs);
clearPieChart(attrs.id);
} else {
if (attrs.groupableUserFields.length) {
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"
});
}
if (attrs.groupableUserFields.length) {
const button = this.attach("button", {
className: "btn-default poll-show-breakdown",
label: "poll.group-results.label",
title: "poll.group-results.title",
icon: "far-eye",
action: "showBreakdown"
});
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(h(`div#poll-results-legend-${attrs.id}.pie-chart-legends`));
return contents;
}
});
@ -1072,19 +954,13 @@ export default createWidget("discourse-poll", {
});
},
toggleGroupedPieCharts() {
this.attrs.groupResults = !this.attrs.groupResults;
},
refreshCharts(newGroupedByValue) {
let el = document.getElementById(
`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;
showBreakdown() {
showModal("poll-breakdown", {
model: this.attrs,
panels: [
{ id: "percentage", title: "poll.breakdown.percentage" },
{ id: "count", title: "poll.breakdown.count" }
]
});
}
});

View 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;
}

View File

@ -149,41 +149,22 @@ div.poll {
}
.poll-results-chart {
height: 310px;
height: 320px;
overflow-y: auto;
overflow-x: hidden;
}
.poll-group-by-toggle {
.poll-show-breakdown {
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 {
.poll-container {
display: inline-block;
height: 310px;
max-height: 310px;
height: 320px;
max-height: 320px;
overflow-y: auto;
.poll-grouped-pie-container {
width: 50%;
}
}
.poll-info {
display: inline-block;

View File

@ -49,11 +49,8 @@ div.poll {
div.poll.pie {
.poll-container {
width: calc(100% - 190px);
.poll-grouped-pie-container {
width: 50%;
}
}
.poll-info {
display: inline-block;
width: 150px;

View File

@ -28,15 +28,15 @@ div.poll {
margin-left: 0.25em;
}
}
.poll-show-breakdown {
display: none;
}
}
div.poll.pie {
.poll-container {
width: calc(100% - 30px);
border-bottom: 1px solid var(--primary-low);
.poll-grouped-pie-container {
width: 100%;
}
}
}

View File

@ -52,10 +52,6 @@ en:
title: "Group votes by user field"
label: "Show breakdown"
ungroup-results:
title: "Combine all votes"
label: "Hide breakdown"
export-results:
title: "Export the poll results"
label: "Export"
@ -74,6 +70,13 @@ en:
closes_in: "Closes in <strong>%{timeLeft}</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_casting_votes: "Sorry, there was an error casting your votes."
error_while_fetching_voters: "Sorry, there was an error displaying the voters."

View File

@ -8,6 +8,7 @@
register_asset "stylesheets/common/poll.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/mobile/poll.scss", :mobile
register_asset "stylesheets/mobile/poll-ui-builder.scss", :mobile

View File

@ -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"
);
});

View File

@ -1,68 +1,11 @@
import { acceptance } from "helpers/qunit-helpers";
import { clearPopupMenuOptionsCallback } from "discourse/controllers/composer";
import { Promise } from "rsvp";
acceptance("Rendering polls with pie charts - desktop", {
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
}
]
}
]
})
);
});
});
}
settings: { poll_enabled: true, poll_groupable_user_fields: "something" }
});
test("Polls", async assert => {
test("Displays the pie chart", async assert => {
await visit("/t/-/topic_with_pie_chart_poll");
const poll = find(".poll")[0];
@ -90,39 +33,4 @@ test("Polls", async assert => {
1,
"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"
);
});

File diff suppressed because one or more lines are too long

View File

@ -623,6 +623,11 @@ chartjs-color@^2.1.0:
chartjs-color-string "^0.6.0"
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:
version "0.12.0"
resolved "https://registry.yarnpkg.com/chrome-launcher/-/chrome-launcher-0.12.0.tgz#08db81ef0f7b283c331df2c350e780c38bd0ce3a"