From 014701442c6242065e711168a1de86fe80142379 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 26 Apr 2019 18:47:47 +0200 Subject: [PATCH] refactor poll calculations --- .../assignments/assignment-poll-option.ts | 10 +- .../assignment-detail.component.ts | 5 - .../assignment-poll.component.ts | 25 ++- .../models/view-assignment-poll-option.ts | 11 +- .../models/view-assignment-poll.ts | 7 - .../services/assignment-pdf.service.ts | 18 +- .../services/assignment-poll.service.ts | 154 ++++++++++++------ .../assignments/poll/poll-slide-data.ts | 5 +- .../poll/poll-slide.component.html | 31 +--- .../assignments/poll/poll-slide.component.ts | 125 +++++++------- 10 files changed, 211 insertions(+), 180 deletions(-) diff --git a/client/src/app/shared/models/assignments/assignment-poll-option.ts b/client/src/app/shared/models/assignments/assignment-poll-option.ts index 298c56f3a..92fee7de9 100644 --- a/client/src/app/shared/models/assignments/assignment-poll-option.ts +++ b/client/src/app/shared/models/assignments/assignment-poll-option.ts @@ -1,6 +1,11 @@ import { Deserializer } from '../base/deserializer'; import { PollVoteValue } from 'app/core/ui-services/poll.service'; +export interface AssignmentOptionVote { + weight: number; + value: PollVoteValue; +} + /** * Representation of a poll option * @@ -11,10 +16,7 @@ export class AssignmentPollOption extends Deserializer { public id: number; // The AssignmentUser id of the candidate public candidate_id: number; // the User id of the candidate public is_elected: boolean; - public votes: { - weight: number; // represented as a string because it's a decimal field - value: PollVoteValue; - }[]; + public votes: AssignmentOptionVote[]; public poll_id: number; public weight: number; // weight to order the display diff --git a/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.ts b/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.ts index 56cd663a6..4d3f327a5 100644 --- a/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.ts +++ b/client/src/app/site/assignments/components/assignment-detail/assignment-detail.component.ts @@ -98,11 +98,6 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn this._assignment = assignment; this.filterCandidates(); - if (this.assignment.polls.length) { - this.assignment.polls.forEach(poll => { - poll.pollBase = this.pollService.getBaseAmount(poll); - }); - } } /** diff --git a/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.ts b/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.ts index 5c166c71d..54d8aca7a 100644 --- a/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.ts +++ b/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.ts @@ -7,10 +7,9 @@ import { TranslateService } from '@ngx-translate/core'; import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll'; import { AssignmentPollDialogComponent } from '../assignment-poll-dialog/assignment-poll-dialog.component'; -import { AssignmentPollService, AssignmentPercentBase } from '../../services/assignment-poll.service'; +import { AssignmentPollService } from '../../services/assignment-poll.service'; import { AssignmentRepositoryService } from 'app/core/repositories/assignments/assignment-repository.service'; import { BaseViewComponent } from 'app/site/base/base-view'; -import { ConfigService } from 'app/core/ui-services/config.service'; import { MajorityMethod, CalculablePollKey } from 'app/core/ui-services/poll.service'; import { OperatorService } from 'app/core/core-services/operator.service'; import { PromptService } from 'app/core/ui-services/prompt.service'; @@ -132,8 +131,7 @@ export class AssignmentPollComponent extends BaseViewComponent implements OnInit public translate: TranslateService, public dialog: MatDialog, private promptService: PromptService, - private formBuilder: FormBuilder, - private config: ConfigService + private formBuilder: FormBuilder ) { super(titleService, translate, matSnackBar); } @@ -148,13 +146,6 @@ export class AssignmentPollComponent extends BaseViewComponent implements OnInit this.descriptionForm = this.formBuilder.group({ description: this.poll ? this.poll.description : '' }); - this.subscriptions.push( - this.config.get('assignments_poll_100_percent_base').subscribe(() => { - if (this.poll) { - this.poll.pollBase = this.pollService.getBaseAmount(this.poll); - } - }) - ); } /** @@ -188,7 +179,11 @@ export class AssignmentPollComponent extends BaseViewComponent implements OnInit public quorumReached(option: ViewAssignmentPollOption): boolean { const yesValue = this.poll.pollmethod === 'votes' ? 'Votes' : 'Yes'; const amount = option.votes.find(v => v.value === yesValue).weight; - const yesQuorum = this.pollService.yesQuorum(this.majorityChoice, this.poll, option); + const yesQuorum = this.pollService.yesQuorum( + this.majorityChoice, + this.pollService.calculationDataFromPoll(this.poll), + option + ); return yesQuorum && amount >= yesQuorum; } @@ -263,7 +258,11 @@ export class AssignmentPollComponent extends BaseViewComponent implements OnInit */ public getQuorumReachedString(option: ViewAssignmentPollOption): string { const name = this.translate.instant(this.majorityChoice.display_name); - const quorum = this.pollService.yesQuorum(this.majorityChoice, this.poll, option); + const quorum = this.pollService.yesQuorum( + this.majorityChoice, + this.pollService.calculationDataFromPoll(this.poll), + option + ); const isReached = this.quorumReached(option) ? this.translate.instant('reached') : this.translate.instant('not reached'); diff --git a/client/src/app/site/assignments/models/view-assignment-poll-option.ts b/client/src/app/site/assignments/models/view-assignment-poll-option.ts index 301bbfc1a..c941783f0 100644 --- a/client/src/app/site/assignments/models/view-assignment-poll-option.ts +++ b/client/src/app/site/assignments/models/view-assignment-poll-option.ts @@ -1,9 +1,9 @@ -import { ViewUser } from 'app/site/users/models/view-user'; +import { AssignmentPollOption, AssignmentOptionVote } from 'app/shared/models/assignments/assignment-poll-option'; import { BaseViewModel } from 'app/site/base/base-view-model'; -import { Updateable } from 'app/site/base/updateable'; import { Identifiable } from 'app/shared/models/base/identifiable'; import { PollVoteValue } from 'app/core/ui-services/poll.service'; -import { AssignmentPollOption } from 'app/shared/models/assignments/assignment-poll-option'; +import { Updateable } from 'app/site/base/updateable'; +import { ViewUser } from 'app/site/users/models/view-user'; /** * Defines the order the option's votes are sorted in (server might send raw data in any order) @@ -40,10 +40,7 @@ export class ViewAssignmentPollOption implements Identifiable, Updateable { return this.option.is_elected; } - public get votes(): { - weight: number; - value: PollVoteValue; - }[] { + public get votes(): AssignmentOptionVote[] { return this.option.votes.sort((a, b) => votesOrder.indexOf(a.value) - votesOrder.indexOf(b.value)); } diff --git a/client/src/app/site/assignments/models/view-assignment-poll.ts b/client/src/app/site/assignments/models/view-assignment-poll.ts index 14537c845..7dfe78802 100644 --- a/client/src/app/site/assignments/models/view-assignment-poll.ts +++ b/client/src/app/site/assignments/models/view-assignment-poll.ts @@ -78,13 +78,6 @@ export class ViewAssignmentPoll implements Identifiable, Updateable, Projectable return this.poll.assignment_id; } - /** - * storing the base values for percentage calculations, - * to avoid recalculating pollBases too often - * (the calculation iterates through all pollOptions in some use cases) - */ - public pollBase: number; - public constructor(assignmentPoll: AssignmentPoll, assignmentPollOptions: ViewAssignmentPollOption[]) { this._assignmentPoll = assignmentPoll; this._assignmentPollOptions = assignmentPollOptions; diff --git a/client/src/app/site/assignments/services/assignment-pdf.service.ts b/client/src/app/site/assignments/services/assignment-pdf.service.ts index 6d0eb191c..863b0527c 100644 --- a/client/src/app/site/assignments/services/assignment-pdf.service.ts +++ b/client/src/app/site/assignments/services/assignment-pdf.service.ts @@ -243,8 +243,11 @@ export class AssignmentPdfService { const conclusionLabel = this.translate.instant(this.pollService.getLabel(key)); const specialLabel = this.translate.instant(this.pollService.getSpecialLabel(poll[key])); let percentLabel = ''; - if (!this.pollService.isAbstractValue(poll, key)) { - percentLabel = ` (${this.pollService.getValuePercent(poll, key)}%)`; + if (!this.pollService.isAbstractValue(this.pollService.calculationDataFromPoll(poll), key)) { + percentLabel = ` (${this.pollService.getValuePercent( + this.pollService.calculationDataFromPoll(poll), + key + )}%)`; } return [ { @@ -302,10 +305,17 @@ export class AssignmentPdfService { let resultString = ''; const label = this.translate.instant(this.pollService.getLabel(optionLabel)); const valueString = this.pollService.getSpecialLabel(value); - const percentNr = this.pollService.getPercent(poll, pollOption, optionLabel); + const percentNr = this.pollService.getPercent( + this.pollService.calculationDataFromPoll(poll), + pollOption, + optionLabel + ); resultString += `${label} ${valueString}`; - if (percentNr && !this.pollService.isAbstractOption(poll, pollOption, optionLabel)) { + if ( + percentNr && + !this.pollService.isAbstractOption(this.pollService.calculationDataFromPoll(poll), pollOption, optionLabel) + ) { resultString += ` (${percentNr}%)`; } diff --git a/client/src/app/site/assignments/services/assignment-poll.service.ts b/client/src/app/site/assignments/services/assignment-poll.service.ts index 6824e33f7..99ab09694 100644 --- a/client/src/app/site/assignments/services/assignment-poll.service.ts +++ b/client/src/app/site/assignments/services/assignment-poll.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; +import { AssignmentOptionVote } from 'app/shared/models/assignments/assignment-poll-option'; import { ConfigService } from 'app/core/ui-services/config.service'; import { PollService, @@ -15,6 +16,28 @@ type AssignmentPollValues = 'auto' | 'votes' | 'yesnoabstain' | 'yesno'; export type AssignmentPollMethod = 'yn' | 'yna' | 'votes'; export type AssignmentPercentBase = 'YES_NO_ABSTAIN' | 'YES_NO' | 'VALID' | 'CAST' | 'DISABLED'; +/** + * interface common to data in a ViewAssignmentPoll and PollSlideData + * + * TODO: simplify + */ +export interface CalculationData { + pollMethod: AssignmentPollMethod; + votesno: number; + votesabstain: number; + votescast: number; + votesvalid: number; + votesinvalid: number; + percentBase?: AssignmentPercentBase; + pollOptions?: { + votes: AssignmentOptionVote[]; + }[]; +} + +interface CalculationOption { + votes: AssignmentOptionVote[]; +} + /** * Vote entries included once for summary (e.g. total votes cast) */ @@ -75,17 +98,18 @@ export class AssignmentPollService extends PollService { * Get the base amount for the 100% calculations. Note that some poll methods * (e.g. yes/no/abstain may have a different percentage base and will return null here) * - * @param poll + * @param data * @returns The amount of votes indicating the 100% base */ - public getBaseAmount(poll: ViewAssignmentPoll): number | null { - switch (this.percentBase) { + public getBaseAmount(data: CalculationData): number | null { + const percentBase = data.percentBase || this.percentBase; + switch (percentBase) { case 'DISABLED': return null; case 'YES_NO': case 'YES_NO_ABSTAIN': - if (poll.pollmethod === 'votes') { - const yes = poll.options.map(option => { + if (data.pollMethod === 'votes') { + const yes = data.pollOptions.map(option => { const yesValue = option.votes.find(v => v.value === 'Votes'); return yesValue ? yesValue.weight : -99; }); @@ -99,9 +123,9 @@ export class AssignmentPollService extends PollService { return null; } case 'CAST': - return poll.votescast > 0 && poll.votesinvalid >= 0 ? poll.votescast : null; + return data.votescast > 0 && data.votesinvalid >= 0 ? data.votescast : null; case 'VALID': - return poll.votesvalid > 0 ? poll.votesvalid : null; + return data.votesvalid > 0 ? data.votesvalid : null; default: return null; } @@ -111,25 +135,25 @@ export class AssignmentPollService extends PollService { * Get the percentage for an option * * @param poll - * @param option - * @param value + * @param data * @returns a percentage number with two digits, null if the value cannot be calculated */ - public getPercent(poll: ViewAssignmentPoll, option: ViewAssignmentPollOption, value: PollVoteValue): number | null { + public getPercent(data: CalculationData, option: CalculationOption, key: PollVoteValue): number | null { + const percentBase = data.percentBase || this.percentBase; let base = 0; - if (this.percentBase === 'DISABLED') { + if (percentBase === 'DISABLED') { return null; - } else if (this.percentBase === 'VALID') { - base = poll.votesvalid; - } else if (this.percentBase === 'CAST') { - base = poll.votescast; + } else if (percentBase === 'VALID') { + base = data.votesvalid; + } else if (percentBase === 'CAST') { + base = data.votescast; } else { - base = poll.pollmethod === 'votes' ? poll.pollBase : this.getOptionBaseAmount(poll, option); + base = data.pollMethod === 'votes' ? this.getBaseAmount(data) : this.getOptionBaseAmount(data, option); } if (!base || base < 0) { return null; } - const vote = option.votes.find(v => v.value === value); + const vote = option.votes.find(v => v.value === key); if (!vote) { return null; } @@ -140,39 +164,53 @@ export class AssignmentPollService extends PollService { * get the percentage for a non-abstract per-poll value * TODO: similar code to getPercent. Mergeable? * - * @param poll the poll this value refers to + * @param data * @param value a per-poll value (e.g. 'votesvalid') * @returns a percentage number with two digits, null if the value cannot be calculated */ - public getValuePercent(poll: ViewAssignmentPoll, value: CalculablePollKey): number | null { - if (!poll.pollBase) { + public getValuePercent(data: CalculationData, value: CalculablePollKey): number | null { + const percentBase = data.percentBase || this.percentBase; + switch (percentBase) { + case 'YES_NO': + case 'YES_NO_ABSTAIN': + case 'DISABLED': + return null; + case 'VALID': + if (value === 'votesinvalid' || value === 'votescast') { + return null; + } + break; + } + const baseAmount = this.getBaseAmount(data); + if (!baseAmount) { return null; } - const amount = poll[value]; + const amount = data[value]; if (amount === undefined || amount < 0) { return null; } - return Math.round(((amount * 100) / poll.pollBase) * 100) / 100; + return Math.round(((amount * 100) / baseAmount) * 100) / 100; } /** * Check if the option in a poll is abstract (percentages should not be calculated) * - * @param poll + * @param data * @param option * @param key (optional) the key to calculate * @returns true if the poll has no percentages, the poll option is a special value, * or if the calculations are disabled in the config */ - public isAbstractOption(poll: ViewAssignmentPoll, option: ViewAssignmentPollOption, key?: PollVoteValue): boolean { - if (this.percentBase === 'DISABLED' || !option.votes || !option.votes.length) { + public isAbstractOption(data: CalculationData, option: ViewAssignmentPollOption, key?: PollVoteValue): boolean { + const percentBase = data.percentBase || this.percentBase; + if (percentBase === 'DISABLED' || !option.votes || !option.votes.length) { return true; } - if (key === 'Abstain' && this.percentBase === 'YES_NO') { + if (key === 'Abstain' && percentBase === 'YES_NO') { return true; } - if (poll.pollmethod === 'votes') { - return poll.pollBase ? false : true; + if (data.pollMethod === 'votes') { + return this.getBaseAmount(data) > 0 ? false : true; } else { return option.votes.some(v => v.weight < 0); } @@ -182,19 +220,20 @@ export class AssignmentPollService extends PollService { * Check for abstract (not usable as percentage) options in non-option * 'meta' values * - * @param poll + * @param data * @param value * @returns true if percentages cannot be calculated * TODO: Yes, No, etc. in an option will always return true. * Use {@link isAbstractOption} for these */ - public isAbstractValue(poll: ViewAssignmentPoll, value: CalculablePollKey): boolean { - if (this.percentBase === 'DISABLED' || !poll.pollBase || !this.pollValues.includes(value)) { + public isAbstractValue(data: CalculationData, value: CalculablePollKey): boolean { + const percentBase = data.percentBase || this.percentBase; + if (percentBase === 'DISABLED' || !this.getBaseAmount(data) || !this.pollValues.includes(value)) { return true; } - if (this.percentBase === 'CAST' && poll[value] >= 0) { + if (percentBase === 'CAST' && data[value] >= 0) { return false; - } else if (this.percentBase === 'VALID' && value === 'votesvalid' && poll[value] > 0) { + } else if (percentBase === 'VALID' && value === 'votesvalid' && data[value] > 0) { return false; } return true; @@ -203,24 +242,27 @@ export class AssignmentPollService extends PollService { /** * Calculate the base amount inside an option. Only useful if poll method is not 'votes' * + * @param data + * @param option * @returns an positive integer to be used as percentage base, or null */ - private getOptionBaseAmount(poll: ViewAssignmentPoll, option: ViewAssignmentPollOption): number | null { - if (this.percentBase === 'DISABLED' || poll.pollmethod === 'votes') { + private getOptionBaseAmount(data: CalculationData, option: CalculationOption): number | null { + const percentBase = data.percentBase || this.percentBase; + if (percentBase === 'DISABLED' || data.pollMethod === 'votes') { return null; - } else if (this.percentBase === 'CAST') { - return poll.votescast > 0 ? poll.votescast : null; - } else if (this.percentBase === 'VALID') { - return poll.votesvalid > 0 ? poll.votesvalid : null; + } else if (percentBase === 'CAST') { + return data.votescast > 0 ? data.votescast : null; + } else if (percentBase === 'VALID') { + return data.votesvalid > 0 ? data.votesvalid : null; } const yes = option.votes.find(v => v.value === 'Yes'); const no = option.votes.find(v => v.value === 'No'); - if (this.percentBase === 'YES_NO') { + if (percentBase === 'YES_NO') { if (!yes || yes.weight === undefined || !no || no.weight === undefined) { return null; } return yes.weight >= 0 && no.weight >= 0 ? yes.weight + no.weight : null; - } else if (this.percentBase === 'YES_NO_ABSTAIN') { + } else if (percentBase === 'YES_NO_ABSTAIN') { const abstain = option.votes.find(v => v.value === 'Abstain'); if (!abstain || abstain.weight === undefined) { return null; @@ -235,16 +277,32 @@ export class AssignmentPollService extends PollService { * Get the minimum amount of votes needed for an option to pass the quorum * * @param method - * @param poll + * @param data * @param option * @returns a positive integer number; may return null if quorum is not calculable */ - public yesQuorum( - method: MajorityMethod, - poll: ViewAssignmentPoll, - option: ViewAssignmentPollOption - ): number | null { - const baseAmount = poll.pollmethod === 'votes' ? poll.pollBase : this.getOptionBaseAmount(poll, option); + public yesQuorum(method: MajorityMethod, data: CalculationData, option: ViewAssignmentPollOption): number | null { + const baseAmount = + data.pollMethod === 'votes' ? this.getBaseAmount(data) : this.getOptionBaseAmount(data, option); return method.calc(baseAmount); } + + /** + * helper function to tuirn a Poll into calculation data for this service + * TODO: temp until better method to normalize Poll ans PollSlideData is implemented + * + * @param poll + * @returns calculationData ready to be used + */ + public calculationDataFromPoll(poll: ViewAssignmentPoll): CalculationData { + return { + pollMethod: poll.pollmethod, + votesno: poll.votesno, + votesabstain: poll.votesabstain, + votescast: poll.votescast, + votesinvalid: poll.votesinvalid, + votesvalid: poll.votesvalid, + pollOptions: poll.options + }; + } } diff --git a/client/src/app/slides/assignments/poll/poll-slide-data.ts b/client/src/app/slides/assignments/poll/poll-slide-data.ts index d532a1e2b..0c79c1d38 100644 --- a/client/src/app/slides/assignments/poll/poll-slide-data.ts +++ b/client/src/app/slides/assignments/poll/poll-slide-data.ts @@ -5,9 +5,8 @@ export interface PollSlideOption { user: string; is_elected: boolean; votes: { - weight: PollVoteValue; - value: string; - percent?: string; + weight: string; + value: PollVoteValue; }[]; } diff --git a/client/src/app/slides/assignments/poll/poll-slide.component.html b/client/src/app/slides/assignments/poll/poll-slide.component.html index 420703db6..17c44405b 100644 --- a/client/src/app/slides/assignments/poll/poll-slide.component.html +++ b/client/src/app/slides/assignments/poll/poll-slide.component.html @@ -24,39 +24,18 @@
- {{ vote.value | translate }}: - - {{ labelValue(vote.weight) | translate }} - - - ({{ vote.percent }}) - + {{ getLabel(vote.value) }}: + {{ getVotePercent(vote.value, option) }}
-
+
- Valid votes + {{ getLabel(value) }}
- {{ labelValue(data.data.poll.votesvalid) | translate }} -
-
-
-
- Invalid votes -
-
- {{ labelValue(data.data.poll.votesinvalid) | translate }} -
-
-
-
- Total votes cast -
-
- {{ labelValue(data.data.poll.votescast) | translate }} + {{ getPollPercent(value) }}
diff --git a/client/src/app/slides/assignments/poll/poll-slide.component.ts b/client/src/app/slides/assignments/poll/poll-slide.component.ts index 4ad071ee0..e9c7722a3 100644 --- a/client/src/app/slides/assignments/poll/poll-slide.component.ts +++ b/client/src/app/slides/assignments/poll/poll-slide.component.ts @@ -1,9 +1,15 @@ import { Component, Input } from '@angular/core'; -import { AssignmentPollService, SummaryPollKey } from 'app/site/assignments/services/assignment-poll.service'; +import { + AssignmentPollService, + CalculationData, + SummaryPollKey +} from 'app/site/assignments/services/assignment-poll.service'; import { BaseSlideComponent } from 'app/slides/base-slide-component'; -import { PollSlideData } from './poll-slide-data'; +import { PollSlideData, PollSlideOption } from './poll-slide-data'; +import { PollVoteValue, CalculablePollKey } from 'app/core/ui-services/poll.service'; import { SlideData } from 'app/core/core-services/projector-data.service'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'os-poll-slide', @@ -13,85 +19,78 @@ import { SlideData } from 'app/core/core-services/projector-data.service'; export class PollSlideComponent extends BaseSlideComponent { private _data: SlideData; - public pollValues: SummaryPollKey[] = ['votesno', 'votesabstain', 'votesvalid', 'votesinvalid', 'votescast']; + private calculationData: CalculationData; + + public get pollValues(): SummaryPollKey[] { + if (!this.data) { + return []; + } + const values: SummaryPollKey[] = ['votesno', 'votesabstain', 'votesvalid', 'votesinvalid', 'votescast']; + return values.filter(val => this.data.data.poll[val] !== null); + } @Input() public set data(data: SlideData) { this._data = data; - this.setPercents(); + this.calculationData = { + pollMethod: data.data.poll.pollmethod, + votesno: parseFloat(data.data.poll.votesno), + votesabstain: parseFloat(data.data.poll.votesabstain), + votescast: parseFloat(data.data.poll.votescast), + votesvalid: parseFloat(data.data.poll.votesvalid), + votesinvalid: parseFloat(data.data.poll.votesinvalid), + pollOptions: data.data.poll.options.map(opt => { + return { + votes: opt.votes.map(vote => { + return { + weight: parseFloat(vote.weight), + value: vote.value + }; + }) + }; + }), + percentBase: data.data.assignments_poll_100_percent_base + }; } public get data(): SlideData { return this._data; } - public constructor(private pollService: AssignmentPollService) { + public constructor(private pollService: AssignmentPollService, private translate: TranslateService) { super(); } - public getVoteString(rawValue: string): string { - const num = parseFloat(rawValue); - if (!isNaN(num)) { - return this.pollService.getSpecialLabel(num); - } - return '-'; + /** + * get a vote's numerical or special label, including percent values if these are to + * be displayed + * + * @param key + * @param option + */ + public getVotePercent(key: PollVoteValue, option: PollSlideOption): string { + const calcOption = { + votes: option.votes.map(vote => { + return { weight: parseFloat(vote.weight), value: vote.value }; + }) + }; + const percent = this.pollService.getPercent(this.calculationData, calcOption, key); + const number = this.translate.instant( + this.pollService.getSpecialLabel(parseFloat(option.votes.find(v => v.value === key).weight)) + ); + return percent === null ? number : `${number} (${percent}%)`; } - private setPercents(): void { - if ( - this.data.data.assignments_poll_100_percent_base === 'DISABLED' || - !this.data.data.poll.has_votes || - !this.data.data.poll.options.length - ) { - return; - } - for (const option of this.data.data.poll.options) { - for (const vote of option.votes) { - const voteweight = parseFloat(vote.weight); - if (isNaN(voteweight) || voteweight < 0) { - return; - } - let base: number; - switch (this.data.data.assignments_poll_100_percent_base) { - case 'CAST': - base = this.data.data.poll.votescast ? parseFloat(this.data.data.poll.votescast) : 0; - break; - case 'VALID': - base = this.data.data.poll.votesvalid ? parseFloat(this.data.data.poll.votesvalid) : 0; - break; - case 'YES_NO': - case 'YES_NO_ABSTAIN': - const yesOption = option.votes.find(v => v.value === 'Yes'); - const yes = yesOption ? parseFloat(yesOption.weight) : -1; - const noOption = option.votes.find(v => v.value === 'No'); - const no = noOption ? parseFloat(noOption.weight) : -1; - const absOption = option.votes.find(v => v.value === 'Abstain'); - const abs = absOption ? parseFloat(absOption.weight) : -1; - if (this.data.data.assignments_poll_100_percent_base === 'YES_NO_ABSTAIN') { - base = yes >= 0 && no >= 0 && abs >= 0 ? yes + no + abs : 0; - } else { - if (vote.value !== 'Abstain') { - base = yes >= 0 && no >= 0 ? yes + no : 0; - } - } - break; - default: - break; - } - if (base) { - vote.percent = `${Math.round(((parseFloat(vote.weight) * 100) / base) * 100) / 100}%`; - } - } - } + public getPollPercent(key: CalculablePollKey): string { + const percent = this.pollService.getValuePercent(this.calculationData, key); + const number = this.translate.instant(this.pollService.getSpecialLabel(this.calculationData[key])); + return percent === null ? number : `${number} (${percent}%)`; } /** - * Converts a number-like string to a simpler, more readable version - * - * @param input a server-sent string representing a numerical value - * @returns either the special label or a cleaned-up number of the inpu string + * @returns a translated label for a key */ - public labelValue(input: string): string { - return this.pollService.getSpecialLabel(parseFloat(input)); + public getLabel(key: CalculablePollKey): string { + return this.translate.instant(this.pollService.getLabel(key)); } }