socialappslab / appcivist-pb-client

Web Client of AppCivist, for the instance of Participatory Budgeting
Other
4 stars 2 forks source link

Review/implement distributed voting UI in WGs and test that it works well with latest version #1427

Open cdparra opened 5 years ago

cdparra commented 5 years ago

Campaign Voting Status:

Problems:

Related errors on campaign loading

$scope.afterLoadingBallotSuccess = function(data) { this.ballotPaperNotFound = !1, this.startVotingDisabled = !1, this.showVotingButtons = !0, this.ballotPaper = data, this.ballotPaper && (this.ballot = this.ballotPaper.ballot, this.ballotPassword = this.ballot.password, this.voteRecord = this.ballotPaper.vote, this.ballotPaperFinished = 0 < this.voteRecord.status, this.votingSignature = this.voteRecord.signature, this.voteRecord || (this.voteRecord = this.ballotPaper.vote = []), this.votes = this.voteRecord ? this.voteRecord.votes : [], this.votes && 0 !== this.votes.length ? this.votesIndex = this.voteRecord.votesIndex : this.votesIndex = this.voteRecord.votesIndex = {}, this.initializeBallotTokens()), this.savingVotes && (this.savingVotes = !1, angular.element("#saveVotes").modal({ show: !0 })), this.finalizingVotes && (this.finalizingVotes = !1, angular.element("#finalizeVotes").modal({ show: !1 }), angular.element("#finalizeVotesDone").modal({ show: !0 })) }

- [ ] Second error has to do with reading individual votes in the paper ballot, when there ar none 
```javascript
// TypeError: Cannot read property 'null' of undefined
//    at contributionCardVotingControlCtrl.<anonymous> 

function contributionCardVotingControlCtrl($scope) {
        var _this = this;
        this.activate = function() {
            this.ballotPaper && (this.ballot = this.ballotPaper.ballot,
            this.voteRecord = this.ballotPaper.vote,
            this.ballotPaperFinished = this.voteRecord && 0 < this.voteRecord.status,
            this.candidatesIndex = this.ballot ? this.ballot.candidatesIndex : null,
            this.candidates = this.ballot ? this.ballot.candidates : [],
            this.votesIndex = this.voteRecord ? this.voteRecord.votesIndex : null,
            this.votesIndex || (this.voteRecord = {},
            this.voteRecord.votesIndex = {},
            this.votesIndex = this.voteRecord.votesIndex),
            this.votes = this.voteRecord ? this.voteRecord.votes : [],
            this.contributionUuid = this.contribution ? this.contribution.uuid : null,
            this.candidateIndex = this.candidatesIndex && this.contributionUuid ? this.candidatesIndex[this.contributionUuid] : null,
            this.candidate = 0 <= this.candidateIndex ? this.candidates[this.candidateIndex] : null,
            this.candidateId = this.candidate ? this.candidate.id : null,
            this.voteIndex = this.votesIndex && this.candidateId ? this.votesIndex[this.candidateId] : null,
            this.vote = 0 <= this.voteIndex ? this.votes[this.voteIndex] : null,
            this.vote || null === this.candidateId || void 0 === this.candidateId || (this.vote = {
                candidate_id: this.candidateId,
                value: "PLURALITY" === this.ballot.voting_system_type ? "" : 0
            },
            this.votesIndex[this.candidateId] = this.votes.length,
            this.voteRecord.votes.push(this.vote)),
            this.voteValue = this.vote ? parseInt(this.vote.value) : 0,
            this.maxTokens = this.ballot ? parseInt(this.ballot.votes_limit) : 0)
        }

this.updateVote = function() { null !== this.voteValue && void 0 !== this.voteValue && "null" !== this.voteValue && "undefined" !== this.voteValue || (this.voteValue = 0); var oldValue = this.vote.value; null != oldValue && "null" !== oldValue && "undefined" !== oldValue || (oldValue = 0); var diff = this.voteValue - oldValue , updatedRemainder = this.ballotTokens ? this.ballotTokens.points - diff : 0; 0 < diff && 0 <= updatedRemainder || diff < 0 && updatedRemainder <= this.maxTokens ? (this.vote.value = this.voteValue + "", this.ballotTokens.points = updatedRemainder) : this.voteValue = parseInt(this.vote.value) }


- [ ] **Save votes error**

PUT https://testplatform.appcivist.org/voting/api/v0/ballot/9e27205c-3a65-4fe2-b7a4-2a7d932839af/vote 404 (Not Found)

And buttons become disabled
![image](https://user-images.githubusercontent.com/630150/58912055-39fd9c00-86e7-11e9-867d-a471918dd203.png)

- [ ] **Finalize voting error**

ui.js:3 TypeError: Cannot convert undefined or null to object at Scope. (https://testpb.appcivist.org/scripts/app.js:3:276433) at fn (eval at compile (https://testpb.appcivist.org/scripts/ui.js:1:1), :4:230) at expensiveCheckFn (https://testpb.appcivist.org/scripts/ui.js:3:921649) at callback (https://testpb.appcivist.org/scripts/ui.js:3:992237) at Scope.$eval (https://testpb.appcivist.org/scripts/ui.js:3:938790) at Scope.$apply (https://testpb.appcivist.org/scripts/ui.js:3:939129) at HTMLButtonElement. (https://testpb.appcivist.org/scripts/ui.js:3:992344) at HTMLButtonElement.dispatch (https://testpb.appcivist.org/scripts/ui.js:3:661083) at HTMLButtonElement.r.handle (https://testpb.appcivist.org/scripts/ui.js:3:659169) at HTMLButtonElement.wrapped (https://testpb.appcivist.org/scripts/ui.js:3:2371707) undefined



- [ ] **Additional problem:** there should be default "No instructions on this ballot" message when ballot instructions are empty. 
![image](https://user-images.githubusercontent.com/630150/58912383-e6d81900-86e7-11e9-80bf-8a71e6e1f0c4.png)

## Open questions:
- [ ] As of now, only PUBLISHED proposals are included in the ballot and their status change to "INBALLOT". Do we want to automatically include also FORKED_PUBLISHED proposals (i.e., dissensus amendments)? Should they become something like INBALLOT_DISSENSUS? 
- [ ] TEST: What happens with proposals' PeerDocs when their status changes to INBALLOT?
cdparra commented 5 years ago

Working Group Voting Status

Problems:

{
  "ballot": {...},
  "ballot_configurations":[
     {"key":"component.voting.system.range.min-score","value":0}, 
     {"key":"component.voting.system.range.max-score","value":100}]
  "ballot_registration_fields": {...}
}
 4.2. Ranked => should display a field asking what is the number of proposals to rank with the label `How many proposals can be included in the ranking?`
{
  "ballot": {...}.
  "ballot_configurations": [
      {"key":"component.voting.system.ranked.number-proposals","value":5}
  ],
  "ballot_registration_fields": {...}
}
 4.3. Distribution => should display a field asking to configure the `Number of votes or points that the voter can distribute among candidates`. See the example below of how this is included in the body. 
{
  "ballot": {...}.
  "ballot_configurations": [
      {"key": "component.voting.system.distributed.points", "value": 30}
  ],
  "ballot_registration_fields": {...}
}
 4.4. Plurality => should ask `Which type of plurality voting will be used?` with options being: "YES votes only", "YES and NO votes", "YES, NO and ABSTAIN votes", and "YES, NO, ABSTAIN and BLOCK votes". If the later is chosen, another option should appear asking `What's the blocking threshold`? 
{
  "ballot": {...}.
  "ballot_configurations": [
      {"key": "component.voting.system.plurality.type", "value": "YES"}
  ],
  "ballot_registration_fields": {...}
}

