1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-26 08:42:41 +01:00

Port the "Slowvote" application

Summary: Port slowvote. This has some style/layout roughness but gets us most of
the way there. I'll followup to fix some of the markup issues.
Test Plan: Created and voted in several different kinds of poll.
Reviewed By: codeblock
Reviewers: codeblock, tomo, jungejason, aran, tuomaspelkonen
Commenters: aran, jungejason
CC: aran, codeblock, jungejason, epriestley
Differential Revision: 613
This commit is contained in:
epriestley 2011-07-08 11:13:11 -07:00
parent cce786a89f
commit 57097c2874
25 changed files with 1248 additions and 11 deletions

View file

@ -0,0 +1,50 @@
CREATE DATABASE phabricator_slowvote;
CREATE TABLE phabricator_slowvote.slowvote_poll (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
question VARCHAR(255) NOT NULL,
phid VARCHAR(64) BINARY NOT NULL,
UNIQUE KEY (phid),
authorPHID VARCHAR(64) BINARY NOT NULL,
responseVisibility INT UNSIGNED NOT NULL,
shuffle INT UNSIGNED NOT NULL,
method INT UNSIGNED NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
);
CREATE TABLE phabricator_slowvote.slowvote_option (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
pollID INT UNSIGNED NOT NULL,
KEY (pollID),
name VARCHAR(255) NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
);
CREATE TABLE phabricator_slowvote.slowvote_comment (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
pollID INT UNSIGNED NOT NULL,
UNIQUE KEY (pollID, authorPHID),
authorPHID VARCHAR(64) BINARY NOT NULL,
commentText LONGBLOB NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
);
CREATE TABLE phabricator_slowvote.slowvote_choice (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
pollID INT UNSIGNED NOT NULL,
KEY (pollID),
optionID INT UNSIGNED NOT NULL,
authorPHID VARCHAR(64) BINARY NOT NULL,
KEY (authorPHID),
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL
);
INSERT INTO phabricator_directory.directory_item
(name, description, href, categoryID, sequence, dateCreated, dateModified)
VALUES
("Slowvote", "Design by committee.", "/vote/", 5, 250,
UNIX_TIMESTAMP(), UNIX_TIMESTAMP());

View file

