You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@steve.apache.org by hu...@apache.org on 2015/03/26 12:12:52 UTC

svn commit: r1669319 - in /steve/trunk/pysteve: lib/plugins/__init__.py lib/plugins/ap.py www/htdocs/ballot_ap.html www/htdocs/js/steve_ap.js www/htdocs/js/steve_rest.js

Author: humbedooh
Date: Thu Mar 26 11:12:52 2015
New Revision: 1669319

URL: http://svn.apache.org/r1669319
Log:
Add Apache Style voting (yna with binding/non-binding votes)

Added:
    steve/trunk/pysteve/lib/plugins/ap.py
    steve/trunk/pysteve/www/htdocs/ballot_ap.html
    steve/trunk/pysteve/www/htdocs/js/steve_ap.js
Modified:
    steve/trunk/pysteve/lib/plugins/__init__.py
    steve/trunk/pysteve/www/htdocs/js/steve_rest.js

Modified: steve/trunk/pysteve/lib/plugins/__init__.py
URL: http://svn.apache.org/viewvc/steve/trunk/pysteve/lib/plugins/__init__.py?rev=1669319&r1=1669318&r2=1669319&view=diff
==============================================================================
--- steve/trunk/pysteve/lib/plugins/__init__.py (original)
+++ steve/trunk/pysteve/lib/plugins/__init__.py Thu Mar 26 11:12:52 2015
@@ -24,6 +24,7 @@ CORE VOTE PLUGINS:
     mntv:   Multiple Non-Transferable Votes
     cop:    Candidate or Party Voting
     fic:    First in Class Voting
