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
|
<button
|
||||||
mat-stroked-button
|
mat-stroked-button
|
||||||
[ngClass]="pollStateActions[poll.state].css"
|
[ngClass]="pollStateActions[poll.state].css"
|
||||||
(click)="changeState(poll.nextState)"
|
(click)="nextPollState()"
|
||||||
[disabled]="stateChangePending"
|
[disabled]="stateChangePending"
|
||||||
>
|
>
|
||||||
<mat-icon> {{ pollStateActions[poll.state].icon }}</mat-icon>
|
<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 { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
import { AssignmentPollRepositoryService } from 'app/core/repositories/assignments/assignment-poll-repository.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 { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { VotingService } from 'app/core/ui-services/voting.service';
|
import { VotingService } from 'app/core/ui-services/voting.service';
|
||||||
import { VotingPrivacyWarningComponent } from 'app/shared/components/voting-privacy-warning/voting-privacy-warning.component';
|
import { VotingPrivacyWarningComponent } from 'app/shared/components/voting-privacy-warning/voting-privacy-warning.component';
|
||||||
@ -82,6 +83,7 @@ export class AssignmentPollComponent
|
|||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
dialog: MatDialog,
|
dialog: MatDialog,
|
||||||
promptService: PromptService,
|
promptService: PromptService,
|
||||||
|
choiceService: ChoiceService,
|
||||||
repo: AssignmentPollRepositoryService,
|
repo: AssignmentPollRepositoryService,
|
||||||
pollDialog: AssignmentPollDialogService,
|
pollDialog: AssignmentPollDialogService,
|
||||||
private pollService: AssignmentPollService,
|
private pollService: AssignmentPollService,
|
||||||
@ -90,7 +92,7 @@ export class AssignmentPollComponent
|
|||||||
private operator: OperatorService,
|
private operator: OperatorService,
|
||||||
private votingService: VotingService
|
private votingService: VotingService
|
||||||
) {
|
) {
|
||||||
super(titleService, matSnackBar, translate, dialog, promptService, repo, pollDialog);
|
super(titleService, matSnackBar, translate, dialog, promptService, choiceService, repo, pollDialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
<button
|
<button
|
||||||
mat-stroked-button
|
mat-stroked-button
|
||||||
[ngClass]="pollStateActions[poll.state].css"
|
[ngClass]="pollStateActions[poll.state].css"
|
||||||
(click)="changeState(poll.nextState)"
|
(click)="nextPollState()"
|
||||||
[disabled]="stateChangePending"
|
[disabled]="stateChangePending"
|
||||||
>
|
>
|
||||||
<mat-icon> {{ pollStateActions[poll.state].icon }}</mat-icon>
|
<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 { OperatorService, Permission } from 'app/core/core-services/operator.service';
|
||||||
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.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 { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { VotingPrivacyWarningComponent } from 'app/shared/components/voting-privacy-warning/voting-privacy-warning.component';
|
import { VotingPrivacyWarningComponent } from 'app/shared/components/voting-privacy-warning/voting-privacy-warning.component';
|
||||||
import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
|
import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
|
||||||
@ -68,6 +69,7 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll, Motio
|
|||||||
titleService: Title,
|
titleService: Title,
|
||||||
matSnackBar: MatSnackBar,
|
matSnackBar: MatSnackBar,
|
||||||
promptService: PromptService,
|
promptService: PromptService,
|
||||||
|
choiceService: ChoiceService,
|
||||||
pollDialog: MotionPollDialogService,
|
pollDialog: MotionPollDialogService,
|
||||||
protected dialog: MatDialog,
|
protected dialog: MatDialog,
|
||||||
protected pollRepo: MotionPollRepositoryService,
|
protected pollRepo: MotionPollRepositoryService,
|
||||||
@ -76,7 +78,7 @@ export class MotionPollComponent extends BasePollComponent<ViewMotionPoll, Motio
|
|||||||
private pdfService: MotionPollPdfService,
|
private pdfService: MotionPollPdfService,
|
||||||
private operator: OperatorService
|
private operator: OperatorService
|
||||||
) {
|
) {
|
||||||
super(titleService, matSnackBar, translate, dialog, promptService, pollRepo, pollDialog);
|
super(titleService, matSnackBar, translate, dialog, promptService, choiceService, pollRepo, pollDialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
public openVotingWarning(): void {
|
public openVotingWarning(): void {
|
||||||
|
@ -6,13 +6,14 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
import { BasePollDialogService } from 'app/core/ui-services/base-poll-dialog.service';
|
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 { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { ChartData } from 'app/shared/components/charts/charts.component';
|
import { ChartData } from 'app/shared/components/charts/charts.component';
|
||||||
import { PollState, PollType } from 'app/shared/models/poll/base-poll';
|
import { PollState, PollType } from 'app/shared/models/poll/base-poll';
|
||||||
import { BaseViewComponentDirective } from 'app/site/base/base-view';
|
import { BaseViewComponentDirective } from 'app/site/base/base-view';
|
||||||
import { BasePollRepositoryService } from '../services/base-poll-repository.service';
|
import { BasePollRepositoryService } from '../services/base-poll-repository.service';
|
||||||
import { PollService } from '../services/poll.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<
|
export abstract class BasePollComponent<
|
||||||
V extends ViewBasePoll,
|
V extends ViewBasePoll,
|
||||||
@ -49,38 +50,46 @@ export abstract class BasePollComponent<
|
|||||||
protected translate: TranslateService,
|
protected translate: TranslateService,
|
||||||
protected dialog: MatDialog,
|
protected dialog: MatDialog,
|
||||||
protected promptService: PromptService,
|
protected promptService: PromptService,
|
||||||
|
protected choiceService: ChoiceService,
|
||||||
protected repo: BasePollRepositoryService,
|
protected repo: BasePollRepositoryService,
|
||||||
protected pollDialog: BasePollDialogService<V, S>
|
protected pollDialog: BasePollDialogService<V, S>
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar);
|
super(titleService, translate, matSnackBar);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async changeState(key: PollState): Promise<void> {
|
public async nextPollState(): Promise<void> {
|
||||||
if (key === PollState.Created) {
|
const currentState: PollState = this._poll.state;
|
||||||
const title = this.translate.instant('Are you sure you want to reset this vote?');
|
if (currentState === PollState.Created || currentState === PollState.Finished) {
|
||||||
const content = this.translate.instant('All votes will be lost.');
|
await this.changeState(this._poll.nextState);
|
||||||
if (await this.promptService.open(title, content)) {
|
} else if (currentState === PollState.Started) {
|
||||||
this.stateChangePending = true;
|
const title = this.translate.instant('Are you sure you want to stop this voting?');
|
||||||
this.repo
|
const actions = [this.translate.instant('Stop'), this.translate.instant('Stop & publish')];
|
||||||
.resetPoll(this._poll)
|
const choice = await this.choiceService.open(title, null, false, actions);
|
||||||
.catch(this.raiseError)
|
|
||||||
.finally(() => {
|
if (choice?.action === 'Stop') {
|
||||||
this.stateChangePending = false;
|
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.changeState(PollState.Created);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,17 +56,17 @@ export abstract class BasePollRepositoryService<
|
|||||||
return viewModel;
|
return viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public changePollState(poll: BasePoll): Promise<void> {
|
public changePollState(poll: BasePoll, targetState: PollState): Promise<void> {
|
||||||
const path = this.restPath(poll);
|
const path = this.restPath(poll);
|
||||||
switch (poll.state) {
|
switch (targetState) {
|
||||||
case PollState.Created:
|
case PollState.Created:
|
||||||
return this.http.post(`${path}/start/`);
|
return this.http.post(`${path}/reset/`);
|
||||||
case PollState.Started:
|
case PollState.Started:
|
||||||
return this.http.post(`${path}/stop/`);
|
return this.http.post(`${path}/start/`);
|
||||||
case PollState.Finished:
|
case PollState.Finished:
|
||||||
return this.http.post(`${path}/publish/`);
|
return this.http.post(`${path}/stop/`);
|
||||||
case PollState.Published:
|
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}`;
|
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> {
|
public pseudoanonymize(poll: BasePoll): Promise<void> {
|
||||||
return this.http.post(`${this.restPath(poll)}/pseudoanonymize/`);
|
return this.http.post(`${this.restPath(poll)}/pseudoanonymize/`);
|
||||||
}
|
}
|
||||||
|
@ -164,9 +164,13 @@ class BasePollViewSet(ModelViewSet):
|
|||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def publish(self, request, pk):
|
def publish(self, request, pk):
|
||||||
poll = self.get_locked_object()
|
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"})
|
raise ValidationError({"detail": "Wrong poll state"})
|
||||||
|
|
||||||
|
# stop poll if needed
|
||||||
|
if poll.state == BasePoll.STATE_STARTED:
|
||||||
|
poll.stop()
|
||||||
|
|
||||||
poll.state = BasePoll.STATE_PUBLISHED
|
poll.state = BasePoll.STATE_PUBLISHED
|
||||||
poll.save()
|
poll.save()
|
||||||
inform_changed_data(
|
inform_changed_data(
|
||||||
|
@ -1284,6 +1284,29 @@ class PublishMotionPoll(TestCase):
|
|||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertEqual(MotionPoll.objects.get().state, MotionPoll.STATE_CREATED)
|
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):
|
class PseudoanonymizeMotionPoll(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user