stop-voting shows prompt
Stop voting shows options to either simply stop, publish directly or abort. Was done using choice service. Alter vote repo to simply choose with voting state to adress rather than calculate the next state Add server-side option to publish a poll in the started state
This commit is contained in:
parent
f4c237a18e
commit
6dc5c3bfa9
@ -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>
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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/`);
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user