Merge pull request #6134 from jsangmeister/fix-projected-analog-motion-poll

Fix projection of analog polls & prevent percent base 'entitled' for analog polls
This commit is contained in:
jsangmeister 2021-07-05 09:13:34 +02:00 committed by GitHub
commit b4b0a958d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 83 additions and 40 deletions

View File

@ -108,7 +108,7 @@
"karma-jasmine": "~4.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"npm-license-crawler": "^0.2.1",
"prettier": "^2.1.1",
"prettier": "~2.1.1",
"protractor": "^7.0.0",
"resize-observer-polyfill": "^1.5.1",
"ts-node": "~9.0.0",

View File

@ -43,7 +43,7 @@
</table>
<!-- Chart -->
<div *ngIf="!poll.hasSpecialVoteValues" class="doughnut-chart">
<div *ngIf="showChart()" class="doughnut-chart">
<os-charts type="doughnut" [data]="chartData"></os-charts>
</div>
</ng-container>

View File

@ -72,6 +72,10 @@ export class MotionPollDetailContentComponent extends BaseComponent {
super(titleService, translate);
}
public showChart(): boolean {
return this.pollService.showChart(this.poll);
}
private setTableData(): void {
this.tableData = this.pollService.generateTableData(this.poll);
}

View File

@ -109,7 +109,11 @@ export class MotionPollService extends PollService {
}
public showChart(poll: PollData): boolean {
return poll && poll.options && poll.options.some(option => option.yes >= 0 && option.no >= 0);
return (
poll &&
poll.options &&
poll.options.some(option => option.yes >= 0 && option.no >= 0 && option.abstain >= 0)
);
}
public getPercentBase(poll: PollData): number {

View File

@ -273,7 +273,9 @@ export abstract class BasePollDetailComponentDirective<V extends ViewBasePoll, S
public ngOnDestroy(): void {
super.ngOnDestroy();
this.entitledUsersSubscription.unsubscribe();
this.entitledUsersSubscription = null;
if (this.entitledUsersSubscription) {
this.entitledUsersSubscription.unsubscribe();
this.entitledUsersSubscription = null;
}
}
}

View File

@ -172,7 +172,7 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
this.patchForm(this.contentForm);
}
this.updatePollValues(this.contentForm.value);
this.updatePercentBases(this.pollMethodControl.value);
this.updatePercentBases(this.pollMethodControl.value, this.pollTypeControl.value);
this.subscriptions.push(
// changes to whole form
@ -183,13 +183,12 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
}),
// poll method changes
this.pollMethodControl.valueChanges.subscribe((method: AssignmentPollMethod) => {
if (method) {
this.updatePercentBases(method);
this.setWarning();
}
this.updatePercentBases(method, this.pollTypeControl.value);
this.setWarning();
}),
// poll type changes
this.pollTypeControl.valueChanges.subscribe(() => {
this.pollTypeControl.valueChanges.subscribe((type: PollType) => {
this.updatePercentBases(this.pollMethodControl.value, type);
this.setWarning();
})
);
@ -226,11 +225,12 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
}
/**
* updates the available percent bases according to the pollmethod
* updates the available percent bases according to the pollmethod and type
* @param method the currently chosen pollmethod
* @param type the currently chosen type
*/
private updatePercentBases(method: AssignmentPollMethod): void {
if (method) {
private updatePercentBases(method: AssignmentPollMethod, type: PollType): void {
if (method || type) {
let forbiddenBases = [];
if (method === AssignmentPollMethod.YN) {
forbiddenBases = [PercentBase.YNA, AssignmentPollPercentBase.Y];
@ -239,6 +239,9 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
} else if (method === AssignmentPollMethod.Y || AssignmentPollMethod.N) {
forbiddenBases = [PercentBase.YN, PercentBase.YNA];
}
if (type === PollType.Analog) {
forbiddenBases.push(PercentBase.Entitled);
}
const bases = {};
for (const [key, value] of Object.entries(this.percentBases)) {
@ -248,7 +251,7 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
}
// update value in case that its no longer valid
const percentBaseControl = this.contentForm.get('onehundred_percent_base');
percentBaseControl.setValue(this.getNormedPercentBase(percentBaseControl.value, method));
percentBaseControl.setValue(this.getNormedPercentBase(percentBaseControl.value, method, type));
this.validPercentBases = bases;
}
@ -256,7 +259,8 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
private getNormedPercentBase(
base: AssignmentPollPercentBase,
method: AssignmentPollMethod
method: AssignmentPollMethod,
type: PollType
): AssignmentPollPercentBase {
if (
method === AssignmentPollMethod.YN &&
@ -270,6 +274,8 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
(base === AssignmentPollPercentBase.YN || base === AssignmentPollPercentBase.YNA)
) {
return AssignmentPollPercentBase.Y;
} else if (type === PollType.Analog && base === AssignmentPollPercentBase.Entitled) {
return AssignmentPollPercentBase.Cast;
}
return base;
}

View File

@ -112,10 +112,6 @@ export abstract class ViewBasePoll<
}
}
public get hasSpecialVoteValues(): boolean {
return this.poll.isAnalog && this.options.some(option => option.votes.some(vote => vote.weight < 0));
}
public abstract get pollmethodVerbose(): string;
public abstract get percentBaseVerbose(): string;

View File

@ -37,10 +37,6 @@ export class MotionPollSlideComponent extends BasePollSlideComponentDirective<Mo
return MotionPoll.DECIMAL_FIELDS;
}
public showChart(): boolean {
return this.pollService.showChart(this.pollData);
}
public getTableData(): PollTableData[] {
return this.pollService.generateTableData(this.pollData);
}