@ -283,17 +283,6 @@ celerity_register_resource_map(array(
), ),
'disk' => '/rsrc/css/application/herald/herald-test.css', 'disk' => '/rsrc/css/application/herald/herald-test.css',
), ),
0 =>
array(
'uri' => '/res/1da00bfe/rsrc/js/javelin/lib/__tests__/URI.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-uri',
1 => 'javelin-php-serializer',
),
'disk' => '/rsrc/js/javelin/lib/__tests__/URI.js',
),
'javelin-behavior' => 'javelin-behavior' =>
array( array(
'uri' => '/res/b28adfa1/rsrc/js/javelin/lib/behavior.js', 'uri' => '/res/b28adfa1/rsrc/js/javelin/lib/behavior.js',
@ -487,6 +476,17 @@ celerity_register_resource_map(array(
), ),
'disk' => '/rsrc/js/application/differential/behavior-show-all-comments.js', 'disk' => '/rsrc/js/application/differential/behavior-show-all-comments.js',
), ),
0 =>
array(
'uri' => '/res/1da00bfe/rsrc/js/javelin/lib/__tests__/URI.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-uri',
1 => 'javelin-php-serializer',
),
'disk' => '/rsrc/js/javelin/lib/__tests__/URI.js',
),
'javelin-behavior-differential-show-more' => 'javelin-behavior-differential-show-more' =>
array( array(
'uri' => '/res/a766c717/rsrc/js/application/differential/behavior-show-more.js', 'uri' => '/res/a766c717/rsrc/js/application/differential/behavior-show-more.js',
@ -1099,6 +1099,15 @@ celerity_register_resource_map(array(
), ),
'disk' => '/rsrc/js/application/core/ShapedRequest.js', 'disk' => '/rsrc/js/application/core/ShapedRequest.js',
), ),
'phabricator-slowvote-css' =>
array(
'uri' => '/res/2caefc66/rsrc/css/application/slowvote/slowvote.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/slowvote/slowvote.css',
),
'phabricator-standard-page-view' => 'phabricator-standard-page-view' =>
array( array(
'uri' => '/res/e8238633/rsrc/css/application/base/standard-page-view.css', 'uri' => '/res/e8238633/rsrc/css/application/base/standard-page-view.css',

View file

@ -521,6 +521,15 @@ phutil_register_library_map(array(
'PhabricatorSearchSelectController' => 'applications/search/controller/select', 'PhabricatorSearchSelectController' => 'applications/search/controller/select',
'PhabricatorSearchUserIndexer' => 'applications/search/index/indexer/user', 'PhabricatorSearchUserIndexer' => 'applications/search/index/indexer/user',
'PhabricatorSetup' => 'infrastructure/setup', 'PhabricatorSetup' => 'infrastructure/setup',
'PhabricatorSlowvoteChoice' => 'applications/slowvote/storage/choice',
'PhabricatorSlowvoteComment' => 'applications/slowvote/storage/comment',
'PhabricatorSlowvoteController' => 'applications/slowvote/controller/base',
'PhabricatorSlowvoteCreateController' => 'applications/slowvote/controller/create',
'PhabricatorSlowvoteDAO' => 'applications/slowvote/storage/base',
'PhabricatorSlowvoteListController' => 'applications/slowvote/controller/list',
'PhabricatorSlowvoteOption' => 'applications/slowvote/storage/option',
'PhabricatorSlowvotePoll' => 'applications/slowvote/storage/poll',
'PhabricatorSlowvotePollController' => 'applications/slowvote/controller/poll',
'PhabricatorStandardPageView' => 'view/page/standard', 'PhabricatorStandardPageView' => 'view/page/standard',
'PhabricatorStatusController' => 'applications/status/base', 'PhabricatorStatusController' => 'applications/status/base',
'PhabricatorSyntaxHighlighter' => 'applications/markup/syntax', 'PhabricatorSyntaxHighlighter' => 'applications/markup/syntax',
@ -1002,6 +1011,15 @@ phutil_register_library_map(array(
'PhabricatorSearchResultView' => 'AphrontView', 'PhabricatorSearchResultView' => 'AphrontView',
'PhabricatorSearchSelectController' => 'PhabricatorSearchController', 'PhabricatorSearchSelectController' => 'PhabricatorSearchController',
'PhabricatorSearchUserIndexer' => 'PhabricatorSearchDocumentIndexer', 'PhabricatorSearchUserIndexer' => 'PhabricatorSearchDocumentIndexer',
'PhabricatorSlowvoteChoice' => 'PhabricatorSlowvoteDAO',
'PhabricatorSlowvoteComment' => 'PhabricatorSlowvoteDAO',
'PhabricatorSlowvoteController' => 'PhabricatorController',
'PhabricatorSlowvoteCreateController' => 'PhabricatorSlowvoteController',
'PhabricatorSlowvoteDAO' => 'PhabricatorLiskDAO',
'PhabricatorSlowvoteListController' => 'PhabricatorSlowvoteController',
'PhabricatorSlowvoteOption' => 'PhabricatorSlowvoteDAO',
'PhabricatorSlowvotePoll' => 'PhabricatorSlowvoteDAO',
'PhabricatorSlowvotePollController' => 'PhabricatorSlowvoteController',
'PhabricatorStandardPageView' => 'AphrontPageView', 'PhabricatorStandardPageView' => 'AphrontPageView',
'PhabricatorStatusController' => 'PhabricatorController', 'PhabricatorStatusController' => 'PhabricatorController',
'PhabricatorTaskmasterDaemon' => 'PhabricatorDaemon', 'PhabricatorTaskmasterDaemon' => 'PhabricatorDaemon',

View file

@ -334,6 +334,12 @@ class AphrontDefaultApplicationConfiguration
'/feed/' => array( '/feed/' => array(
'$' => 'PhabricatorFeedStreamController', '$' => 'PhabricatorFeedStreamController',
), ),
'/V(?P<id>\d+)$' => 'PhabricatorSlowvotePollController',
'/vote/' => array(
'(?:view/(?P<view>\w+)/)?$' => 'PhabricatorSlowvoteListController',
'create/' => 'PhabricatorSlowvoteCreateController',
),
); );
} }

View file

@ -31,6 +31,7 @@ final class PhabricatorPHIDConstants {
const PHID_TYPE_OPKG = 'OPKG'; const PHID_TYPE_OPKG = 'OPKG';
const PHID_TYPE_PSTE = 'PSTE'; const PHID_TYPE_PSTE = 'PSTE';
const PHID_TYPE_STRY = 'STRY'; const PHID_TYPE_STRY = 'STRY';
const PHID_TYPE_POLL = 'POLL';
public static function getTypes() { public static function getTypes() {
return array( return array(
@ -47,6 +48,7 @@ final class PhabricatorPHIDConstants {
self::PHID_TYPE_PSTE, self::PHID_TYPE_PSTE,
self::PHID_TYPE_OPKG, self::PHID_TYPE_OPKG,
self::PHID_TYPE_STRY, self::PHID_TYPE_STRY,
self::PHID_TYPE_POLL,
); );
} }
} }

View file

@ -0,0 +1,45 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed 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.
*/
abstract class PhabricatorSlowvoteController extends PhabricatorController {
public function buildStandardPageResponse($view, array $data) {
$page = $this->buildStandardPageView();
$page->setApplicationName('Slowvote');
$page->setBaseURI('/vote/');
$page->setTitle(idx($data, 'title'));
$page->setGlyph("\xE2\x9C\x94");
$doc_href = PhabricatorEnv::getDoclink('articles/Slowvote_User_Guide.html');
$page->setTabs(
array(
'help' => array(
'name' => 'Help',
'href' => $doc_href,
),
),
null);
$page->appendChild($view);
$response = new AphrontWebpageResponse();
return $response->setContent($page->render());
}
}

View file

@ -0,0 +1,16 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/response/webpage');
phutil_require_module('phabricator', 'applications/base/controller/base');
phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorSlowvoteController.php');

View file

@ -0,0 +1,160 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed 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.
*/
class PhabricatorSlowvoteCreateController
extends PhabricatorSlowvoteController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$poll = new PhabricatorSlowvotePoll();
$poll->setAuthorPHID($user->getPHID());
$e_question = true;
$e_response = true;
$errors = array();
$responses = $request->getArr('response');
if ($request->isFormPost()) {
$poll->setQuestion($request->getStr('question'));
$poll->setResponseVisibility($request->getInt('response_visibility'));
$poll->setShuffle($request->getBool('shuffle', false));
$poll->setMethod($request->getInt('method'));
if (!strlen($poll->getQuestion())) {
$e_question = 'Required';
$errors[] = 'You must ask a poll question.';
} else {
$e_question = null;
}
$responses = array_filter($responses);
if (empty($responses)) {
$errors[] = 'You must offer at least one response.';
$e_response = 'Required';
} else {
$e_response = null;
}
if (empty($errors)) {
$poll->save();
foreach ($responses as $response) {
$option = new PhabricatorSlowvoteOption();
$option->setName($response);
$option->setPollID($poll->getID());
$option->save();
}
return id(new AphrontRedirectResponse())
->setURI('/V'.$poll->getID());
}
}
$error_view = null;
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle('Form Errors');
$error_view->setErrors($errors);
}
$form = id(new AphrontFormView())
->setUser($user)
->appendChild(
'<p class="aphront-form-instructions">Resolve issues and build '.
'consensus through protracted deliberation.</p>')
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Question')
->setName('question')
->setValue($poll->getQuestion())
->setError($e_question));
for ($ii = 0; $ii < 10; $ii++) {
$n = ($ii + 1);
$response = id(new AphrontFormTextControl())
->setLabel("Response {$n}")
->setName('response[]')
->setValue(idx($responses, $ii, ''));
if ($ii == 0) {
$response->setError($e_response);
}
$form->appendChild($response);
}
$poll_type_options = array(
PhabricatorSlowvotePoll::METHOD_PLURALITY => 'Plurality (Single Choice)',
PhabricatorSlowvotePoll::METHOD_APPROVAL => 'Approval (Multiple Choice)',
);
$response_type_options = array(
PhabricatorSlowvotePoll::RESPONSES_VISIBLE
=> 'Allow anyone to see the responses',
PhabricatorSlowvotePoll::RESPONSES_VOTERS
=> 'Require a vote to see the responses',
PhabricatorSlowvotePoll::RESPONSES_OWNER
=> 'Only I can see the responses',
);
$form
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Vote Type')
->setName('method')
->setValue($poll->getMethod())
->setOptions($poll_type_options))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Responses')
->setName('response_visibility')
->setValue($poll->getResponseVisibility())
->setOptions($response_type_options))
->appendChild(
id(new AphrontFormCheckboxControl())
->setLabel('Shuffle')
->addCheckbox(
'shuffle',
1,
'Show choices in random order',
$poll->getShuffle()))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Create Slowvote')
->addCancelButton('/vote/'));
$panel = new AphrontPanelView();
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
$panel->setHeader('Create Slowvote');
$panel->appendChild($form);
return $this->buildStandardPageResponse(
array(
$error_view,
$panel,
),
array(
'title' => 'Create Slowvote',
));
}
}