image

Open Questions:

cdparra commented 5 years ago

Distributed Voting process and API status

Currently, the Voting API has support for tallying votes of a ballot of types plurality, range, and distributed (named cumulative in the API). See code below to know which module implements each type.

...
 when "PLURALITY", "APPROVAL"
      RangeVoting.sort_candidates_by_score(@ballot.votes)         results =  PluralityVoting.sort_candidates_by_score(@ballot.votes)
    when "RANGE"
      results =  PluralityVoting.sort_candidates_by_score(@ballot.votes)
    when "DISTRIBUTED", "CUMULATIVE"
      results =  CumulativeVoting.sort_candidates_by_score(@ballot.votes)
    end

None of these voting systems are fully complete. Basically, we only have the methods for tallying the votes for each system. For example, in distributed, the tallying process simply sums up votes received for each candidate and returns the sorted array of candidates along with their scores, but the validation mechanism is not implemented for any voting system (i.e., we do not check if the voter submitted only N votes in distributed for example). When voting is distributed, each voter has N votes to distribute among the candidates. The winning proposal is the one with most votes. Or using the Schulze method.

Validation is needed in all the systems when saving or updating votes (see https://github.com/socialappslab/appcivist-voting-api/blob/d83e844979531a9aa5baffd36325912ec93490ae/app/models/ballot_paper.rb), the voting API does not check if the votes distributed by the voter equal the total number of votes that are allowed to distribute (in the ballot configurations) in the case of distributed. It does not check if votes are within the minimum and maximum allowed scores in RANGE, and it does not check plurality rules related to abstain or blocking options. So this is a TODO in the voting API, that could be simply implemented in the UI for the moment. In fact, the UI might already have some of these checks in place for the campaign page, but certainly not for the WG page.

So, we should now focus only in the WG voting story, and fix later the current issues with campaign voting.

WG Voting Story:

The basic idea for voting in the WG page is that a coordinator can, at any given time, create a ballot that would include a list of candidates representing proposals in the group. By default, all published proposals could be included, but ideally, the coordinator should be able to select which proposals to include in the ballot (so as to create mini decision making competitions sort of).

The current model only allows for one ballot at a time, but ideally, a WG might contain several ballots at the same time (for example, to make decisions on multiple groups of dissensus proposals), and members could select which ballot they want to vote on, therefore filtering contributions bellow including only those in the selected ballot.

The idea of having multiple ballots (i.e., multiple voting processes with different list of candidates) running in parallel might also be useful for the campaign page. Perhaps also there the voting can be configured by coordinators rather than automatically (as it is now).

ToDo List.

Based on comments above, below is the preliminary ToDo of priorities (on top of all the problems identified in the previous comments).

yolile commented 4 years ago

This is still an issue