Merge pull request #6070 from tsiegleauq/stop-voting-publish-prompt

stop-voting shows prompt
This commit is contained in:
Emanuel Schütze 2021-05-26 18:13:00 +02:00 committed by GitHub
commit c60553e376
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 74 additions and 38 deletions

View File

@ -48,7 +48,7 @@
<button
mat-stroked-button
[ngClass]="pollStateActions[poll.state].css"
(click)="changeState(poll.nextState)"
(click)="nextPollState()"
[disabled]="stateChangePending"
>
<mat-icon> {{ pollStateActions[poll.state].icon }}</mat-icon>

View File

@ -8,6 +8,7 @@ import { TranslateService } from '@ngx-translate/core';
import { OperatorService } from 'app/core/core-services/operator.service';
import { AssignmentPollRepositoryService } from 'app/core/repositories/assignments/assignment-poll-repository.service';
import { ChoiceService } from 'app/core/ui-services/choice.service';
import { PromptService } from 'app/core/ui-services/prompt.service';
import { VotingService } from 'app/core/ui-services/voting.service';
import { VotingPrivacyWarningComponent } from 'app/shared/components/voting-privacy-warning/voting-privacy-warning.component';
@ -82,6 +83,7 @@ export class AssignmentPollComponent
translate: TranslateService,
dialog: MatDialog,
promptService: PromptService,
choiceService: ChoiceService,
repo: AssignmentPollRepositoryService,
pollDialog: AssignmentPollDialogService,
private pollService: AssignmentPollService,
@ -90,7 +92,7 @@ export class AssignmentPollComponent
private operator: OperatorService,
private votingService: VotingService
) {
super(titleService, matSnackBar, translate, dialog, promptService, repo, pollDialog);
super(titleService, matSnackBar, translate, dialog, promptService, choiceService, repo, pollDialog);
}
public ngOnInit(): void {

View File

@ -50,7 +50,7 @@
<button
mat-stroked-button
[ngClass]="pollStateActions[poll.state].css"
(click)="changeState(poll.nextState)"
(click)="nextPollState()"
[disabled]="stateChangePending"
>
<mat-icon> {{ pollStateActions[poll.state].icon }}</mat-icon>

View File

@ -7,6 +7,7 @@ import { TranslateService } from '@ngx-translate/core';
import { OperatorService, Permission } from 'app/core/core-services/operator.service';
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
import { ChoiceService } from 'app/core/ui-services/choice.service';
import { PromptService } from 'app/core/ui-services/prompt.service';
import { VotingPrivacyWarningComponent } from 'app/shared/components/voting-privacy-warning/voting-privacy-warning.component';
import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
@ -68,6 +69,7 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll, Motio
titleService: Title,
matSnackBar: MatSnackBar,
promptService: PromptService,
choiceService: ChoiceService,
pollDialog: MotionPollDialogService,
protected dialog: MatDialog,
protected pollRepo: MotionPollRepositoryService,
@ -76,7 +78,7 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll, Motio
private pdfService: MotionPollPdfService,
private operator: OperatorService
) {
super(titleService, matSnackBar, translate, dialog, promptService, pollRepo, pollDialog);
super(titleService, matSnackBar, translate, dialog, promptService, choiceService, pollRepo, pollDialog);
}
public openVotingWarning(): void {

View File

@ -6,13 +6,14 @@ import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject } from 'rxjs';
import { BasePollDialogService } from 'app/core/ui-services/base-poll-dialog.service';
import { ChoiceService } from 'app/core/ui-services/choice.service';
import { PromptService } from 'app/core/ui-services/prompt.service';
import { ChartData } from 'app/shared/components/charts/charts.component';
import { PollState, PollType } from 'app/shared/models/poll/base-poll';
import { BaseViewComponentDirective } from 'app/site/base/base-view';
import { BasePollRepositoryService } from '../services/base-poll-repository.service';
import { PollService } from '../services/poll.service';
import { ViewBasePoll } from '../models/view-base-poll';
import { PollStateChangeActionVerbose, ViewBasePoll } from '../models/view-base-poll';
export abstract class BasePollComponent<
V extends ViewBasePoll,
@ -49,39 +50,47 @@ export abstract class BasePollComponent<
protected translate: TranslateService,
protected dialog: MatDialog,
protected promptService: PromptService,
protected choiceService: ChoiceService,
protected repo: BasePollRepositoryService,
protected pollDialog: BasePollDialogService<V, S>
) {
super(titleService, translate, matSnackBar);
}
public async changeState(key: PollState): Promise<void> {
if (key === PollState.Created) {
const title = this.translate.instant('Are you sure you want to reset this vote?');
const content = this.translate.instant('All votes will be lost.');
if (await this.promptService.open(title, content)) {
this.stateChangePending = true;
this.repo
.resetPoll(this._poll)
.catch(this.raiseError)
.finally(() => {
this.stateChangePending = false;
});
public async nextPollState(): Promise<void> {
const currentState: PollState = this._poll.state;
if (currentState === PollState.Created || currentState === PollState.Finished) {
await this.changeState(this._poll.nextState);
} else if (currentState === PollState.Started) {
const title = this.translate.instant('Are you sure you want to stop this voting?');
const actions = [this.translate.instant('Stop'), this.translate.instant('Stop & publish')];
const choice = await this.choiceService.open(title, null, false, actions);
if (choice?.action === 'Stop') {
await this.changeState(PollState.Finished);
} else if (choice?.action === 'Stop & publish') {
await this.changeState(PollState.Published);
}
} else {
this.stateChangePending = true;
this.repo
.changePollState(this._poll)
.catch(this.raiseError)
.finally(() => {
this.stateChangePending = false;
});
}
}
public resetState(): void {
private async changeState(targetState: PollState): Promise<void> {
this.stateChangePending = true;
this.repo
.changePollState(this._poll, targetState)
.catch(this.raiseError)
.finally(() => {
this.stateChangePending = false;
});
}
public async resetState(): Promise<void> {
const title = this.translate.instant('Are you sure you want to reset this vote?');
const content = this.translate.instant('All votes will be lost.');
if (await this.promptService.open(title, content)) {
this.changeState(PollState.Created);
}
}
/**
* Handler for the 'delete poll' button

View File

@ -56,17 +56,17 @@ export abstract class BasePollRepositoryService<
return viewModel;
}
public changePollState(poll: BasePoll): Promise<void> {
public changePollState(poll: BasePoll, targetState: PollState): Promise<void> {
const path = this.restPath(poll);
switch (poll.state) {
switch (targetState) {
case PollState.Created:
return this.http.post(`${path}/start/`);
return this.http.post(`${path}/reset/`);
case PollState.Started:
return this.http.post(`${path}/stop/`);
return this.http.post(`${path}/start/`);
case PollState.Finished:
return this.http.post(`${path}/publish/`);
return this.http.post(`${path}/stop/`);
case PollState.Published:
return this.resetPoll(poll);
return this.http.post(`${path}/publish/`);
}
}
@ -74,10 +74,6 @@ export abstract class BasePollRepositoryService<
return `/rest/${poll.collectionString}/${poll.id}`;
}
public resetPoll(poll: BasePoll): Promise<void> {
return this.http.post(`${this.restPath(poll)}/reset/`);
}
public pseudoanonymize(poll: BasePoll): Promise<void> {
return this.http.post(`${this.restPath(poll)}/pseudoanonymize/`);
}

View File

@ -164,9 +164,13 @@ class BasePollViewSet(ModelViewSet):
@transaction.atomic
def publish(self, request, pk):
poll = self.get_locked_object()
if poll.state != BasePoll.STATE_FINISHED:
if poll.state not in (BasePoll.STATE_STARTED, BasePoll.STATE_FINISHED):
raise ValidationError({"detail": "Wrong poll state"})
# stop poll if needed
if poll.state == BasePoll.STATE_STARTED:
poll.stop()
poll.state = BasePoll.STATE_PUBLISHED
poll.save()
inform_changed_data(

View File

@ -1284,6 +1284,29 @@ class PublishMotionPoll(TestCase):
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertEqual(MotionPoll.objects.get().state, MotionPoll.STATE_CREATED)
def test_publish_from_started(self):
self.poll.state = MotionPoll.STATE_STARTED
self.poll.save()
response = self.client.post(reverse("motionpoll-publish", args=[self.poll.pk]))
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
self.assertEqual(MotionPoll.objects.get().state, MotionPoll.STATE_PUBLISHED)
def test_publish_from_started_with_entitled_users(self):
self.poll.state = MotionPoll.STATE_STARTED
self.poll.save()
admin = get_user_model().objects.get(username="admin")
admin.is_present = True
admin.save()
self.poll.groups.add(GROUP_ADMIN_PK)
response = self.client.post(reverse("motionpoll-publish", args=[self.poll.pk]))
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
poll = MotionPoll.objects.get()
self.assertEqual(poll.state, MotionPoll.STATE_PUBLISHED)
self.assertEqual(
poll.entitled_users_at_stop,
[{"user_id": admin.id, "voted": False, "vote_delegated_to_id": None}],
)
class PseudoanonymizeMotionPoll(TestCase):
def setUp(self):