Last changes and cleanup some todos
This commit is contained in:
parent
d15c9892ed
commit
64f2720b1a
14
.travis.yml
14
.travis.yml
@ -85,11 +85,7 @@ matrix:
|
||||
- "3.6"
|
||||
script:
|
||||
- mypy openslides/ tests/
|
||||
<<<<<<< HEAD
|
||||
- pytest --cov --cov-fail-under=72
|
||||
=======
|
||||
- pytest --cov --cov-fail-under=74
|
||||
>>>>>>> Initial work for supporting voting
|
||||
- pytest --cov --cov-fail-under=75
|
||||
|
||||
- name: "Server: Tests Python 3.7"
|
||||
language: python
|
||||
@ -100,11 +96,7 @@ matrix:
|
||||
- isort --check-only --diff --recursive openslides tests
|
||||
- black --check --diff --target-version py36 openslides tests
|
||||
- mypy openslides/ tests/
|
||||
<<<<<<< HEAD
|
||||
- pytest --cov --cov-fail-under=72
|
||||
=======
|
||||
- pytest --cov --cov-fail-under=74
|
||||
>>>>>>> Initial work for supporting voting
|
||||
- pytest --cov --cov-fail-under=75
|
||||
|
||||
- name: "Server: Tests Python 3.8"
|
||||
language: python
|
||||
@ -115,7 +107,7 @@ matrix:
|
||||
- isort --check-only --diff --recursive openslides tests
|
||||
- black --check --diff --target-version py36 openslides tests
|
||||
- mypy openslides/ tests/
|
||||
- pytest --cov --cov-fail-under=72
|
||||
- pytest --cov --cov-fail-under=75
|
||||
|
||||
- name: "Client: Linting"
|
||||
language: node_js
|
||||
|
@ -114,14 +114,6 @@ export class AppComponent {
|
||||
.subscribe(() => servertimeService.startScheduler());
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to alter the normal Array.toString - function
|
||||
*
|
||||
* Will add a whitespace after a comma and shorten the output to
|
||||
* three strings.
|
||||
*
|
||||
* TODO: Should be renamed
|
||||
*/
|
||||
private overloadArrayFunctions(): void {
|
||||
Object.defineProperty(Array.prototype, 'toString', {
|
||||
value: function(): string {
|
||||
@ -153,7 +145,6 @@ export class AppComponent {
|
||||
enumerable: false
|
||||
});
|
||||
|
||||
// intersect
|
||||
Object.defineProperty(Array.prototype, 'intersect', {
|
||||
value: function<T>(other: T[]): T[] {
|
||||
let a = this;
|
||||
@ -167,7 +158,6 @@ export class AppComponent {
|
||||
enumerable: false
|
||||
});
|
||||
|
||||
// mapToObject
|
||||
Object.defineProperty(Array.prototype, 'mapToObject', {
|
||||
value: function<T>(f: (item: T) => { [key: string]: any }): { [key: string]: any } {
|
||||
return this.reduce((aggr, item) => {
|
||||
@ -188,18 +178,17 @@ export class AppComponent {
|
||||
* Adds some functions to Set.
|
||||
*/
|
||||
private overloadSetFunctions(): void {
|
||||
// equals
|
||||
Object.defineProperty(Set.prototype, 'equals', {
|
||||
value: function<T>(other: Set<T>): boolean {
|
||||
const _difference = new Set(this);
|
||||
const difference = new Set(this);
|
||||
for (const elem of other) {
|
||||
if (_difference.has(elem)) {
|
||||
_difference.delete(elem);
|
||||
if (difference.has(elem)) {
|
||||
difference.delete(elem);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !_difference.size;
|
||||
return !difference.size;
|
||||
},
|
||||
enumerable: false
|
||||
});
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { _ } from 'app/core/translate/translation-marker';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
|
||||
import { _ } from 'app/core/translate/translation-marker';
|
||||
import { BannerDefinition, BannerService } from '../ui-services/banner.service';
|
||||
|
||||
/**
|
||||
|
@ -71,7 +71,6 @@ export abstract class PollPdfService {
|
||||
* @returns the amount of ballots, depending on the config settings
|
||||
*/
|
||||
protected getBallotCount(): number {
|
||||
// TODO: seems to be broken
|
||||
switch (this.ballotCountSelection) {
|
||||
case 'NUMBER_OF_ALL_PARTICIPANTS':
|
||||
return this.userRepo.getViewModelList().length;
|
||||
|
@ -9,6 +9,7 @@ import { ViewModelStoreService } from 'app/core/core-services/view-model-store.s
|
||||
import { RelationDefinition } from 'app/core/definitions/relations';
|
||||
import { VotingService } from 'app/core/ui-services/voting.service';
|
||||
import { MotionPoll } from 'app/shared/models/motions/motion-poll';
|
||||
import { VoteValue } from 'app/shared/models/poll/base-vote';
|
||||
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
||||
import { ViewMotionOption } from 'app/site/motions/models/view-motion-option';
|
||||
import { MotionPollTitleInformation, ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||
@ -91,7 +92,7 @@ export class MotionPollRepositoryService extends BasePollRepositoryService<
|
||||
return this.translate.instant(plural ? 'Polls' : 'Poll');
|
||||
};
|
||||
|
||||
public vote(vote: 'Y' | 'N' | 'A', poll_id: number): Promise<void> {
|
||||
public vote(vote: VoteValue, poll_id: number): Promise<void> {
|
||||
return this.http.post(`/rest/motions/motion-poll/${poll_id}/vote/`, JSON.stringify(vote));
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +99,6 @@ _('Only main agenda items');
|
||||
_('Topics');
|
||||
_('Open requests to speak');
|
||||
|
||||
|
||||
// ** Motions **
|
||||
// config strings
|
||||
// subgroup general
|
||||
@ -246,7 +245,6 @@ _('Motion block');
|
||||
_('The text field may not be blank.');
|
||||
_('The reason field may not be blank.');
|
||||
|
||||
|
||||
// ** Assignments **
|
||||
// Assignment config strings
|
||||
_('Elections');
|
||||
@ -257,7 +255,7 @@ _('All valid ballots');
|
||||
_('All casted ballots');
|
||||
_('Disabled (no percents)');
|
||||
_('Default groups with voting rights');
|
||||
_('Sort election results by amount of votes')
|
||||
_('Sort election results by amount of votes');
|
||||
_('Put all candidates on the list of speakers');
|
||||
// subgroup ballot papers
|
||||
_('Ballot papers');
|
||||
@ -291,7 +289,6 @@ _('Entitled to vote');
|
||||
_('Voting method');
|
||||
_('Amount of votes');
|
||||
|
||||
|
||||
// ** Users **
|
||||
// permission strings (see models.py of each Django app)
|
||||
// agenda
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { _ } from 'app/core/translate/translation-marker';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { _ } from 'app/core/translate/translation-marker';
|
||||
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
||||
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||
import { ViewBasePoll } from 'app/site/polls/models/view-base-poll';
|
||||
@ -44,10 +44,7 @@ export class VotingBannerService {
|
||||
const banner =
|
||||
pollsToVote.length === 1
|
||||
? this.createBanner(this.getTextForPoll(pollsToVote[0]), pollsToVote[0].parentLink)
|
||||
: this.createBanner(
|
||||
`${pollsToVote.length} ${this.translate.instant('open votes')}`,
|
||||
'/polls/'
|
||||
);
|
||||
: this.createBanner(`${pollsToVote.length} ${this.translate.instant('open votes')}`, '/polls/');
|
||||
this.sliceBanner(banner);
|
||||
}
|
||||
|
||||
|
@ -47,21 +47,15 @@ export class AttachmentControlComponent extends BaseFormControlComponent<ViewMed
|
||||
return 'attachment-control';
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*
|
||||
* @param dialogService Reference to the `MatDialog`
|
||||
* @param mediaService Reference for the `MediaFileRepositoryService`
|
||||
*/
|
||||
public constructor(
|
||||
fb: FormBuilder,
|
||||
fm: FocusMonitor,
|
||||
formBuilder: FormBuilder,
|
||||
focusMonitor: FocusMonitor,
|
||||
element: ElementRef<HTMLElement>,
|
||||
@Optional() @Self() public ngControl: NgControl,
|
||||
private dialogService: MatDialog,
|
||||
private mediaService: MediafileRepositoryService
|
||||
) {
|
||||
super(fb, fm, element, ngControl);
|
||||
super(formBuilder, focusMonitor, element, ngControl);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,12 +96,15 @@ export class AttachmentControlComponent extends BaseFormControlComponent<ViewMed
|
||||
this.errorHandler.emit(error);
|
||||
}
|
||||
|
||||
public onContainerClick(event: MouseEvent): void {
|
||||
// TODO: implement
|
||||
}
|
||||
/**
|
||||
* Declared as abstract in MatFormFieldControl and not required for this component
|
||||
*/
|
||||
public onContainerClick(event: MouseEvent): void {}
|
||||
|
||||
protected initializeForm(): void {
|
||||
this.contentForm = this.fb.control([]);
|
||||
}
|
||||
|
||||
protected updateForm(value: ViewMediafile[] | null): void {
|
||||
this.contentForm.setValue(value || []);
|
||||
}
|
||||
|
@ -15,7 +15,8 @@
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchDefault>
|
||||
<a class="banner-link" [routerLink]="banner.link" [style.cursor]="banner.link ? 'pointer' : 'default'">
|
||||
<mat-icon>{{ banner.icon }}</mat-icon> <span>{{ banner.text }}</span>
|
||||
<mat-icon>{{ banner.icon }}</mat-icon>
|
||||
<span>{{ banner.text }}</span>
|
||||
<div *ngIf="banner.subText">
|
||||
{{ banner.subText | translate }}
|
||||
</div>
|
||||
|
@ -76,7 +76,7 @@ export class ChartsComponent extends BaseViewComponent {
|
||||
}
|
||||
];
|
||||
this.circleColors = !!circleColors[0].backgroundColor.length ? circleColors : null;
|
||||
this.checkChartType();
|
||||
this.checkAndUpdateChartType();
|
||||
this.cd.detectChanges();
|
||||
})
|
||||
);
|
||||
@ -87,7 +87,8 @@ export class ChartsComponent extends BaseViewComponent {
|
||||
*/
|
||||
@Input()
|
||||
public set type(type: ChartType) {
|
||||
this.checkChartType(type);
|
||||
this._type = type;
|
||||
this.checkAndUpdateChartType();
|
||||
this.cd.detectChanges();
|
||||
}
|
||||
|
||||
@ -290,12 +291,10 @@ export class ChartsComponent extends BaseViewComponent {
|
||||
this.cd.detectChanges();
|
||||
}
|
||||
|
||||
private checkChartType(chartType?: ChartType): void {
|
||||
let type = chartType || this._type;
|
||||
if (type === 'stackedBar') {
|
||||
private checkAndUpdateChartType(): void {
|
||||
if (this._type === 'stackedBar') {
|
||||
this.setupBar();
|
||||
type = 'horizontalBar';
|
||||
this._type = 'horizontalBar';
|
||||
}
|
||||
this._type = type;
|
||||
}
|
||||
}
|
||||
|
@ -6,12 +6,6 @@
|
||||
'bar action';
|
||||
grid-template-columns: auto min-content;
|
||||
|
||||
.mat-progress-bar-buffer {
|
||||
// TODO theme
|
||||
// background-color: mat-color($background, card) !important;
|
||||
background-color: white !important;
|
||||
}
|
||||
|
||||
.message {
|
||||
grid-area: message;
|
||||
}
|
||||
|
@ -0,0 +1,10 @@
|
||||
@import '~@angular/material/theming';
|
||||
|
||||
/** Custom component theme. Only lives in a specific scope */
|
||||
@mixin os-progress-snack-bar-style($theme) {
|
||||
$background: map-get($theme, background);
|
||||
|
||||
.mat-progress-bar-buffer {
|
||||
background-color: mat-color($background, card) !important;
|
||||
}
|
||||
}
|
@ -9,8 +9,10 @@
|
||||
[removable]="true"
|
||||
(removed)="removeItem(item.id)"
|
||||
[disableRipple]="true"
|
||||
>{{ item.getTitle() }} <mat-icon matChipRemove>cancel</mat-icon></mat-chip
|
||||
>
|
||||
{{ item.getTitle() }}
|
||||
<mat-icon matChipRemove>cancel</mat-icon>
|
||||
</mat-chip>
|
||||
</mat-chip-list>
|
||||
</div>
|
||||
<div class="os-search-value-selector-chip-placeholder"></div>
|
||||
|
@ -119,17 +119,14 @@ export class SearchValueSelectorComponent extends BaseFormControlComponent<Selec
|
||||
*/
|
||||
private selectableItems: Selectable[];
|
||||
|
||||
/**
|
||||
* Empty constructor
|
||||
*/
|
||||
public constructor(
|
||||
protected translate: TranslateService,
|
||||
fb: FormBuilder,
|
||||
formBuilder: FormBuilder,
|
||||
@Optional() @Self() public ngControl: NgControl,
|
||||
fm: FocusMonitor,
|
||||
focusMonitor: FocusMonitor,
|
||||
element: ElementRef<HTMLElement>
|
||||
) {
|
||||
super(fb, fm, element, ngControl);
|
||||
super(formBuilder, focusMonitor, element, ngControl);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { BaseModel } from './base-model';
|
||||
|
||||
export abstract class BaseDecimalModel<T = any> extends BaseModel<T> {
|
||||
// TODO: no more elegant solution available in current Typescript
|
||||
protected abstract getDecimalFields(): string[];
|
||||
|
||||
public deserialize(input: any): void {
|
||||
|
@ -38,6 +38,10 @@ export enum PercentBase {
|
||||
Disabled = 'disabled'
|
||||
}
|
||||
|
||||
export const VOTE_MAJORITY = -1;
|
||||
export const VOTE_UNDOCUMENTED = -2;
|
||||
export const LOWEST_VOTE_VALUE = VOTE_UNDOCUMENTED;
|
||||
|
||||
export abstract class BasePoll<
|
||||
T = any,
|
||||
O extends BaseOption<any> = any,
|
||||
|
@ -2,6 +2,8 @@ import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { VOTE_MAJORITY, VOTE_UNDOCUMENTED } from '../models/poll/base-poll';
|
||||
|
||||
@Pipe({
|
||||
name: 'parsePollNumber'
|
||||
})
|
||||
@ -11,9 +13,9 @@ export class ParsePollNumberPipe implements PipeTransform {
|
||||
public transform(value: number): number | string {
|
||||
const input = Math.trunc(value);
|
||||
switch (input) {
|
||||
case -1:
|
||||
case VOTE_MAJORITY:
|
||||
return this.translate.instant('majority');
|
||||
case -2:
|
||||
case VOTE_UNDOCUMENTED:
|
||||
return this.translate.instant('undocumented');
|
||||
default:
|
||||
return input;
|
||||
|
@ -18,7 +18,7 @@ export const AssignmentsAppConfig: AppConfig = {
|
||||
{
|
||||
model: Assignment,
|
||||
viewModel: ViewAssignment,
|
||||
// searchOrder: 3, // TODO: enable, if there is a detail page and so on.
|
||||
searchOrder: 3,
|
||||
repository: AssignmentRepositoryService
|
||||
},
|
||||
{
|
||||
|
@ -308,7 +308,6 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
* Creates a new Poll
|
||||
*/
|
||||
public openDialog(): void {
|
||||
// TODO: That is not really a ViewObject
|
||||
const dialogData = {
|
||||
collectionString: ViewAssignmentPoll.COLLECTIONSTRING,
|
||||
assignment_id: this.assignment.id,
|
||||
|
@ -12,6 +12,7 @@ import { AssignmentVoteRepositoryService } from 'app/core/repositories/assignmen
|
||||
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
|
||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
import { ChartType } from 'app/shared/components/charts/charts.component';
|
||||
import { VoteValue } from 'app/shared/models/poll/base-vote';
|
||||
import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component';
|
||||
import { PollService, PollTableData, VotingResult } from 'app/site/polls/services/poll.service';
|
||||
import { AssignmentPollDialogService } from '../../services/assignment-poll-dialog.service';
|
||||
@ -110,7 +111,7 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
|
||||
this.isReady = true;
|
||||
}
|
||||
|
||||
private voteValueToLabel(vote: 'Y' | 'N' | 'A'): string {
|
||||
private voteValueToLabel(vote: VoteValue): string {
|
||||
if (vote === 'Y') {
|
||||
return this.translate.instant('Yes');
|
||||
} else if (vote === 'N') {
|
||||
|
@ -8,7 +8,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
||||
|
||||
import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll';
|
||||
import { PollType } from 'app/shared/models/poll/base-poll';
|
||||
import { LOWEST_VOTE_VALUE, PollType } from 'app/shared/models/poll/base-poll';
|
||||
import { GeneralValueVerbose, VoteValue, VoteValueVerbose } from 'app/shared/models/poll/base-vote';
|
||||
import {
|
||||
AssignmentPollMethodVerbose,
|
||||
@ -168,30 +168,20 @@ export class AssignmentPollDialogComponent extends BasePollDialogComponent<ViewA
|
||||
[option.user_id]: this.fb.group(
|
||||
// for each user, create a form group with a control for each valid input (Y, N, A)
|
||||
this.analogPollValues.mapToObject(value => ({
|
||||
[value]: ['', [Validators.min(-2)]]
|
||||
[value]: ['', [Validators.min(LOWEST_VOTE_VALUE)]]
|
||||
}))
|
||||
)
|
||||
}))
|
||||
),
|
||||
amount_global_no: ['', [Validators.min(-2)]],
|
||||
amount_global_abstain: ['', [Validators.min(-2)]],
|
||||
amount_global_no: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
|
||||
amount_global_abstain: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
|
||||
// insert all used global fields
|
||||
...this.sumValues.mapToObject(sumValue => ({
|
||||
[sumValue]: ['', [Validators.min(-2)]]
|
||||
[sumValue]: ['', [Validators.min(LOWEST_VOTE_VALUE)]]
|
||||
}))
|
||||
});
|
||||
if (this.isAnalogPoll && this.pollData.poll) {
|
||||
this.updateDialogVoteForm(this.pollData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a per-poll value
|
||||
*
|
||||
* @param value
|
||||
* @param weight
|
||||
*/
|
||||
public setSumValue(value: any /*SummaryPollKey*/, weight: string): void {
|
||||
this.pollData[value] = parseFloat(weight);
|
||||
}
|
||||
}
|
||||
|
@ -14,19 +14,18 @@ import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
import { VotingService } from 'app/core/ui-services/voting.service';
|
||||
import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll';
|
||||
import { PollType } from 'app/shared/models/poll/base-poll';
|
||||
import { VoteValue } from 'app/shared/models/poll/base-vote';
|
||||
import { BasePollVoteComponent } from 'app/site/polls/components/base-poll-vote.component';
|
||||
import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
||||
|
||||
// TODO: Duplicate
|
||||
interface VoteActions {
|
||||
vote: Vote;
|
||||
vote: VoteValue;
|
||||
css: string;
|
||||
icon: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
type Vote = 'Y' | 'N' | 'A';
|
||||
|
||||
@Component({
|
||||
selector: 'os-assignment-poll-vote',
|
||||
templateUrl: './assignment-poll-vote.component.html',
|
||||
@ -45,12 +44,12 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponent<ViewAssig
|
||||
title: Title,
|
||||
protected translate: TranslateService,
|
||||
matSnackbar: MatSnackBar,
|
||||
vmanager: VotingService,
|
||||
operator: OperatorService,
|
||||
public vmanager: VotingService,
|
||||
private pollRepo: AssignmentPollRepositoryService,
|
||||
private promptService: PromptService
|
||||
) {
|
||||
super(title, translate, matSnackbar, vmanager, operator);
|
||||
super(title, translate, matSnackbar, operator);
|
||||
}
|
||||
|
||||
public ngOnInit(): void {
|
||||
@ -116,7 +115,7 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponent<ViewAssig
|
||||
});
|
||||
}
|
||||
|
||||
public saveSingleVote(optionId: number, vote: Vote): void {
|
||||
public saveSingleVote(optionId: number, vote: VoteValue): void {
|
||||
if (this.isGlobalOptionSelected()) {
|
||||
delete this.voteRequestData.global;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
AssignmentPollMethod,
|
||||
AssignmentPollPercentBase
|
||||
} from 'app/shared/models/assignments/assignment-poll';
|
||||
import { MajorityMethod } from 'app/shared/models/poll/base-poll';
|
||||
import { MajorityMethod, VOTE_UNDOCUMENTED } from 'app/shared/models/poll/base-poll';
|
||||
import { PollData, PollService, PollTableData, VotingResult } from 'app/site/polls/services/poll.service';
|
||||
import { ViewAssignmentPoll } from '../models/view-assignment-poll';
|
||||
|
||||
@ -77,17 +77,16 @@ export class AssignmentPollService extends PollService {
|
||||
}
|
||||
|
||||
private getGlobalVoteKeys(poll: ViewAssignmentPoll): VotingResult[] {
|
||||
// debugger;
|
||||
return [
|
||||
{
|
||||
vote: 'amount_global_no',
|
||||
showPercent: this.showPercentOfValidOrCast(poll),
|
||||
hide: poll.amount_global_no === -2 || !poll.amount_global_no
|
||||
hide: poll.amount_global_no === VOTE_UNDOCUMENTED || !poll.amount_global_no
|
||||
},
|
||||
{
|
||||
vote: 'amount_global_abstain',
|
||||
showPercent: this.showPercentOfValidOrCast(poll),
|
||||
hide: poll.amount_global_abstain === -2 || !poll.amount_global_abstain
|
||||
hide: poll.amount_global_abstain === VOTE_UNDOCUMENTED || !poll.amount_global_abstain
|
||||
}
|
||||
];
|
||||
}
|
||||
|
@ -1628,7 +1628,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
}
|
||||
|
||||
public openDialog(): void {
|
||||
// TODO: Could be simpler, requires a lot of refactoring
|
||||
const dialogData = {
|
||||
collectionString: ViewMotionPoll.COLLECTIONSTRING,
|
||||
motion_id: this.motion.id,
|
||||
|
@ -5,6 +5,7 @@ import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { LOWEST_VOTE_VALUE } from 'app/shared/models/poll/base-poll';
|
||||
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||
import { BasePollDialogComponent } from 'app/site/polls/components/base-poll-dialog.component';
|
||||
import { PollFormComponent } from 'app/site/polls/components/poll-form/poll-form.component';
|
||||
@ -22,11 +23,11 @@ export class MotionPollDialogComponent extends BasePollDialogComponent<ViewMotio
|
||||
protected pollForm: PollFormComponent<ViewMotionPoll>;
|
||||
|
||||
public constructor(
|
||||
private fb: FormBuilder,
|
||||
title: Title,
|
||||
protected translate: TranslateService,
|
||||
translate: TranslateService,
|
||||
matSnackbar: MatSnackBar,
|
||||
public dialogRef: MatDialogRef<BasePollDialogComponent<ViewMotionPoll>>,
|
||||
private formBuilder: FormBuilder,
|
||||
@Inject(MAT_DIALOG_DATA) public pollData: Partial<ViewMotionPoll>
|
||||
) {
|
||||
super(title, translate, matSnackbar, dialogRef);
|
||||
@ -56,13 +57,13 @@ export class MotionPollDialogComponent extends BasePollDialogComponent<ViewMotio
|
||||
* Pre-executed method to initialize the dialog-form depending on the poll-method.
|
||||
*/
|
||||
private createDialog(): void {
|
||||
this.dialogVoteForm = this.fb.group({
|
||||
Y: ['', [Validators.min(-2)]],
|
||||
N: ['', [Validators.min(-2)]],
|
||||
A: ['', [Validators.min(-2)]],
|
||||
votesvalid: ['', [Validators.min(-2)]],
|
||||
votesinvalid: ['', [Validators.min(-2)]],
|
||||
votescast: ['', [Validators.min(-2)]]
|
||||
this.dialogVoteForm = this.formBuilder.group({
|
||||
Y: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
|
||||
N: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
|
||||
A: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
|
||||
votesvalid: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
|
||||
votesinvalid: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
|
||||
votescast: ['', [Validators.min(LOWEST_VOTE_VALUE)]]
|
||||
});
|
||||
|
||||
if (this.pollData.poll) {
|
||||
|
@ -8,11 +8,12 @@ import { OperatorService } from 'app/core/core-services/operator.service';
|
||||
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
import { VotingService } from 'app/core/ui-services/voting.service';
|
||||
import { VoteValue } from 'app/shared/models/poll/base-vote';
|
||||
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||
import { BasePollVoteComponent } from 'app/site/polls/components/base-poll-vote.component';
|
||||
|
||||
interface VoteOption {
|
||||
vote?: 'Y' | 'N' | 'A';
|
||||
vote?: VoteValue;
|
||||
css?: string;
|
||||
icon?: string;
|
||||
label?: string;
|
||||
@ -50,18 +51,15 @@ export class MotionPollVoteComponent extends BasePollVoteComponent<ViewMotionPol
|
||||
title: Title,
|
||||
translate: TranslateService,
|
||||
matSnackbar: MatSnackBar,
|
||||
vmanager: VotingService,
|
||||
operator: OperatorService,
|
||||
public vmanager: VotingService,
|
||||
private pollRepo: MotionPollRepositoryService,
|
||||
private promptService: PromptService
|
||||
) {
|
||||
super(title, translate, matSnackbar, vmanager, operator);
|
||||
super(title, translate, matSnackbar, operator);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: 'Y' | 'N' | 'A' should refer to some ENUM
|
||||
*/
|
||||
public saveVote(vote: 'Y' | 'N' | 'A'): void {
|
||||
public saveVote(vote: VoteValue): void {
|
||||
this.currentVote.vote = vote;
|
||||
const title = this.translate.instant('Submit selection now?');
|
||||
const content = this.translate.instant('Your decision cannot be changed afterwards.');
|
||||
|
@ -4,6 +4,7 @@ import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
import { VotingPrivacyWarningComponent } from 'app/shared/components/voting-privacy-warning/voting-privacy-warning.component';
|
||||
@ -14,7 +15,6 @@ import { MotionPollPdfService } from 'app/site/motions/services/motion-poll-pdf.
|
||||
import { MotionPollService } from 'app/site/motions/services/motion-poll.service';
|
||||
import { BasePollComponent } from 'app/site/polls/components/base-poll.component';
|
||||
import { PollService, PollTableData } from 'app/site/polls/services/poll.service';
|
||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||
|
||||
/**
|
||||
* Component to show a motion-poll.
|
||||
@ -25,10 +25,6 @@ import { OperatorService } from 'app/core/core-services/operator.service';
|
||||
styleUrls: ['./motion-poll.component.scss']
|
||||
})
|
||||
export class MotionPollComponent extends BasePollComponent<ViewMotionPoll> {
|
||||
/**
|
||||
* The dedicated `ViewMotionPoll`.
|
||||
* TODO: shadows superclass `poll`. Maybe change when chart data is generated?
|
||||
*/
|
||||
@Input()
|
||||
public set poll(value: ViewMotionPoll) {
|
||||
this.initPoll(value);
|
||||
|
@ -8,6 +8,7 @@ import { Label } from 'ng2-charts';
|
||||
import { BehaviorSubject, from, Observable } from 'rxjs';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
|
||||
import { Deferred } from 'app/core/promises/deferred';
|
||||
import { BaseRepository } from 'app/core/repositories/base-repository';
|
||||
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
|
||||
import { BasePollDialogService } from 'app/core/ui-services/base-poll-dialog.service';
|
||||
@ -73,7 +74,7 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
||||
// The observable for the votes-per-user table
|
||||
public votesDataObservable: Observable<BaseVoteData[]>;
|
||||
|
||||
protected optionsLoaded = false;
|
||||
protected optionsLoaded = new Deferred();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -103,7 +104,13 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
||||
protected votesRepo: BaseRepository<ViewBaseVote, BaseVote, object>
|
||||
) {
|
||||
super(title, translate, matSnackbar);
|
||||
votesRepo
|
||||
this.setup();
|
||||
}
|
||||
|
||||
private async setup(): Promise<void> {
|
||||
await this.optionsLoaded;
|
||||
|
||||
this.votesRepo
|
||||
.getViewModelListObservable()
|
||||
.pipe(
|
||||
filter(() => this.poll && this.canSeeVotes), // filter first for valid poll state to avoid unneccessary iteration of potentially thousands of votes
|
||||
@ -111,10 +118,7 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
||||
filter(votes => !!votes.length)
|
||||
)
|
||||
.subscribe(() => {
|
||||
// votes data can only be created when options are ready
|
||||
if (this.optionsLoaded) {
|
||||
this.createVotesData();
|
||||
}
|
||||
this.createVotesData();
|
||||
});
|
||||
}
|
||||
|
||||
@ -156,10 +160,6 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
||||
*/
|
||||
protected onPollLoaded(): void {}
|
||||
|
||||
protected onPollWithOptionsLoaded(): void {
|
||||
this.createVotesData();
|
||||
}
|
||||
|
||||
protected onStateChanged(): void {}
|
||||
|
||||
protected abstract hasPerms(): boolean;
|
||||
@ -194,15 +194,6 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
||||
this.chartDataSubject.next(this.pollService.generateChartData(this.poll));
|
||||
}
|
||||
|
||||
/**
|
||||
* This checks if the poll has votes.
|
||||
*/
|
||||
private checkData(): void {
|
||||
if (this.poll.stateHasVotes) {
|
||||
setTimeout(() => this.initChartData());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper-function to search for this poll and display data or create a new one.
|
||||
*/
|
||||
@ -214,23 +205,12 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
||||
if (poll) {
|
||||
this.poll = poll;
|
||||
this.onPollLoaded();
|
||||
this.waitForOptions();
|
||||
this.checkData();
|
||||
this.createVotesData();
|
||||
this.initChartData();
|
||||
this.optionsLoaded.resolve();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until poll's options are loaded.
|
||||
*/
|
||||
private waitForOptions(): void {
|
||||
if (!this.poll.options || !this.poll.options.length) {
|
||||
setTimeout(() => this.waitForOptions(), 1);
|
||||
} else {
|
||||
this.optionsLoaded = true;
|
||||
this.onPollWithOptionsLoaded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { VOTE_UNDOCUMENTED } from 'app/shared/models/poll/base-poll';
|
||||
import { OneOfValidator } from 'app/shared/validators/one-of-validator';
|
||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||
import { PollFormComponent } from './poll-form/poll-form.component';
|
||||
@ -80,7 +81,9 @@ export abstract class BasePollDialogComponent<T extends ViewBasePoll> extends Ba
|
||||
}
|
||||
|
||||
/**
|
||||
* check recursively whether the given vote data object is empty, meaning all values would be '-2' when sent
|
||||
* check recursively whether the given vote data object is empty, meaning all values would
|
||||
* be VOTE_UNDOCUMENTED when sent
|
||||
*
|
||||
* @param voteData the (partial) vote data
|
||||
*/
|
||||
private isVoteDataEmpty(voteData: object): boolean {
|
||||
@ -91,7 +94,7 @@ export abstract class BasePollDialogComponent<T extends ViewBasePoll> extends Ba
|
||||
|
||||
/**
|
||||
* iterates over the given data and returns a new object with all empty fields recursively
|
||||
* replaced with '-2'
|
||||
* replaced with VOTE_UNDOCUMENTED
|
||||
* @param voteData the (partial) data
|
||||
*/
|
||||
private replaceEmptyValues(voteData: object, undo: boolean = false): object {
|
||||
@ -101,9 +104,9 @@ export abstract class BasePollDialogComponent<T extends ViewBasePoll> extends Ba
|
||||
result[key] = this.replaceEmptyValues(voteData[key], undo);
|
||||
} else {
|
||||
if (undo) {
|
||||
result[key] = voteData[key] === -2 ? null : voteData[key];
|
||||
result[key] = voteData[key] === VOTE_UNDOCUMENTED ? null : voteData[key];
|
||||
} else {
|
||||
result[key] = !!voteData[key] ? voteData[key] : -2;
|
||||
result[key] = !!voteData[key] ? voteData[key] : VOTE_UNDOCUMENTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -111,7 +114,9 @@ export abstract class BasePollDialogComponent<T extends ViewBasePoll> extends Ba
|
||||
}
|
||||
|
||||
/**
|
||||
* reverses the replacement of empty values by '-2'; replaces each '-2' with null
|
||||
* reverses the replacement of empty values by VOTE_UNDOCUMENTED; replaces each
|
||||
* VOTE_UNDOCUMENTED with null
|
||||
*
|
||||
* @param voteData the vote data
|
||||
*/
|
||||
protected undoReplaceEmptyValues(voteData: object): object {
|
||||
|
@ -5,7 +5,7 @@ import { Title } from '@angular/platform-browser';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||
import { VotingError, VotingService } from 'app/core/ui-services/voting.service';
|
||||
import { VotingError } from 'app/core/ui-services/voting.service';
|
||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
import { ViewBasePoll } from '../models/view-base-poll';
|
||||
@ -20,9 +20,8 @@ export abstract class BasePollVoteComponent<V extends ViewBasePoll> extends Base
|
||||
|
||||
public constructor(
|
||||
title: Title,
|
||||
protected translate: TranslateService,
|
||||
translate: TranslateService,
|
||||
matSnackbar: MatSnackBar,
|
||||
public vmanager: VotingService,
|
||||
protected operator: OperatorService
|
||||
) {
|
||||
super(title, translate, matSnackbar);
|
||||
@ -30,10 +29,7 @@ export abstract class BasePollVoteComponent<V extends ViewBasePoll> extends Base
|
||||
this.subscriptions.push(
|
||||
this.operator.getViewUserObservable().subscribe(user => {
|
||||
this.user = user;
|
||||
// this.updateVotes();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// protected abstract updateVotes(): void;
|
||||
}
|
||||
|
@ -205,7 +205,6 @@ export class PollFormComponent<T extends ViewBasePoll> extends BaseViewComponent
|
||||
/**
|
||||
* Disable votes_amount form control if the poll type is anonymous
|
||||
* and the poll method is votes.
|
||||
* TODO: Enabling this requires at least another layout and some rework
|
||||
*/
|
||||
private setVotesAmountCtrl(): void {
|
||||
if (this.contentForm.get('type').value === PollType.Pseudoanonymous) {
|
||||
|
@ -3,7 +3,14 @@ import { Injectable } from '@angular/core';
|
||||
import { _ } from 'app/core/translate/translation-marker';
|
||||
import { ChartData, ChartDate } from 'app/shared/components/charts/charts.component';
|
||||
import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll';
|
||||
import { BasePoll, MajorityMethod, PercentBase, PollColor, PollType } from 'app/shared/models/poll/base-poll';
|
||||
import {
|
||||
BasePoll,
|
||||
MajorityMethod,
|
||||
PercentBase,
|
||||
PollColor,
|
||||
PollType,
|
||||
VOTE_UNDOCUMENTED
|
||||
} from 'app/shared/models/poll/base-poll';
|
||||
import { AssignmentPollMethodVerbose } from 'app/site/assignments/models/view-assignment-poll';
|
||||
import {
|
||||
MajorityMethodVerbose,
|
||||
@ -70,17 +77,17 @@ export const PollMajorityMethod: CalculableMajorityMethod[] = [
|
||||
{
|
||||
value: 'simple_majority',
|
||||
display_name: 'Simple majority',
|
||||
calc: base => calcMajority(base * 0.5, true)
|
||||
calc: base => calcMajority(base / 2, true)
|
||||
},
|
||||
{
|
||||
value: 'two-thirds_majority',
|
||||
display_name: 'Two-thirds majority',
|
||||
calc: base => calcMajority((base / 3) * 2)
|
||||
calc: base => calcMajority((base * 2) / 3)
|
||||
},
|
||||
{
|
||||
value: 'three-quarters_majority',
|
||||
display_name: 'Three-quarters majority',
|
||||
calc: base => calcMajority((base / 4) * 3)
|
||||
calc: base => calcMajority((base * 3) / 4)
|
||||
},
|
||||
{
|
||||
value: 'disabled',
|
||||
@ -91,6 +98,7 @@ export const PollMajorityMethod: CalculableMajorityMethod[] = [
|
||||
|
||||
export interface PollData {
|
||||
pollmethod: string;
|
||||
type: string;
|
||||
onehundred_percent_base: string;
|
||||
options: {
|
||||
user?: {
|
||||
@ -242,20 +250,18 @@ export abstract class PollService {
|
||||
return [
|
||||
{
|
||||
vote: 'votesvalid',
|
||||
hide: poll.votesvalid === -2,
|
||||
hide: poll.votesvalid === VOTE_UNDOCUMENTED,
|
||||
showPercent: this.showPercentOfValidOrCast(poll)
|
||||
},
|
||||
{
|
||||
vote: 'votesinvalid',
|
||||
icon: 'not_interested',
|
||||
// TODO || PollType === analog
|
||||
hide: poll.votesinvalid === -2,
|
||||
hide: poll.votesinvalid === VOTE_UNDOCUMENTED || poll.type !== PollType.Analog,
|
||||
showPercent: poll.onehundred_percent_base === PercentBase.Cast
|
||||
},
|
||||
{
|
||||
vote: 'votescast',
|
||||
// TODO || PollType === analog
|
||||
hide: poll.votescast === -2,
|
||||
hide: poll.votescast === VOTE_UNDOCUMENTED || poll.type !== PollType.Analog,
|
||||
showPercent: poll.onehundred_percent_base === PercentBase.Cast
|
||||
}
|
||||
];
|
||||
@ -312,6 +318,6 @@ export abstract class PollService {
|
||||
}
|
||||
|
||||
public isVoteDocumented(vote: number): boolean {
|
||||
return vote !== null && vote !== undefined && vote !== -2;
|
||||
return vote !== null && vote !== undefined && vote !== VOTE_UNDOCUMENTED;
|
||||
}
|
||||
}
|
||||
|
@ -110,9 +110,7 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit {
|
||||
|
||||
this.constantsService.get<UserBackends>('UserBackends').subscribe(backends => (this.userBackends = backends));
|
||||
|
||||
this.groupRepo
|
||||
.getViewModelListObservable()
|
||||
.subscribe(groups => this.groups.next(groups.filter(group => group.id !== 1)));
|
||||
this.groupRepo.getViewModelListObservableWithoutDefaultGroup().subscribe(this.groups);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -31,6 +31,7 @@
|
||||
@import './app/site/motions/modules/motion-poll/motion-poll/motion-poll.component.scss-theme.scss';
|
||||
@import './app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.scss-theme.scss';
|
||||
@import './app/site/assignments/components/assignment-poll-detail/assignment-poll-detail-component.scss-theme.scss';
|
||||
@import './app/shared/components/progress-snack-bar/progress-snack-bar.component.scss-theme.scss';
|
||||
|
||||
/** fonts */
|
||||
@import './assets/styles/fonts.scss';
|
||||
@ -62,6 +63,7 @@ $narrow-spacing: (
|
||||
@include os-motion-poll-style($theme);
|
||||
@include os-motion-poll-detail-style($theme);
|
||||
@include os-assignment-poll-detail-style($theme);
|
||||
@include os-progress-snack-bar-style($theme);
|
||||
}
|
||||
|
||||
/** Load projector specific SCSS values */
|
||||
|
@ -7,7 +7,6 @@ from django.db import connections, models
|
||||
from jsonfield import JSONField
|
||||
|
||||
from openslides.utils import logging
|
||||
|
||||
from openslides.utils.manager import BaseManager
|
||||
|
||||
from ..agenda.mixins import ListOfSpeakersMixin
|
||||
|
@ -184,8 +184,9 @@ class TestCreation(TestCase):
|
||||
self.assertEqual(
|
||||
sorted([group.id for group in mediafile.access_groups.all()]), [2, 4]
|
||||
)
|
||||
self.assertTrue(mediafile.mediafile.name)
|
||||
self.assertEqual(mediafile.path, mediafile.original_filename)
|
||||
self.assertEqual(mediafile.mediafile.name, "")
|
||||
self.assertEqual(mediafile.original_filename, "")
|
||||
self.assertEqual(mediafile.path, mediafile.title + "/")
|
||||
|
||||
def test_with_access_groups_wrong_json(self):
|
||||
response = self.client.post(
|
||||
|
Loading…
Reference in New Issue
Block a user