View file

@ -0,0 +1,24 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/response/redirect');
phutil_require_module('phabricator', 'applications/slowvote/controller/base');
phutil_require_module('phabricator', 'applications/slowvote/storage/option');
phutil_require_module('phabricator', 'applications/slowvote/storage/poll');
phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/checkbox');
phutil_require_module('phabricator', 'view/form/control/select');
phutil_require_module('phabricator', 'view/form/control/submit');
phutil_require_module('phabricator', 'view/form/control/text');
phutil_require_module('phabricator', 'view/form/error');
phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorSlowvoteCreateController.php');

View file

@ -0,0 +1,167 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed 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.
*/
class PhabricatorSlowvoteListController
extends PhabricatorSlowvoteController {
private $view;
const VIEW_ALL = 'all';
const VIEW_CREATED = 'created';
const VIEW_VOTED = 'voted';
public function willProcessRequest(array $data) {
$this->view = idx($data, 'view');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$filters = array(
self::VIEW_ALL => 'All Slowvotes',
self::VIEW_CREATED => 'Created',
self::VIEW_VOTED => 'Voted In',
);
$view = isset($filters[$this->view])
? $this->view
: self::VIEW_ALL;
$side_nav = new AphrontSideNavView();
foreach ($filters as $key => $name) {
$side_nav->addNavItem(
phutil_render_tag(
'a',
array(
'href' => '/vote/view/'.$key.'/',
'class' => ($view == $key) ? 'aphront-side-nav-selected' : null,
),
phutil_escape_html($name)));
}
$poll = new PhabricatorSlowvotePoll();
$conn = $poll->establishConnection('r');
$pager = new AphrontPagerView();
$pager->setOffset($request->getInt('page'));
$pager->setURI($request->getRequestURI(), 'page');
$offset = $pager->getOffset();
$limit = $pager->getPageSize() + 1;
switch ($view) {
case self::VIEW_ALL:
$data = queryfx_all(
$conn,
'SELECT * FROM %T ORDER BY id DESC LIMIT %d, %d',
$poll->getTableName(),
$offset,
$limit);
break;
case self::VIEW_CREATED:
$data = queryfx_all(
$conn,
'SELECT * FROM %T WHERE authorPHID = %s ORDER BY id DESC
LIMIT %d, %d',
$poll->getTableName(),
$user->getPHID(),
$offset,
$limit);
break;
case self::VIEW_VOTED:
$choice = new PhabricatorSlowvoteChoice();
$data = queryfx_all(
$conn,
'SELECT p.* FROM %T p JOIN %T o
ON o.pollID = p.id
WHERE o.authorPHID = %s
GROUP BY p.id
ORDER BY p.id DESC
LIMIT %d, %d',
$poll->getTableName(),
$choice->getTableName(),
$user->getPHID(),
$offset,
$limit);
break;
}
$data = $pager->sliceResults($data);
$polls = $poll->loadAllFromArray($data);
$phids = mpull($polls, 'getAuthorPHID');
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
$rows = array();
foreach ($polls as $poll) {
$rows[] = array(
$handles[$poll->getAuthorPHID()]->renderLink(),
phutil_render_tag(
'a',
array(
'href' => '/V'.$poll->getID(),
),
phutil_escape_html('V'.$poll->getID().' '.$poll->getQuestion())),
phabricator_date($poll->getDateCreated(), $user),
phabricator_time($poll->getDateCreated(), $user),
);
}
$table = new AphrontTableView($rows);
$table->setColumnClasses(
array(
'',
'pri wide',
'',
'right',
));
$table->setHeaders(
array(
'Author',
'Poll',
'Date',
'Time',
));
$headers = array(
self::VIEW_ALL
=> 'Slowvotes Not Yet Consumed by the Ravages of Time',
self::VIEW_CREATED
=> 'Slowvotes Birthed from Your Noblest of Great Minds',
self::VIEW_VOTED
=> 'Slowvotes Within Which You Express Your Mighty Opinion',
);
$panel = new AphrontPanelView();
$panel->setHeader(idx($headers, $view));
$panel->setCreateButton('Create Slowvote', '/vote/create/');
$panel->appendChild($table);
$panel->appendChild($pager);
$side_nav->appendChild($panel);
return $this->buildStandardPageResponse(
$side_nav,
array(
'title' => 'Slowvotes',
));
}
}