View File

@ -110,8 +110,8 @@ class AssignmentPollSerializer(BasePollSerializer):
validated_data.pop("assignment", None)
return super().update(instance, validated_data)
def norm_100_percent_base_to_pollmethod(
self, onehundred_percent_base, pollmethod, old_100_percent_base=None
def norm_100_percent_base(
self, onehundred_percent_base, pollmethod, polltype, old_100_percent_base=None
):
"""
Returns None, if the 100-%-base must not be changed, otherwise the correct 100-%-base.
@ -140,7 +140,9 @@ class AssignmentPollSerializer(BasePollSerializer):
AssignmentPoll.PERCENT_BASE_YNA,
):
return AssignmentPoll.PERCENT_BASE_Y
return None
return super().norm_100_percent_base(
onehundred_percent_base, pollmethod, polltype, old_100_percent_base
)
class AssignmentSerializer(ModelSerializer):

View File

@ -260,15 +260,17 @@ class MotionPollSerializer(BasePollSerializer):
validated_data.pop("motion", None)
return super().update(instance, validated_data)
def norm_100_percent_base_to_pollmethod(
self, onehundred_percent_base, pollmethod, old_100_percent_base=None
def norm_100_percent_base(
self, onehundred_percent_base, pollmethod, polltype, old_100_percent_base=None
):
if (
pollmethod == MotionPoll.POLLMETHOD_YN
and onehundred_percent_base == MotionPoll.PERCENT_BASE_YNA
):
return MotionPoll.PERCENT_BASE_YN
return None
return super().norm_100_percent_base(
onehundred_percent_base, pollmethod, polltype, old_100_percent_base
)
class MotionChangeRecommendationSerializer(ModelSerializer):

View File

@ -77,12 +77,14 @@ class BasePollSerializer(ModelSerializer):
def create(self, validated_data):
"""
Match the 100 percent base to the pollmethod. Change the base, if it does not
fit to the pollmethod.
Match the 100 percent base to the pollmethod and type. Change the base, if it does not
fit to the pollmethod or type.
Set is_pseudoanonymized if type is pseudoanonymous.
"""
new_100_percent_base = self.norm_100_percent_base_to_pollmethod(
validated_data["onehundred_percent_base"], validated_data["pollmethod"]
new_100_percent_base = self.norm_100_percent_base(
validated_data["onehundred_percent_base"],
validated_data["pollmethod"],
validated_data["type"],
)
if new_100_percent_base is not None:
validated_data["onehundred_percent_base"] = new_100_percent_base
@ -92,7 +94,7 @@ class BasePollSerializer(ModelSerializer):
def update(self, instance, validated_data):
"""
Adjusts the 100%-base to the pollmethod. This might be needed,
Adjusts the 100%-base to the pollmethod and type. This might be needed,
if at least one of them was changed. Wrong combinations should be
also handled by the client, but here we make it sure aswell!
@ -109,8 +111,11 @@ class BasePollSerializer(ModelSerializer):
validated_data["is_pseudoanonymized"] = False
instance = super().update(instance, validated_data)
new_100_percent_base = self.norm_100_percent_base_to_pollmethod(
instance.onehundred_percent_base, instance.pollmethod, old_100_percent_base
new_100_percent_base = self.norm_100_percent_base(
instance.onehundred_percent_base,
instance.pollmethod,
instance.type,
old_100_percent_base,
)
if new_100_percent_base is not None:
instance.onehundred_percent_base = new_100_percent_base
@ -136,7 +141,17 @@ class BasePollSerializer(ModelSerializer):
)
return data
def norm_100_percent_base_to_pollmethod(
self, onehundred_percent_base, pollmethod, old_100_percent_base=None
def norm_100_percent_base(
self, onehundred_percent_base, pollmethod, polltype, old_100_percent_base=None
):
raise NotImplementedError()
if (
polltype == BasePoll.TYPE_ANALOG
and onehundred_percent_base == BasePoll.PERCENT_BASE_ENTITLED
):
if (
old_100_percent_base
and old_100_percent_base != BasePoll.PERCENT_BASE_ENTITLED
):
return old_100_percent_base
return BasePoll.PERCENT_BASE_CAST
return None

View File

@ -322,6 +322,22 @@ class CreateMotionPoll(TestCase):
self.assertFalse(MotionPoll.objects.exists())
self.assertFalse(MotionVote.objects.exists())
def test_create_with_invalid_percent_base(self):
response = self.client.post(
reverse("motionpoll-list"),
{
"title": "test_title_PgvqRIvuKuVImEpQJAMZ",
"pollmethod": MotionPoll.POLLMETHOD_YN,
"type": MotionPoll.TYPE_ANALOG,
"motion_id": self.motion.id,
"onehundred_percent_base": MotionPoll.PERCENT_BASE_ENTITLED,
"majority_method": MotionPoll.MAJORITY_SIMPLE,
},
)
self.assertHttpStatusVerbose(response, status.HTTP_201_CREATED)
poll = MotionPoll.objects.get()
self.assertEqual(poll.onehundred_percent_base, MotionPoll.PERCENT_BASE_CAST)
class UpdateMotionPoll(TestCase):
"""