diff --git a/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.html b/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.html index 8fc6d4b34..923f10459 100644 --- a/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.html +++ b/client/src/app/site/assignments/components/assignment-poll/assignment-poll.component.html @@ -139,11 +139,11 @@ >{{ pollService.getLabel(vote.value) | translate }}: {{ pollService.getSpecialLabel(vote.weight) }} - ({{ pollService.getPercent(poll, option, vote.value) }}%) -
+
{{ pollService.getSpecialLabel(poll[key]) | translate }} + + ({{ pollService.getValuePercent(poll,key) }} %) +
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 ee2fe28c1..33a14e2f9 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 @@ -1,18 +1,19 @@ import { Component, OnInit, Input } from '@angular/core'; import { FormGroup, FormBuilder } from '@angular/forms'; import { MatDialog, MatSnackBar } from '@angular/material'; +import { Title } from '@angular/platform-browser'; 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 } from '../../services/assignment-poll.service'; +import { AssignmentPollService, AssignmentPercentBase } 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 { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll'; import { PromptService } from 'app/core/ui-services/prompt.service'; -import { Title } from '@angular/platform-browser'; import { ViewAssignment } from '../../models/view-assignment'; import { ViewAssignmentPoll } from '../../models/view-assignment-poll'; import { ViewAssignmentPollOption } from '../../models/view-assignment-poll-option'; @@ -131,7 +132,8 @@ export class AssignmentPollComponent extends BaseViewComponent implements OnInit public translate: TranslateService, public dialog: MatDialog, private promptService: PromptService, - private formBuilder: FormBuilder + private formBuilder: FormBuilder, + private config: ConfigService ) { super(titleService, translate, matSnackBar); } @@ -146,6 +148,13 @@ 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); + } + }) + ); } /** 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 ba48b8a26..301bbfc1a 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 @@ -5,6 +5,11 @@ 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'; +/** + * Defines the order the option's votes are sorted in (server might send raw data in any order) + */ +const votesOrder: PollVoteValue[] = ['Votes', 'Yes', 'No', 'Abstain']; + export class ViewAssignmentPollOption implements Identifiable, Updateable { private _assignmentPollOption: AssignmentPollOption; private _user: ViewUser; // This is the "candidate". We'll stay consistent wich user here... @@ -39,7 +44,7 @@ export class ViewAssignmentPollOption implements Identifiable, Updateable { weight: number; value: PollVoteValue; }[] { - return this.option.votes; + return this.option.votes.sort((a, b) => votesOrder.indexOf(a.value) - votesOrder.indexOf(b.value)); } public get poll_id(): number { 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 79f733509..44f1ee992 100644 --- a/client/src/app/site/assignments/services/assignment-poll.service.ts +++ b/client/src/app/site/assignments/services/assignment-poll.service.ts @@ -13,7 +13,7 @@ import { ViewAssignmentPoll } from '../models/view-assignment-poll'; type AssignmentPollValues = 'auto' | 'votes' | 'yesnoabstain' | 'yesno'; export type AssignmentPollMethod = 'yn' | 'yna' | 'votes'; -type AssignmentPercentBase = 'YES_NO_ABSTAIN' | 'YES_NO' | 'VALID' | 'CAST' | 'DISABLED'; +export type AssignmentPercentBase = 'YES_NO_ABSTAIN' | 'YES_NO' | 'VALID' | 'CAST' | 'DISABLED'; /** * Service class for assignment polls. @@ -77,7 +77,7 @@ export class AssignmentPollService extends PollService { case 'YES_NO_ABSTAIN': if (poll.pollmethod === 'votes') { const yes = poll.options.map(option => { - const yesValue = option.votes.find(v => v.value === 'Yes'); + const yesValue = option.votes.find(v => v.value === 'Votes'); return yesValue ? yesValue.weight : -99; }); if (Math.min(...yes) < 0) { @@ -106,8 +106,17 @@ export class AssignmentPollService extends PollService { * @returns a percentage number with two digits, null if the value cannot be calculated */ public getPercent(poll: ViewAssignmentPoll, option: ViewAssignmentPollOption, value: PollVoteValue): number | null { - const base = poll.pollmethod === 'votes' ? poll.pollBase : this.getOptionBaseAmount(poll, option); - if (!base) { + let base = 0; + if (this.percentBase === 'DISABLED') { + return null; + } else if (this.percentBase === 'VALID') { + base = poll.votesvalid; + } else if (this.percentBase === 'CAST') { + base = poll.votescast; + } else { + base = poll.pollmethod === 'votes' ? poll.pollBase : this.getOptionBaseAmount(poll, option); + } + if (!base || base < 0) { return null; } const vote = option.votes.find(v => v.value === value); @@ -141,11 +150,15 @@ export class AssignmentPollService extends PollService { * * @param poll * @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): boolean { - if (!option.votes || !option.votes.length) { + public isAbstractOption(poll: ViewAssignmentPoll, option: ViewAssignmentPollOption, key?: PollVoteValue): boolean { + if (this.percentBase === 'DISABLED' || !option.votes || !option.votes.length) { + return true; + } + if (key === 'Abstain' && this.percentBase === 'YES_NO') { return true; } if (poll.pollmethod === 'votes') { @@ -166,7 +179,7 @@ export class AssignmentPollService extends PollService { * Use {@link isAbstractOption} for these */ public isAbstractValue(poll: ViewAssignmentPoll, value: CalculablePollKey): boolean { - if (!poll.pollBase || !this.pollValues.includes(value)) { + if (this.percentBase === 'DISABLED' || !poll.pollBase || !this.pollValues.includes(value)) { return true; } if (this.percentBase === 'CAST' && poll[value] >= 0) { @@ -183,17 +196,21 @@ export class AssignmentPollService extends PollService { * @returns an positive integer to be used as percentage base, or null */ private getOptionBaseAmount(poll: ViewAssignmentPoll, option: ViewAssignmentPollOption): number | null { - if (poll.pollmethod === 'votes') { + if (this.percentBase === 'DISABLED' || poll.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; } const yes = option.votes.find(v => v.value === 'Yes'); const no = option.votes.find(v => v.value === 'No'); - if (poll.pollmethod === 'yn') { + if (this.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 { + } else if (this.percentBase === 'YES_NO_ABSTAIN') { const abstain = option.votes.find(v => v.value === 'Abstain'); if (!abstain || abstain.weight === undefined) { return null;