Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
cdparra opened this issue Feb 20, 2019 · 4 comments

Comments

@cdparra
Copy link
Member

cdparra commented Feb 20, 2019

No description provided.

@cdparra
Copy link
Member Author

cdparra commented Jun 4, 2019

Campaign Voting Status:

  • ✅ Ballot should come when reading the campaign in the attribute ballotIndex which is an index of all ballots associated to this campaign or assembly, indexed by uuid. The property currentBallot of the campaign should indicate which of these ballots is the current one.
  • ✅ Automatic ballots creation for campaigns in voting phase
  • ✅ Automatically created ballot contains all PUBLISHED proposals.
  • ✅ Ballot paper creation when clicking on "Begin Voting"
  • ✅ After beginning the vote, voting controls appear under cards
  • ❌ Changing the vote using the voting controls under the card updates the individual vote on backend
  • ❌ Updating all the votes by using Save Votes
  • ❌ Finalize voting should change the status of the ballot paper and disable the voting controls.
  • ❌ Sort by voting results should be available if the Campaign has a ballot that already ended
  • ❌ If the campaign has a ballot that ended, the resulting tallied vote for each proposal should appear below the card

Problems:

Related errors on campaign loading

  • First error has to do with property password of the ballot not being available
//TypeError: Cannot read property 'password' of undefined
//    at Scope.<anonymous> (https://testpb.appcivist.org/scripts/app.js:3:273808)`

$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
// 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)
        }
  • Change individual vote error
// TypeError: Cannot read property 'value' of undefined
//    at contributionCardVotingControlCtrl.

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

  • Finalize voting error
ui.js:3 TypeError: Cannot convert undefined or null to object
    at Scope.<anonymous> (https://testpb.appcivist.org/scripts/app.js:3:276433)
    at fn (eval at compile (https://testpb.appcivist.org/scripts/ui.js:1:1), <anonymous>: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.<anonymous> (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

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
Copy link
Member Author

cdparra commented Jun 4, 2019

Working Group Voting Status

  • ✅ Start voting menu is available only for coordinators
  • ➖Configure voting modal is shown when clicking on start voting
  • ➖ POST ballot creation to voting API when you click send from voting modal
  • ❌ The configure voting modal should ask specific configuration field for each voting type.
  • ❌ Ranked should be disabled (because it is not supported yet and we will only try to implement distribution in addition to range and plurality that already are there
  • ❌ If the WG has a ballot, display the same voting controls as in the campaign, also showing first the voting instructions modal.
  • ❌ If the WG has currently an active ballot, the menu for coordinators should contain "End voting". This should end the voting.
  • ❌ Sort by voting results should be available if the WG has a ballot that already ended
  • ❌ If the WG has a ballot tha ended, the resulting tallied vote for each proposal should appear below the card

Problems:

  • POST ballot is returning always {"error":"Votes limit can't be blank"} even when in all cases, default values for ballot configurations are included in the body. The results should be that a ballot is created. After a ballot is created, the WG should be updated to include the ballot uuid in the property consensus_ballot. If there was another ballot in there, it should be archived in the working_group_ballot_history table.

  • Configure voting modal has missing fields:

  1. Voting starting date (defaults to today)
  2. Voting end date (defaults to a week from Today)
  3. Instructions field (should allow for simple formatting: bold, cursive, paragraphs, links)
  4. For each voting system type, there are additional configurations that are not shown right now, and that should be displayed under a section called Ballot configurations. Each configuration is later included as a config object, with a property key and property value, inside an array of configurations called ballot_configurations. Below the list of configs that should be shown and are currently not:
    4.1. Range => should display fields to configure a minimum and maximum score that can be assigned to candidates with labels Minimum score, and Maximum score. Below an example that shows the keys to use.
{
  "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

  • When the parent campaign is in a voting phase, creating proposals should be disabled.
    image
  • Start voting modal shows issues with translations when you open the first time. If you close it and then open again it no longer shows the problems.
    image

Open Questions:

  • Should we allow a Working Group Coordinator to select which proposals to include in the ballot?
  • Should we allow a Working Group to have more than one ballot in parallel (currently we can only have one ballot at a time)?

@cdparra
Copy link
Member Author

cdparra commented Jun 4, 2019

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).

  • In the WG: Implement the story above and solve the problems identified in the comments before this.
  • In the Voting API: when a user updates individual votes or updates all its votes at once, check if that the limit of votes to distribute is respected, if not, reject the update with an error message. This should be implemented in the model BallotPaper.
  • In the campaign page: when the ballot is of the type distributed, test if AppCivist UI has checks to both (1) inform the user how many votes left to distribute he/she has, and (2) avoid to assign more votes when a user has distributed them all. And Fix the problem listed in previous comments.

@yolile
Copy link
Contributor

yolile commented Jul 27, 2020

This is still an issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants