mirror of
https://github.com/discourse/discourse.git
synced 2025-06-06 23:07:28 +08:00
FEATURE: Absolute Numbers in Poll (#28240)
What does this add? =================== This PR adds an extra button to the poll to show the absolute number of people who voted for each option. This button will only be added for the single/multi-select bar chart. Related meta topic: https://meta.discourse.org/t/absolute-numbers-in-polls/32771
This commit is contained in:
@ -35,6 +35,20 @@ const buttonOptionsMap = {
|
|||||||
icon: "lock",
|
icon: "lock",
|
||||||
action: "toggleStatus",
|
action: "toggleStatus",
|
||||||
},
|
},
|
||||||
|
showTally: {
|
||||||
|
className: "btn-default show-tally",
|
||||||
|
label: "poll.show-tally.label",
|
||||||
|
title: "poll.show-tally.title",
|
||||||
|
icon: "info",
|
||||||
|
action: "toggleDisplayMode",
|
||||||
|
},
|
||||||
|
showPercentage: {
|
||||||
|
className: "btn-default show-percentage",
|
||||||
|
label: "poll.show-percentage.label",
|
||||||
|
title: "poll.show-percentage.title",
|
||||||
|
icon: "info",
|
||||||
|
action: "toggleDisplayMode",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class PollButtonsDropdownComponent extends Component {
|
export default class PollButtonsDropdownComponent extends Component {
|
||||||
@ -68,8 +82,15 @@ export default class PollButtonsDropdownComponent extends Component {
|
|||||||
topicArchived,
|
topicArchived,
|
||||||
groupableUserFields,
|
groupableUserFields,
|
||||||
isAutomaticallyClosed,
|
isAutomaticallyClosed,
|
||||||
|
availableDisplayMode,
|
||||||
} = this.args;
|
} = this.args;
|
||||||
|
|
||||||
|
if (availableDisplayMode) {
|
||||||
|
const option = { ...buttonOptionsMap[availableDisplayMode] };
|
||||||
|
option.id = option.action;
|
||||||
|
contents.push(option);
|
||||||
|
}
|
||||||
|
|
||||||
if (groupableUserFields.length && voters > 0) {
|
if (groupableUserFields.length && voters > 0) {
|
||||||
const option = { ...buttonOptionsMap.showBreakdown };
|
const option = { ...buttonOptionsMap.showBreakdown };
|
||||||
option.id = option.action;
|
option.id = option.action;
|
||||||
|
@ -76,10 +76,17 @@ export default class PollResultsStandardComponent extends Component {
|
|||||||
<div class="option">
|
<div class="option">
|
||||||
<p>
|
<p>
|
||||||
{{#unless @isRankedChoice}}
|
{{#unless @isRankedChoice}}
|
||||||
<span class="percentage">{{i18n
|
{{#if @showTally}}
|
||||||
"number.percent"
|
<span class="absolute">{{i18n
|
||||||
count=option.percentage
|
"poll.votes"
|
||||||
}}</span>
|
count=option.votes
|
||||||
|
}}</span>
|
||||||
|
{{else}}
|
||||||
|
<span class="percentage">{{i18n
|
||||||
|
"number.percent"
|
||||||
|
count=option.percentage
|
||||||
|
}}</span>
|
||||||
|
{{/if}}
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
<span class="option-text">{{htmlSafe option.html}}</span>
|
<span class="option-text">{{htmlSafe option.html}}</span>
|
||||||
</p>
|
</p>
|
||||||
|
@ -65,6 +65,7 @@ export default class TabsComponent extends Component {
|
|||||||
@voters={{@voters}}
|
@voters={{@voters}}
|
||||||
@votersCount={{@votersCount}}
|
@votersCount={{@votersCount}}
|
||||||
@fetchVoters={{@fetchVoters}}
|
@fetchVoters={{@fetchVoters}}
|
||||||
|
@showTally={{@showTally}}
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
@ -12,7 +12,11 @@ import icon from "discourse-common/helpers/d-icon";
|
|||||||
import i18n from "discourse-common/helpers/i18n";
|
import i18n from "discourse-common/helpers/i18n";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
import PollBreakdownModal from "../components/modal/poll-breakdown";
|
import PollBreakdownModal from "../components/modal/poll-breakdown";
|
||||||
import { PIE_CHART_TYPE } from "../components/modal/poll-ui-builder";
|
import {
|
||||||
|
MULTIPLE_POLL_TYPE,
|
||||||
|
PIE_CHART_TYPE,
|
||||||
|
REGULAR_POLL_TYPE,
|
||||||
|
} from "../components/modal/poll-ui-builder";
|
||||||
import PollButtonsDropdown from "../components/poll-buttons-dropdown";
|
import PollButtonsDropdown from "../components/poll-buttons-dropdown";
|
||||||
import PollInfo from "../components/poll-info";
|
import PollInfo from "../components/poll-info";
|
||||||
import PollOptions from "../components/poll-options";
|
import PollOptions from "../components/poll-options";
|
||||||
@ -48,6 +52,8 @@ export default class PollComponent extends Component {
|
|||||||
(this.topicArchived && !this.staffOnly) ||
|
(this.topicArchived && !this.staffOnly) ||
|
||||||
(this.closed && !this.staffOnly);
|
(this.closed && !this.staffOnly);
|
||||||
|
|
||||||
|
@tracked showTally = false;
|
||||||
|
|
||||||
checkUserGroups = (user, poll) => {
|
checkUserGroups = (user, poll) => {
|
||||||
const pollGroups =
|
const pollGroups =
|
||||||
poll && poll.groups && poll.groups.split(",").map((g) => g.toLowerCase());
|
poll && poll.groups && poll.groups.split(",").map((g) => g.toLowerCase());
|
||||||
@ -452,6 +458,17 @@ export default class PollComponent extends Component {
|
|||||||
return htmlSafe(I18n.t("poll.average_rating", { average }));
|
return htmlSafe(I18n.t("poll.average_rating", { average }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get availableDisplayMode() {
|
||||||
|
if (
|
||||||
|
!this.showResults ||
|
||||||
|
this.poll.chart_type === PIE_CHART_TYPE ||
|
||||||
|
![REGULAR_POLL_TYPE, MULTIPLE_POLL_TYPE].includes(this.poll.type)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.showTally ? "showPercentage" : "showTally";
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
updatedVoters() {
|
updatedVoters() {
|
||||||
this.preloadedVoters = this.defaultPreloadedVoters();
|
this.preloadedVoters = this.defaultPreloadedVoters();
|
||||||
@ -640,6 +657,12 @@ export default class PollComponent extends Component {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
toggleDisplayMode() {
|
||||||
|
this.showTally = !this.showTally;
|
||||||
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
{{didUpdate this.updatedVoters @preloadedVoters}}
|
{{didUpdate this.updatedVoters @preloadedVoters}}
|
||||||
@ -669,6 +692,7 @@ export default class PollComponent extends Component {
|
|||||||
@votersCount={{this.poll.voters}}
|
@votersCount={{this.poll.voters}}
|
||||||
@fetchVoters={{this.fetchVoters}}
|
@fetchVoters={{this.fetchVoters}}
|
||||||
@rankedChoiceOutcome={{this.rankedChoiceOutcome}}
|
@rankedChoiceOutcome={{this.rankedChoiceOutcome}}
|
||||||
|
@showTally={{this.showTally}}
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
@ -754,6 +778,7 @@ export default class PollComponent extends Component {
|
|||||||
@groupableUserFields={{this.groupableUserFields}}
|
@groupableUserFields={{this.groupableUserFields}}
|
||||||
@isAutomaticallyClosed={{this.isAutomaticallyClosed}}
|
@isAutomaticallyClosed={{this.isAutomaticallyClosed}}
|
||||||
@dropDownClick={{this.dropDownClick}}
|
@dropDownClick={{this.dropDownClick}}
|
||||||
|
@availableDisplayMode={{this.availableDisplayMode}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -347,7 +347,9 @@ div.poll-outer {
|
|||||||
.poll-buttons-dropdown,
|
.poll-buttons-dropdown,
|
||||||
.export-results,
|
.export-results,
|
||||||
.toggle-status,
|
.toggle-status,
|
||||||
.show-breakdown {
|
.show-breakdown,
|
||||||
|
.show-tally,
|
||||||
|
.show-percentage {
|
||||||
// we want these controls to be separated
|
// we want these controls to be separated
|
||||||
// from voting controls
|
// from voting controls
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
@ -367,7 +369,8 @@ div.poll-outer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.percentage {
|
.percentage,
|
||||||
|
.absolute {
|
||||||
float: right;
|
float: right;
|
||||||
color: var(--primary-medium);
|
color: var(--primary-medium);
|
||||||
margin-left: 0.25em;
|
margin-left: 0.25em;
|
||||||
|
@ -7,6 +7,9 @@ en:
|
|||||||
total_votes:
|
total_votes:
|
||||||
one: "total vote"
|
one: "total vote"
|
||||||
other: "total votes"
|
other: "total votes"
|
||||||
|
votes:
|
||||||
|
one: "%{count} vote"
|
||||||
|
other: "%{count} votes"
|
||||||
|
|
||||||
average_rating: "Average rating: <strong>%{average}</strong>."
|
average_rating: "Average rating: <strong>%{average}</strong>."
|
||||||
|
|
||||||
@ -46,6 +49,14 @@ en:
|
|||||||
title: "Display the poll results"
|
title: "Display the poll results"
|
||||||
label: "Results"
|
label: "Results"
|
||||||
|
|
||||||
|
show-tally:
|
||||||
|
title: "Show voting results by number of votes"
|
||||||
|
label: "Display tally"
|
||||||
|
|
||||||
|
show-percentage:
|
||||||
|
title: "Show voting results as percentage"
|
||||||
|
label: "Display as percentage"
|
||||||
|
|
||||||
remove-vote:
|
remove-vote:
|
||||||
title: "Remove your vote"
|
title: "Remove your vote"
|
||||||
label: "Undo vote"
|
label: "Undo vote"
|
||||||
|
@ -37,7 +37,7 @@ module("Poll | Component | poll-buttons-dropdown", function (hooks) {
|
|||||||
|
|
||||||
await click(".widget-dropdown-header");
|
await click(".widget-dropdown-header");
|
||||||
|
|
||||||
assert.strictEqual(count("li.dropdown-menu__item"), 2);
|
assert.dom("li.dropdown-menu__item").exists({ count: 2 });
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
query("li.dropdown-menu__item span").textContent.trim(),
|
query("li.dropdown-menu__item span").textContent.trim(),
|
||||||
@ -46,6 +46,43 @@ module("Poll | Component | poll-buttons-dropdown", function (hooks) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Renders a show-tally button when poll is a bar chart", async function (assert) {
|
||||||
|
this.setProperties({
|
||||||
|
closed: false,
|
||||||
|
voters: 2,
|
||||||
|
isStaff: false,
|
||||||
|
isMe: false,
|
||||||
|
topicArchived: false,
|
||||||
|
groupableUserFields: ["stuff"],
|
||||||
|
isAutomaticallyClosed: false,
|
||||||
|
dropDownClick: () => {},
|
||||||
|
availableDisplayMode: "showTally",
|
||||||
|
});
|
||||||
|
|
||||||
|
await render(hbs`<PollButtonsDropdown
|
||||||
|
@closed={{this.closed}}
|
||||||
|
@voters={{this.voters}}
|
||||||
|
@isStaff={{this.isStaff}}
|
||||||
|
@isMe={{this.isMe}}
|
||||||
|
@topicArchived={{this.topicArchived}}
|
||||||
|
@groupableUserFields={{this.groupableUserFields}}
|
||||||
|
@isAutomaticallyClosed={{this.isAutomaticallyClosed}}
|
||||||
|
@dropDownClick={{this.dropDownClick}}
|
||||||
|
@availableDisplayMode={{this.availableDisplayMode}}
|
||||||
|
/>`);
|
||||||
|
|
||||||
|
await click(".widget-dropdown-header");
|
||||||
|
|
||||||
|
assert.strictEqual(count("li.dropdown-menu__item"), 2);
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom(query("li.dropdown-menu__item span"))
|
||||||
|
.hasText(
|
||||||
|
I18n.t("poll.show-tally.label"),
|
||||||
|
"displays the show absolute button"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("Renders a single button when there is only one authorised action", async function (assert) {
|
test("Renders a single button when there is only one authorised action", async function (assert) {
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
closed: false,
|
closed: false,
|
||||||
|
@ -3,6 +3,7 @@ import hbs from "htmlbars-inline-precompile";
|
|||||||
import { module, test } from "qunit";
|
import { module, test } from "qunit";
|
||||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
import { exists, queryAll } from "discourse/tests/helpers/qunit-helpers";
|
import { exists, queryAll } from "discourse/tests/helpers/qunit-helpers";
|
||||||
|
import I18n from "discourse-i18n";
|
||||||
|
|
||||||
const TWO_OPTIONS = [
|
const TWO_OPTIONS = [
|
||||||
{ id: "1ddc47be0d2315b9711ee8526ca9d83f", html: "This", votes: 5, rank: 0 },
|
{ id: "1ddc47be0d2315b9711ee8526ca9d83f", html: "This", votes: 5, rank: 0 },
|
||||||
@ -160,4 +161,40 @@ module("Poll | Component | poll-results-standard", function (hooks) {
|
|||||||
"b"
|
"b"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("options in ascending order, showing absolute vote number", async function (assert) {
|
||||||
|
this.setProperties({
|
||||||
|
options: FIVE_OPTIONS,
|
||||||
|
pollName: "Five Multi Option Poll",
|
||||||
|
pollType: "multiple",
|
||||||
|
postId: 123,
|
||||||
|
vote: ["1ddc47be0d2315b9711ee8526ca9d83f"],
|
||||||
|
voters: PRELOADEDVOTERS,
|
||||||
|
votersCount: 12,
|
||||||
|
fetchVoters: () => {},
|
||||||
|
showTally: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await render(hbs`<PollResultsStandard
|
||||||
|
@options={{this.options}}
|
||||||
|
@pollName={{this.pollName}}
|
||||||
|
@pollType={{this.pollType}}
|
||||||
|
@postId={{this.postId}}
|
||||||
|
@vote={{this.vote}}
|
||||||
|
@voters={{this.voters}}
|
||||||
|
@votersCount={{this.votersCount}}
|
||||||
|
@fetchVoters={{this.fetchVoters}}
|
||||||
|
@showTally={{this.showTally}}
|
||||||
|
/>`);
|
||||||
|
|
||||||
|
let percentages = queryAll(".option .absolute");
|
||||||
|
assert.dom(percentages[0]).hasText(I18n.t("poll.votes", { count: 5 }));
|
||||||
|
assert.dom(percentages[1]).hasText(I18n.t("poll.votes", { count: 4 }));
|
||||||
|
assert.dom(percentages[2]).hasText(I18n.t("poll.votes", { count: 2 }));
|
||||||
|
assert.dom(percentages[3]).hasText(I18n.t("poll.votes", { count: 1 }));
|
||||||
|
|
||||||
|
assert.dom(queryAll(".option")[3].querySelectorAll("span")[1]).hasText("a");
|
||||||
|
assert.dom(percentages[4]).hasText(I18n.t("poll.votes", { count: 1 }));
|
||||||
|
assert.dom(queryAll(".option")[4].querySelectorAll("span")[1]).hasText("b");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user