View file

@ -0,0 +1,24 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/phid/handle/data');
phutil_require_module('phabricator', 'applications/slowvote/controller/base');
phutil_require_module('phabricator', 'applications/slowvote/storage/choice');
phutil_require_module('phabricator', 'applications/slowvote/storage/poll');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_module('phabricator', 'view/control/pager');
phutil_require_module('phabricator', 'view/control/table');
phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phabricator', 'view/layout/sidenav');
phutil_require_module('phabricator', 'view/utils');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorSlowvoteListController.php');

View file

@ -0,0 +1,390 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed 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.
*/
class PhabricatorSlowvotePollController
extends PhabricatorSlowvoteController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$viewer_phid = $user->getPHID();
$poll = id(new PhabricatorSlowvotePoll())->load($this->id);
if (!$poll) {
return new Aphront404Response();
}
$options = id(new PhabricatorSlowvoteOption())->loadAllWhere(
'pollID = %d',
$poll->getID());
$choices = id(new PhabricatorSlowvoteChoice())->loadAllWhere(
'pollID = %d',
$poll->getID());
$comments = id(new PhabricatorSlowvoteComment())->loadAllWhere(
'pollID = %d',
$poll->getID());
switch ($poll->getMethod()) {
case PhabricatorSlowvotePoll::METHOD_PLURALITY:
$out_of_total = count($choices);
break;
case PhabricatorSlowvotePoll::METHOD_APPROVAL:
// Count unique respondents for approval votes.
$out_of_total = count(mpull($choices, null, 'getAuthorPHID'));
break;
default:
throw new Exception("Unknown poll method!");
}
$choices_by_option = mgroup($choices, 'getOptionID');
$comments_by_user = mpull($comments, null, 'getAuthorPHID');
$choices_by_user = mgroup($choices, 'getAuthorPHID');
$viewer_choices = idx($choices_by_user, $viewer_phid, array());
$viewer_comment = idx($comments_by_user, $viewer_phid, null);
$comment_text = null;
if ($viewer_comment) {
$comment_text = $viewer_comment->getCommentText();
}
if ($request->isFormPost()) {
$comment = idx($comments_by_user, $viewer_phid, null);
if ($comment) {
$comment->delete();
}
$comment_text = $request->getStr('comments');
if (strlen($comment_text)) {
id(new PhabricatorSlowvoteComment())
->setAuthorPHID($viewer_phid)
->setPollID($poll->getID())
->setCommentText($comment_text)
->save();
}
$votes = $request->getArr('vote');
switch ($poll->getMethod()) {
case PhabricatorSlowvotePoll::METHOD_PLURALITY:
// Enforce only one vote.
$votes = array_slice($votes, 0, 1);
break;
case PhabricatorSlowvotePoll::METHOD_APPROVAL:
// No filtering.
break;
default:
throw new Exception("Unknown poll method!");
}
foreach ($viewer_choices as $viewer_choice) {
$viewer_choice->delete();
}
foreach ($votes as $vote) {
id(new PhabricatorSlowvoteChoice())
->setAuthorPHID($viewer_phid)
->setPollID($poll->getID())
->setOptionID($vote)
->save();
}
return id(new AphrontRedirectResponse())->setURI('/V'.$poll->getID());
}
require_celerity_resource('phabricator-slowvote-css');
$phids = array_merge(
mpull($choices, 'getAuthorPHID'),
mpull($comments, 'getAuthorPHID'),
array(
$poll->getAuthorPHID(),
));
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
if ($poll->getShuffle()) {
shuffle($options);
}
$option_markup = array();
foreach ($options as $option) {
$id = $option->getID();
switch ($poll->getMethod()) {
case PhabricatorSlowvotePoll::METHOD_PLURALITY:
// Render a radio button.
$selected_option = head($viewer_choices);
if ($selected_option) {
$selected = $selected_option->getOptionID();
} else {
$selected = null;
}
if ($selected == $id) {
$checked = "checked";
} else {
$checked = null;
}
$input = phutil_render_tag(
'input',
array(
'type' => 'radio',
'name' => 'vote[]',
'value' => $id,
'checked' => $checked,
));
break;
case PhabricatorSlowvotePoll::METHOD_APPROVAL:
// Render a check box.
$checked = null;
foreach ($viewer_choices as $choice) {
if ($choice->getOptionID() == $id) {
$checked = 'checked';
break;
}
}
$input = phutil_render_tag(
'input',
array(
'type' => 'checkbox',
'name' => 'vote[]',
'checked' => $checked,
'value' => $id,
));
break;
default:
throw new Exception("Unknown poll method!");
}
if ($checked) {
$checked_class = 'phabricator-slowvote-checked';
} else {
$checked_class = null;
}
$option_markup[] = phutil_render_tag(
'label',
array(
'class' => 'phabricator-slowvote-label '.$checked_class,
),
$input.phutil_escape_html($option->getName()));
}
$option_markup = implode("\n", $option_markup);
$comments_by_option = array();
switch ($poll->getMethod()) {
case PhabricatorSlowvotePoll::METHOD_PLURALITY:
$choice_ids = array();
foreach ($choices_by_user as $user_phid => $choices) {
$choice_ids[$user_phid] = head($choices)->getOptionID();
}
foreach ($comments as $comment) {
$choice = idx($choice_ids, $comment->getAuthorPHID());
if ($choice) {
$comments_by_option[$choice][] = $comment;
}
}
break;
case PhabricatorSlowvotePoll::METHOD_APPROVAL:
// All comments are grouped in approval voting.
break;
default:
throw new Exception("Unknown poll method!");
}
$can_see_responses = false;
$need_vote = false;
switch ($poll->getResponseVisibility()) {
case PhabricatorSlowvotePoll::RESPONSES_VISIBLE:
$can_see_responses = true;
break;
case PhabricatorSlowvotePoll::RESPONSES_VOTERS:
$can_see_responses = (bool)$viewer_choices;
$need_vote = true;
break;
case PhabricatorSlowvotePoll::RESPONSES_OWNER:
$can_see_responses = ($viewer_phid == $poll->getAuthorPHID());
break;
}
$result_markup = id(new AphrontFormLayoutView())
->appendChild('<h1>Ongoing Deliberation</h1>');
if (!$can_see_responses) {
if ($need_vote) {
$reason = "You must vote to see the results.";
} else {
$reason = "The results are not public.";
}
$result_markup
->appendChild(
'<p class="aphront-form-instructions"><em>'.$reason.'</em></p>');
} else {
foreach ($options as $option) {
$id = $option->getID();
$chosen = idx($choices_by_option, $id, array());
$users = array_select_keys($handles, mpull($chosen, 'getAuthorPHID'));
if ($users) {
$user_markup = array();
foreach ($users as $handle) {
$user_markup[] = $handle->renderLink();
}
$user_markup = implode('', $user_markup);
} else {
$user_markup = 'This option has failed to appeal to anyone.';
}
$comment_markup = $this->renderComments(
idx($comments_by_option, $id, array()),
$handles);
$display = sprintf(
'%d / %d (%d%%)',
number_format(count($chosen)),
number_format($out_of_total),
$out_of_total
? round(100 * count($chosen) / $out_of_total)
: 0);
$result_markup->appendChild(
'<div>'.
'<div class="phabricator-slowvote-count">'.
$display.
'</div>'.
'<h1>'.phutil_escape_html($option->getName()).'</h1>'.
'<hr class="phabricator-slowvote-hr" />'.
$user_markup.
'<hr class="phabricator-slowvote-hr" />'.
$comment_markup.
'</div>');
}
if ($poll->getMethod() == PhabricatorSlowvotePoll::METHOD_APPROVAL &&
$comments) {
$comment_markup = $this->renderComments(
$comments,
$handles);
$result_markup->appendChild(
'<h1>Motions Proposed for Consideration</h1>');
$result_markup->appendChild($comment_markup);
}
}
if ($viewer_choices) {
$instructions =
'Your vote has been recorded... but there is still ample time to '.
'rethink your position. Have you thoroughly considered all possible '.
'eventualities?';
} else {
$instructions =
'This is a weighty matter indeed. Consider your choices with the '.
'greatest of care.';
}
$form = id(new AphrontFormView())
->setUser($user)
->appendChild(
'<p class="aphront-form-instructions">'.$instructions.'</p>')
->appendChild(
id(new AphrontFormMarkupControl())
->setLabel('Vote')
->setValue($option_markup))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel('Comments')
->setHeight(AphrontFormTextAreaControl::HEIGHT_SHORT)
->setName('comments')
->setValue($comment_text))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Cautiously Engage in Deliberations'));
$panel = new AphrontPanelView();
$panel->setHeader(phutil_escape_html($poll->getQuestion()));
$panel->setWidth(AphrontPanelView::WIDTH_WIDE);
$panel->appendChild($form);
$panel->appendChild('<br /><br />');
$panel->appendChild($result_markup);
return $this->buildStandardPageResponse(
$panel,
array(
'title' => 'V'.$poll->getID().' '.$poll->getQuestion(),
));
}
private function renderComments(array $comments, array $handles) {
$viewer = $this->getRequest()->getUser();
$factory = new DifferentialMarkupEngineFactory();
$engine = $factory->newDifferentialCommentMarkupEngine();
$comment_markup = array();
foreach ($comments as $comment) {
$handle = $handles[$comment->getAuthorPHID()];
$markup = $engine->markupText($comment->getCommentText());
require_celerity_resource('phabricator-remarkup-css');
$comment_markup[] =
'<tr>'.
'<th>'.
$handle->renderLink().
'<div class="phabricator-slowvote-datestamp">'.
phabricator_datetime($comment->getDateCreated(), $viewer).
'</div>'.
'<td>'.
'<div class="phabricator-remarkup">'.
$markup.
'</div>'.
'</td>'.
'</tr>';
}
if ($comment_markup) {
$comment_markup = phutil_render_tag(
'table',
array(
'class' => 'phabricator-slowvote-comments',
),
implode("\n", $comment_markup));
} else {
$comment_markup = null;
}
return $comment_markup;
}
}