+    ap:     Apache PMC Style voting
 """
 
-__all__ = ['yna','stv','dh','fpp','mntv','cop','fic']
\ No newline at end of file
+__all__ = ['yna','stv','dh','fpp','mntv','cop','fic','ap']
\ No newline at end of file

Added: steve/trunk/pysteve/lib/plugins/ap.py
URL: http://svn.apache.org/viewvc/steve/trunk/pysteve/lib/plugins/ap.py?rev=1669319&view=auto
==============================================================================
--- steve/trunk/pysteve/lib/plugins/ap.py (added)
+++ steve/trunk/pysteve/lib/plugins/ap.py Thu Mar 26 11:12:52 2015
@@ -0,0 +1,76 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+""" ASF PMC style voting plugin """
+from lib import constants, voter
+
+def tallyAP(votes, issue):
+    """ Simple YNA tallying
+    :param votes: The JSON object from $issueid.json.votes
+    :return: y,n,a as numbers
+    """
+    y = n = a = 0
+    by = bn = 0
+    for vote in votes.values():
+        if vote == 'y':
+            y += 1
+        if vote == 'n':
+            n += 1
+        if vote == 'a':
+            a += 1
+        if vote == 'by':
+            by += 1
+        if vote == 'bn':
+            bn += 1
+
+    return {
+        'votes': len(votes),
+        'yes': y,
+        'no': n,
+        'abstain': a,
+        'binding_yes': by,
+        'binding_no': bn
+    }
+
+
+def validateAP(vote, issue):
+    "Tries to invalidate a vote, returns why if succeeded, None otherwise"
+    letters = ['y','n','a', 'by', 'bn']
+    if len(vote) >= 3 or not vote in letters:
+        return "Invalid vote. Accepted votes are: %s" % ", ".join(letters)
+    return None
+
+
+# Verification process
+def verifyAP(basedata, issueID, voterID, vote):
+    "Invalidate a binding vote if not allowed to cast such"
+    email = voter.get(basedata['id'], basedata, voterID)
+    if vote.startswith('b'):
+        # Simple check example: if not apache committer, discard vote if binding
+        if not email.endswith("@apache.org"):
+            raise Exception("You are not allowed to cast a binding vote!")
+
+
+constants.VOTE_TYPES += (
+    {
+        'key': "ap",
+        'description': "ASF PMC Style vote (YNA with binding votes)",
+        'category': 'ap',
+        'validate_func': validateAP,
+        'vote_func': verifyAP,
+        'tally_func': tallyAP
+    },
+)
\ No newline at end of file

Added: steve/trunk/pysteve/www/htdocs/ballot_ap.html
URL: http://svn.apache.org/viewvc/steve/trunk/pysteve/www/htdocs/ballot_ap.html?rev=1669319&view=auto
==============================================================================
--- steve/trunk/pysteve/www/htdocs/ballot_ap.html (added)
+++ steve/trunk/pysteve/www/htdocs/ballot_ap.html Thu Mar 26 11:12:52 2015
@@ -0,0 +1,41 @@
+ <!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="css/steve_interactive.css">
+<link rel="stylesheet" href="css/jquery-ui.css">
+<script src="js/steve_rest.js" type="text/javascript"></script>
+<script src="js/steve_ap.js" type="text/javascript"></script>
+<script src="js/jquery.js" type="text/javascript"></script>
+<script src="js/jquery-ui.js" type="text/javascript"></script>
+<title>Apache STeVe: ASF PMC style vote</title>
+
+</head>
+<body onload="window.setTimeout(loadIssue, 500, null, null, null, displayIssueAP);">
+    <div id="popups"></div>
+    <p style="text-align: center;">
+        <img src="/images/steve_logo.png"/>
+    </p>
+    <a href="javascript:void(location.href='election.html' + document.location.search);">Back to election front page</a>
+<div class="formbox">
+    <p>
+        This is a standard ASF PMC vote with binding and non-binding votes.
+        If you agree with the motion put forward, vote <kbd>Yes</kbd>. If you disagree, vote <kbd>No</kbd>.
+        If you neither agree nor disagree, you can mark your ballot as blank by clicking on <kbd>Abstain</kbd>. F
+        or some elections where the number of votes
+        are important, abstaining on a vote you have no preference for/against can help reach quorum.
+    </p>
+<div id="preloaderWrapper">
+    <img src="/images/steve_spinner.gif"/>
+    <div id="preloader">
+        Loading issue, please wait...
+    </div>
+</div>
+</div>
+<p style="font-size: 12px; font-style: italic; text-align: center;">
+    Powered by <a href="https://steve.apache.org/">Apache STeVe</a>.
+    Copyright 2015, the Apache Software Foundation.
+    Licensed under the <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
+</p>
+</body>
+</html>
\ No newline at end of file

Added: steve/trunk/pysteve/www/htdocs/js/steve_ap.js
URL: http://svn.apache.org/viewvc/steve/trunk/pysteve/www/htdocs/js/steve_ap.js?rev=1669319&view=auto
==============================================================================
--- steve/trunk/pysteve/www/htdocs/js/steve_ap.js (added)
+++ steve/trunk/pysteve/www/htdocs/js/steve_ap.js Thu Mar 26 11:12:52 2015
@@ -0,0 +1,102 @@
+function displayIssueAP(code, response, state) {
+    election_data = response
+	var obj = document.getElementById('preloaderWrapper')
+	obj.setAttribute("id", "ynavote")
+    if (code != 200) {
+        obj.innerHTML = "<h1>Could not load issue:</h1><h2>" + response.message + "</h2>";
+    } else {
+		obj.innerHTML = ""
+		
+		var title = document.createElement('h2')
+		title.innerHTML = response.issue.title;
+		obj.appendChild(title)
+		
+		obj.appendChild(keyvaluepairText("nominatedby", "Put forward (nominated) by:", response.issue.nominatedby))
+		obj.appendChild(keyvaluepairText("seconds", "Seconded by:", response.issue.seconds.length > 0 ?  response.issue.seconds.join(", ") : "no-one" ))
+		
+		var desc = document.createElement('pre')
+		desc.setAttribute("class", "statement")
+		desc.innerHTML = response.issue.description
+		obj.appendChild(desc)
+		
+		var outer = document.createElement('div')
+		outer.setAttribute("class", "issueListItemWide")
+		
+		var byes = document.createElement('input')
+		byes.setAttribute("type", "button")
+		byes.setAttribute("value", "Binding Yes (+1)")
+		byes.setAttribute("class", "btn-green")
+		byes.setAttribute("style", "float: right;");
+		byes.setAttribute("onclick", "castSingleVote('by');")
+		
+		var yes = document.createElement('input')
+		yes.setAttribute("type", "button")
+		yes.setAttribute("value", "Yes (+1)")
+		yes.setAttribute("class", "btn-green")
+		yes.setAttribute("style", "float: right;");
+		yes.setAttribute("onclick", "castSingleVote('y');")
+		
+		var no = document.createElement('input')
+		no.setAttribute("type", "button")
+		no.setAttribute("value", "No  (-1)")
+		no.setAttribute("class", "btn-red")
+		no.setAttribute("style", " float: right;");
+		no.setAttribute("onclick", "castSingleVote('n');")
+		
+		var bno = document.createElement('input')
+		bno.setAttribute("type", "button")
+		bno.setAttribute("value", "Binding No (-1)")
+		bno.setAttribute("class", "btn-red")
+		bno.setAttribute("style", " float: right;");
+		bno.setAttribute("onclick", "castSingleVote('bn');")
+		
+		var abstain = document.createElement('input')
+		abstain.setAttribute("type", "button")
+		abstain.setAttribute("value", "Abstain (0)")
+		abstain.setAttribute("class", "btn-yellow")
+		abstain.setAttribute("style", "float: right;");
+		abstain.setAttribute("onclick", "castSingleVote('a');")
+		
+		var p = document.createElement('p')
+		p.innerHTML = "Cast your vote by clicking on the respective button below. You may recast your vote as many time as you like, should you reconsider."
+		
+		obj.appendChild(p)
+		outer.appendChild(bno)
+		outer.appendChild(no)
+		outer.appendChild(abstain)
+		outer.appendChild(yes)
+		outer.appendChild(byes)
+		
+		obj.appendChild(outer)
+	}
+}
+
+function loadIssue(election, issue, uid, callback) {
+	
+	var messages = ["Herding cats...", "Shaving yaks...", "Shooing some cows away...", "Fetching election data...", "Loading issues..."]
+	if (!election || !uid) {
+		var l = document.location.search.substr(1).split("/");
+		election = l[0];
+		issue = l.length > 1 ? l[l.length-2] : "";
+		uid = l.length > 2 ? l[l.length-1] : "";
+	}
+	if (step == -1) {
+		getJSON("/steve/voter/view/" + election + "/" + issue + "?uid=" + uid, [election, issue, uid], callback)
+	}
+	
+	var obj = document.getElementById('preloader');
+	step++;
+	if (!election_data && obj) {
+		if (step % 2 == 1) obj.innerHTML = messages[parseInt(Math.random()*messages.length-0.01)]
+	} else if (obj && (step % 2 == 1)) {
+		obj.innerHTML = "Ready..!"
+	}
+	if (step % 2 == 1) {
+		obj.style.transform = "translate(0,0)"
+	} else if (obj) {
+		obj.style.transform = "translate(0,-500%)"
+	}
+	if (!election_data|| (step % 2 == 0) ) {
+		window.setTimeout(loadElection, 750, election, uid, callback);
+	}
+}

Modified: steve/trunk/pysteve/www/htdocs/js/steve_rest.js
URL: http://svn.apache.org/viewvc/steve/trunk/pysteve/www/htdocs/js/steve_rest.js?rev=1669319&r1=1669318&r2=1669319&view=diff
==============================================================================
--- steve/trunk/pysteve/www/htdocs/js/steve_rest.js (original)
+++ steve/trunk/pysteve/www/htdocs/js/steve_rest.js Thu Mar 26 11:12:52 2015
@@ -153,11 +153,15 @@ function displayTally(code, response, is
 				obj.innerHTML += "<li>" + winner + ": " + winnerName + pct + "</li>"
 			}
 			obj.innerHTML += "</ol>"
-		} else if (response.yes && response.yes != undefined) {
+		} else if (response.yes != undefined) {
 			obj.innerHTML = "<i>(" + response.votes + " votes cast)</i>\n\n"
-			obj.innerHTML += "<b>Yes:     </b>" + response.yes + "\n"
-			obj.innerHTML += "<b>No:      </b>" + response.no + "\n"
-			obj.innerHTML += "<b>Abstain: </b>" + response.abstain + "\n"
+			obj.innerHTML += "<b>Yes:             </b>" + response.yes + "\n"
+			obj.innerHTML += "<b>No:              </b>" + response.no + "\n"
+			obj.innerHTML += "<b>Abstain:         </b>" + response.abstain + "\n"
+			if (response.binding_yes != undefined) {
+				obj.innerHTML += "<b>Binding Yes:     </b>" + response.binding_yes + "\n"
+				obj.innerHTML += "<b>Binding No:      </b>" + response.binding_no + "\n"
+			}
 		} else {
 			obj.innerHTML = "Unknonwn vote type or no votes cast yet"
 		}
@@ -391,6 +395,26 @@ function renderEditIssue(code, response,
 			btn.setAttribute("onclick", "saveYNA();")
 			div.appendChild(btn)
 			obj.appendChild(div)
+			
+		} else if (edit_i.type == "ap") {
+			obj.innerHTML = "<h3>Editing a Apache PMC Style issue</h3>"
+			
+			obj.appendChild(keyvaluepair("id", "Issue ID:", "text", edit_i.id, true))
+			obj.appendChild(keyvaluepair("ititle", "Issue title:", "text", edit_i.title))
+			obj.appendChild(keyvaluepair("nominatedby", "Nominated by:", "text", edit_i.nominatedby))
+			obj.appendChild(keyvaluepair("seconds", "Seconded by:", "text", (edit_i.seconds ? edit_i.seconds : []).join(", ")))
+			obj.appendChild(document.createElement('hr'))
+			obj.appendChild(keyvaluepair("description", "Description/statement:", "textarea", edit_i.description))
+			
+			var div = document.createElement('div')
+			div.setAttribute("class", "keyvaluepair")
+			var btn = document.createElement('input')
+			btn.setAttribute("type", "button")
+			btn.setAttribute("class", "btn-green")
+			btn.setAttribute("value", "Save changes")
+			btn.setAttribute("onclick", "saveYNA();")
+			div.appendChild(btn)
+			obj.appendChild(div)
 		} else if (edit_i.type.match(/^stv/)) {
 			
 			// base data
@@ -660,7 +684,7 @@ function peekCallback(code, response, el
 }
 
 function changeSTVType(type) {
-	if (type == "yna") {
+	if (type == "yna" || type == "ap") {
 		document.getElementById('yna').style.display = "block";
 		document.getElementById('stv').style.display = "none";
 	} else {



Re: svn commit: r1669319 - in /steve/trunk/pysteve: lib/plugins/__init__.py lib/plugins/ap.py www/htdocs/ballot_ap.html www/htdocs/js/steve_ap.js www/htdocs/js/steve_rest.js

Posted by Greg Stein <gs...@gmail.com>.
On Thu, Mar 26, 2015 at 6:12 AM, <hu...@apache.org> wrote:
>...

> +++ steve/trunk/pysteve/lib/plugins/ap.py Thu Mar 26 11:12:52 2015
>
>...

> +    y = n = a = 0
> +    by = bn = 0
> +    for vote in votes.values():
> +        if vote == 'y':
> +            y += 1
> +        if vote == 'n':
> +            n += 1
> +        if vote == 'a':
> +            a += 1
> +        if vote == 'by':
> +            by += 1
> +        if vote == 'bn':
> +            bn += 1
>

"elif", please ... no need to run all tests for every vote. Yah yah, speed
isn't an issue, but it also improves clarity, that these are disjoint.
Could also throw in an 'else' and raise an exception if a vote doesn't
match one of the legal values.

>...

> +
> +def validateAP(vote, issue):
> +    "Tries to invalidate a vote, returns why if succeeded, None otherwise"
>

The name of the function is "validateAP" and the docstring says it tries to
*invalidate* ?? These should match.

>...

> +constants.VOTE_TYPES += (
>

Modifying a global in *another module* is all kinds of ugly. Totally breaks
any kind of encapsulation of concerns. And what happens if this gets run
twice? What if not all are imported? Does something force them all to be
loaded? etc etc.

This can also introduce big problems with references to
constants.VOTE_TYPES that may exist at different times. Not only are you
creating a system with a seeming global constant that is NOT, but it also
takes on multiple values.

If I do:

  k1 = constants.VOTE_TYPES
  import ap
  k2 = constants.VOTE_TYPES

k1 and k2 will be *different*. You're rebinding the VOTE_TYPES name, rather
than modifying it in place.

So not only breaking encapsulation, but also creating a potential hazard of
modules thinking they have "the latest" VOTE_TYPES, but merely have a
snapshot of how it existed at a single point in time.

>...

Cheers,
-g