View file

@ -0,0 +1,31 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/response/404');
phutil_require_module('phabricator', 'aphront/response/redirect');
phutil_require_module('phabricator', 'applications/differential/parser/markup');
phutil_require_module('phabricator', 'applications/phid/handle/data');
phutil_require_module('phabricator', 'applications/slowvote/controller/base');
phutil_require_module('phabricator', 'applications/slowvote/storage/choice');
phutil_require_module('phabricator', 'applications/slowvote/storage/comment');
phutil_require_module('phabricator', 'applications/slowvote/storage/option');
phutil_require_module('phabricator', 'applications/slowvote/storage/poll');
phutil_require_module('phabricator', 'infrastructure/celerity/api');
phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/markup');
phutil_require_module('phabricator', 'view/form/control/submit');
phutil_require_module('phabricator', 'view/form/control/textarea');
phutil_require_module('phabricator', 'view/form/layout');
phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phabricator', 'view/utils');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorSlowvotePollController.php');

View file

@ -0,0 +1,25 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed 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.
*/
class PhabricatorSlowvoteDAO extends PhabricatorLiskDAO {
public function getApplicationName() {
return 'slowvote';
}
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/base/storage/lisk');
phutil_require_source('PhabricatorSlowvoteDAO.php');

View file

@ -0,0 +1,25 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed 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.
*/
class PhabricatorSlowvoteChoice extends PhabricatorSlowvoteDAO {
protected $pollID;
protected $optionID;
protected $authorPHID;
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/slowvote/storage/base');
phutil_require_source('PhabricatorSlowvoteChoice.php');

View file

@ -0,0 +1,25 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed 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.
*/
class PhabricatorSlowvoteComment extends PhabricatorSlowvoteDAO {
protected $pollID;
protected $authorPHID;
protected $commentText;
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/slowvote/storage/base');
phutil_require_source('PhabricatorSlowvoteComment.php');

View file

@ -0,0 +1,25 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed 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.
*/
class PhabricatorSlowvoteOption extends PhabricatorSlowvoteDAO {
protected $pollID;
protected $name;
}

View file

@ -0,0 +1,12 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/slowvote/storage/base');
phutil_require_source('PhabricatorSlowvoteOption.php');

View file

@ -0,0 +1,46 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed 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.
*/
class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO {
const RESPONSES_VISIBLE = 0;
const RESPONSES_VOTERS = 1;
const RESPONSES_OWNER = 2;
const METHOD_PLURALITY = 0;
const METHOD_APPROVAL = 1;
protected $question;
protected $phid;
protected $authorPHID;
protected $responseVisibility;
protected $shuffle;
protected $method;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_POLL);
}
}

View file

@ -0,0 +1,14 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/phid/constants');
phutil_require_module('phabricator', 'applications/phid/storage/phid');
phutil_require_module('phabricator', 'applications/slowvote/storage/base');
phutil_require_source('PhabricatorSlowvotePoll.php');

View file

@ -0,0 +1,27 @@
@title Slowvote User Guide
@group userguide
Detailed dispute resolution manual.
= Overview =
Slowvote is a simple polling application for asking questions like "Where should
we order pizza from?", "Are dogs better than cats?" and "What is the optimal
strategic direction for our company?". You can use it to quickly get feedback
from people in a lightweight, structured way.
Slowvote is based on a Facebook application called Quickvote, which was pretty
good when it was originally written but went through a couple of poorly executed
rewrites and ended up being extremely slow and glitchy, often taking more than
10 seconds to load. I originally designed and implemented the entire Slowvote
application while waiting for a Quikvote to load.
= Plurality vs Approval =
When you create a poll, you can choose **plurality** or **approval** voting.
In plurality voting, users choose one option. The winning option is the one with
the most votes.
In approval voting, users choose all the options they approve of. The winning
option is the one the largest number of people accept.

View file

@ -0,0 +1,60 @@
/**
* @provides phabricator-slowvote-css
*/
.phabricator-slowvote-comments {
width: 100%;
}
.phabricator-slowvote-comments th {
width: 150px;
text-align: right;
padding: 6px 4px 6px;
white-space: nowrap;
}
.phabricator-slowvote-comments td {
vertical-align: top;
padding: 6px 2px;
border-bottom: 1px solid #d0d0d0;
}
.phabricator-slowvote-datestamp {
font-size: 9px;
font-family: "Verdana";
color: #666666;
margin-top: 1px;
}
.phabricator-slowvote-hr {
border: none;
height: 1px;
position: relative;
background: #c0c0c0;
}
.phabricator-slowvote-count {
float: right;
font-size: 13px;
font-weight: bold;
}
.phabricator-slowvote-label {
display: block;
width: 100%;
font-size: 14px;
font-weight: bold;
color: #222222;
text-align: left;
margin: 0px 0px 6px;
padding: 6px 4px;
background: #cccccc;
border-bottom: 1px solid #aaaaaa;
cursor: pointer;
}
.aphront-form-input .phabricator-slowvote-label input {
display: inline;
width: auto;
margin-right: 12px;
}