r77302 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r77301‎ | r77302 | r77303 >
Date:21:39, 25 November 2010
Author:catrope
Status:ok
Tags:
Comment:
(bug 26014) Allow users to strike their own sign-offs. Implemented with a timestamp_struck field which contains either the timestamp the sign-off was struck or Block::infinity(). This allows us to use the UNIQUE INDEX (which was extended to cover that field too) to prevent duplicate sign-offs and at the same time allow the same sign-off (i.e. same user, revision and flag) to be struck and resubmitted multiple times. The UI mostly resembles the mockup Trevor attached to the bug, but it still needs a bit of CSS love. Documentation of the code I added will follow in a separate commit.
Modified paths:
  • /trunk/extensions/CodeReview/CodeReview.i18n.php (modified) (history)
  • /trunk/extensions/CodeReview/CodeReview.php (modified) (history)
  • /trunk/extensions/CodeReview/archives/code_signoffs_timestamp_struck.sql (added) (history)
  • /trunk/extensions/CodeReview/backend/CodeRevision.php (modified) (history)
  • /trunk/extensions/CodeReview/backend/CodeSignoff.php (modified) (history)
  • /trunk/extensions/CodeReview/codereview.css (modified) (history)
  • /trunk/extensions/CodeReview/codereview.sql (modified) (history)
  • /trunk/extensions/CodeReview/ui/CodeRevisionCommitter.php (modified) (history)
  • /trunk/extensions/CodeReview/ui/CodeRevisionView.php (modified) (history)

Diff [purge]

Index: trunk/extensions/CodeReview/CodeReview.i18n.php
@@ -104,11 +104,14 @@
105105 'code-signoffs' => 'Sign-offs',
106106 'code-signoff-legend' => 'Add sign-off',
107107 'code-signoff-submit' => 'Sign off',
 108+ 'code-signoff-strike' => 'Strike selected sign-offs',
 109+ 'code-signoff-signoff' => 'Sign off on this revision:',
108110 'code-signoff-flag-inspected' => 'Inspected',
109111 'code-signoff-flag-tested' => 'Tested',
110112 'code-signoff-field-user' => 'User',
111113 'code-signoff-field-flag' => 'Flag',
112114 'code-signoff-field-date' => 'Date',
 115+ 'code-signoff-struckdate' => '$1 (struck $2)',
113116 'code-pathsearch-legend' => 'Search revisions in this repo by path',
114117 'code-pathsearch-path' => 'Path:',
115118 'code-pathsearch-filter' => 'Filter applied:',
Index: trunk/extensions/CodeReview/CodeReview.php
@@ -199,12 +199,16 @@
200200
201201 $updater->addExtensionUpdate( array( 'addField', 'code_signoffs', 'cs_user',
202202 "$base/archives/code_signoffs_userid.sql", true ) );
 203+ $updater->addExtensionUpdate( array( 'addField', 'code_signoffs', 'cs_timestamp_struck',
 204+ "$base/archives/code_signoffs_timestamp_struck.sql", true ) );
203205 break;
204206 case 'sqlite':
205207 $updater->addExtensionUpdate( array( 'addTable', 'code_rev', "$base/codereview.sql", true ) );
206208 $updater->addExtensionUpdate( array( 'addTable', 'code_signoffs', "$base/archives/code_signoffs.sql", true ) );
207209 $updater->addExtensionUpdate( array( 'addField', 'code_signoffs', 'cs_user',
208210 "$base/archives/code_signoffs_userid-sqlite.sql", true ) );
 211+ $updater->addExtensionUpdate( array( 'addField', 'code_signoffs', 'cs_timestamp_struck',
 212+ "$base/archives/code_signoffs_timestamp_struck.sql", true ) );
209213 break;
210214 case 'postgres':
211215 // TODO
Index: trunk/extensions/CodeReview/codereview.css
@@ -99,6 +99,15 @@
100100 color: #666;
101101 }
102102
 103+.mw-codereview-signoffchecks {
 104+ float: right;
 105+}
 106+
 107+.mw-codereview-struck td {
 108+ text-decoration: line-through;
 109+ background: #aaa;
 110+}
 111+
103112 .mw-codereview-success {
104113 color: #1a2;
105114 }
Index: trunk/extensions/CodeReview/archives/code_signoffs_timestamp_struck.sql
@@ -0,0 +1,4 @@
 2+ALTER TABLE /*_*/code_signoffs
 3+ ADD COLUMN cs_timestamp_struck varbinary(14) not null default 'infinity';
 4+DROP INDEX /*i*/cs_repo_rev_user_flag ON /*_*/code_signoffs;
 5+CREATE UNIQUE INDEX /*i*/cs_repo_rev_user_flag_tstruck ON /*_*/code_signoffs (cs_repo_id, cs_rev_id, cs_user_text, cs_flag, cs_timestamp_struck);
Property changes on: trunk/extensions/CodeReview/archives/code_signoffs_timestamp_struck.sql
___________________________________________________________________
Added: svn:eol-style
16 + native
Index: trunk/extensions/CodeReview/backend/CodeRevision.php
@@ -632,7 +632,7 @@
633633 public function getSignoffs( $from = DB_SLAVE ) {
634634 $db = wfGetDB( $from );
635635 $result = $db->select( 'code_signoffs',
636 - array( 'cs_user', 'cs_user_text', 'cs_flag', 'cs_timestamp' ),
 636+ array( 'cs_user', 'cs_user_text', 'cs_flag', 'cs_timestamp', 'cs_timestamp_struck' ),
637637 array(
638638 'cs_repo_id' => $this->mRepoId,
639639 'cs_rev_id' => $this->mId,
@@ -664,11 +664,22 @@
665665 'cs_user_text' => $user->getName(),
666666 'cs_flag' => $flag,
667667 'cs_timestamp' => $dbw->timestamp(),
 668+ 'cs_timestamp_struck' => Block::infinity()
668669 );
669670 }
670671 $dbw->insert( 'code_signoffs', $rows, __METHOD__, array( 'IGNORE' ) );
671672 }
672673
 674+ public function strikeSignoffs( $user, $ids ) {
 675+ foreach ( $ids as $id ) {
 676+ $signoff = CodeSignoff::newFromId( $this, $id );
 677+ // Only allow striking own signoffs
 678+ if ( $signoff && $signoff->userText === $user->getName() ) {
 679+ $signoff->strike();
 680+ }
 681+ }
 682+ }
 683+
673684 public function getTags( $from = DB_SLAVE ) {
674685 $db = wfGetDB( $from );
675686 $result = $db->select( 'code_tags',
Index: trunk/extensions/CodeReview/backend/CodeSignoff.php
@@ -1,22 +1,74 @@
22 <?php
33 class CodeSignoff {
44 public $rev, $user, $flag, $timestamp, $userText;
 5+ private $timestampStruck;
56
6 - public function __construct( $rev, $user, $userText, $flag, $timestamp ) {
 7+ public function __construct( $rev, $user, $userText, $flag, $timestamp, $timestampStruck ) {
78 $this->rev = $rev;
89 $this->user = $user;
910 $this->userText = $userText;
1011 $this->flag = $flag;
1112 $this->timestamp = $timestamp;
 13+ $this->timestampStruck = $timestampStruck;
1214 }
1315
 16+ public function isStruck() {
 17+ return $this->timestampStruck !== Block::infinity();
 18+ }
 19+
 20+ public function getTimestampStruck() {
 21+ return wfTimestamp( TS_MW, $this->timestampStruck );
 22+ }
 23+
 24+ public function strike() {
 25+ if ( $this->isStruck() ) {
 26+ return;
 27+ }
 28+ $dbw = wfGetDB( DB_MASTER );
 29+ $dbw->update( 'code_signoffs', array( 'cs_timestamp_struck' => $dbw->timestamp() ),
 30+ array(
 31+ 'cs_repo_id' => $this->rev->getRepoId(),
 32+ 'cs_rev_id' => $this->rev->getId(),
 33+ 'cs_flag' => $this->flag,
 34+ 'cs_user_text' => $this->userText,
 35+ 'cs_timestamp_struck' => $this->timestampStruck
 36+ ), __METHOD__
 37+ );
 38+ }
 39+
 40+ public function getID() {
 41+ return implode( '|', array( $this->flag, $this->timestampStruck, $this->userText ) );
 42+ }
 43+
1444 public static function newFromRow( $rev, $row ) {
1545 return self::newFromData( $rev, get_object_vars( $row ) );
1646 }
1747
1848 public static function newFromData( $rev, $data ) {
1949 return new self( $rev, $data['cs_user'], $data['cs_user_text'], $data['cs_flag'],
20 - wfTimestamp( TS_MW, $data['cs_timestamp'] )
 50+ wfTimestamp( TS_MW, $data['cs_timestamp'] ), $data['cs_timestamp_struck']
2151 );
2252 }
 53+
 54+ public static function newFromID( $rev, $id ) {
 55+ $parts = explode( '|', $id, 3 );
 56+ if ( count( $parts ) != 3 ) {
 57+ return null;
 58+ }
 59+ $dbr = wfGetDB( DB_SLAVE );
 60+ $row = $dbr->selectRow( 'code_signoffs',
 61+ array( 'cs_user', 'cs_user_text', 'cs_flag', 'cs_timestamp', 'cs_timestamp_struck' ),
 62+ array(
 63+ 'cs_repo_id' => $rev->getRepoId(),
 64+ 'cs_rev_id' => $rev->getId(),
 65+ 'cs_flag' => $parts[0],
 66+ 'cs_timestamp_struck' => $parts[1],
 67+ 'cs_user_text' => $parts[2]
 68+ ), __METHOD__
 69+ );
 70+ if ( !$row ) {
 71+ return null;
 72+ }
 73+ return self::newFromRow( $rev, $row );
 74+ }
2375 }
Index: trunk/extensions/CodeReview/codereview.sql
@@ -236,7 +236,10 @@
237237 cs_flag varchar(25) not null,
238238
239239 -- Timestamp of the sign-off
240 - cs_timestamp binary(14) not null default ''
 240+ cs_timestamp binary(14) not null default '',
 241+
 242+ -- Timestamp the sign-off was struck, or Block::infinity() if not struck
 243+ cs_timestamp_struck varbinary(14) not null default 'infinity'
241244 ) /*$wgDBTableOptions*/;
242 -CREATE UNIQUE INDEX /*i*/cs_repo_rev_user_flag ON /*_*/code_signoffs (cs_repo_id, cs_rev_id, cs_user_text, cs_flag);
 245+CREATE UNIQUE INDEX /*i*/cs_repo_rev_user_flag_tstruck ON /*_*/code_signoffs (cs_repo_id, cs_rev_id, cs_user_text, cs_flag, cs_timestamp_struck);
243246 CREATE INDEX /*i*/cs_repo_repo_rev_timestamp ON /*_*/code_signoffs (cs_repo_id, cs_rev_id, cs_timestamp);
Index: trunk/extensions/CodeReview/ui/CodeRevisionCommitter.php
@@ -15,7 +15,7 @@
1616 }
1717
1818 $commentId = $this->revisionUpdate( $this->mStatus, $this->mAddTags, $this->mRemoveTags,
19 - $this->mSignoffFlags, $this->text, $wgRequest->getIntOrNull( 'wpParent' ),
 19+ $this->mSignoffFlags, $this->mStrikeSignoffs, $this->text, $wgRequest->getIntOrNull( 'wpParent' ),
2020 $wgRequest->getInt( 'wpReview' )
2121 );
2222
@@ -50,15 +50,15 @@
5151 * @param string $status Status to set the revision to
5252 * @param Array $addTags Tags to add to the revision
5353 * @param Array $removeTags Tags to remove from the Revision
54 - * @param Array $signoffFlags
 54+ * @param Array $signoffFlags Array of sign-off flags to add
 55+ * @param Array $strikeSignoffs Array of sign-off IDs to strike
5556 * @param string $commentText Comment to add to the revision
5657 * @param null|int $parent What the parent comment is (if a subcomment)
5758 * @param int $review (unused)
5859 * @return int Comment ID if added, else 0
5960 */
60 - public function revisionUpdate( $status, $addTags, $removeTags, $signoffFlags, $commentText, $parent = null,
61 - $review = 0 ) {
62 -
 61+ public function revisionUpdate( $status, $addTags, $removeTags, $signoffFlags, $strikeSignoffs,
 62+ $commentText, $parent = null, $review = 0 ) {
6363 if ( !$this->mRev ) {
6464 return false;
6565 }
@@ -88,6 +88,10 @@
8989 if ( count( $signoffFlags ) && $this->validPost( 'codereview-signoff' ) ) {
9090 $this->mRev->addSignoff( $wgUser, $signoffFlags );
9191 }
 92+ // Strike any signoffs
 93+ if ( count( $strikeSignoffs ) && $this->validPost( 'codereview-signoff' ) ) {
 94+ $this->mRev->strikeSignoffs( $wgUser, $strikeSignoffs );
 95+ }
9296 // Add any comments
9397 $commentAdded = false;
9498 $commentId = 0;
Index: trunk/extensions/CodeReview/ui/CodeRevisionView.php
@@ -44,7 +44,9 @@
4545 # Make tag arrays
4646 $this->mAddTags = $this->splitTags( $this->mAddTags );
4747 $this->mRemoveTags = $this->splitTags( $this->mRemoveTags );
48 - $this->mSignoffFlags = $wgRequest->getArray( 'wpSignoffFlags' );
 48+ $this->mSignoffFlags = $wgRequest->getCheck( 'wpSignoff' ) ? $wgRequest->getArray( 'wpSignoffFlags' ) : array();
 49+ $this->mSelectedSignoffs = $wgRequest->getArray( 'wpSignoffs' );
 50+ $this->mStrikeSignoffs = $wgRequest->getCheck( 'wpStrikeSignoffs' ) ? $this->mSelectedSignoffs : array();
4951 }
5052
5153 function execute() {
@@ -129,14 +131,11 @@
130132 $html .= $this->formatImgDiff();
131133 }
132134 # Show sign-offs
133 - $signoffs = $this->formatSignoffs();
 135+ $signoffs = $this->formatSignoffs( $this->canSignoff() );
134136 if ( $signoffs ) {
135137 $html .= "<h2 id='code-signoffs'>" . wfMsgHtml( 'code-signoffs' ) .
136138 "</h2>\n" . $signoffs;
137139 }
138 - if( $this->canSignoff() ) {
139 - $html .= $this->signoffForm();
140 - }
141140 # Show code relations
142141 $relations = $this->formatReferences();
143142 if ( $relations ) {
@@ -431,17 +430,19 @@
432431 return wfMsg( 'code-load-diff' );
433432 }
434433
435 - protected function formatSignoffs() {
 434+ protected function formatSignoffs( $showButtons ) {
436435 $signoffs = implode( "\n",
437436 array_map( array( $this, 'formatSignoffInline' ), $this->mRev->getSignoffs() )
438437 );
439438 if ( !$signoffs ) {
440439 return false;
441440 }
442 - $header = '<th>' . wfMsg( 'code-signoff-field-user' ) . '</th>';
 441+ $header = '<th></th>';
 442+ $header .= '<th>' . wfMsg( 'code-signoff-field-user' ) . '</th>';
443443 $header .= '<th>' . wfMsg( 'code-signoff-field-flag' ). '</th>';
444444 $header .= '<th>' . wfMsg( 'code-signoff-field-date' ). '</th>';
445 - return "<table border='1' class='TablePager'><tr>$header</tr>$signoffs</table>";
 445+ $buttonrow = $showButtons ? $this->signoffButtons() : '';
 446+ return "<table border='1' class='TablePager'><tr>$header</tr>$signoffs$buttonrow</table>";
446447 }
447448
448449 protected function formatComments() {
@@ -487,12 +488,19 @@
488489 */
489490 protected function formatSignoffInline( $signoff ) {
490491 global $wgLang;
 492+ $checkbox = Html::input( 'wpSignoffs[]', $signoff->getID(), 'checkbox' );
491493 $user = $this->skin->userLink( $signoff->user, $signoff->userText );
492 -
493494 $flag = htmlspecialchars( $signoff->flag );
494 - $date = $wgLang->timeanddate( $signoff->timestamp, true );
495 - $class = "mw-codereview-signoff-$flag";
496 - return "<tr class='$class'><td>$user</td><td>$flag</td><td>$date</td></tr>";
 495+ $signoffDate = $wgLang->timeanddate( $signoff->timestamp, true );
 496+ $class = "mw-codereview-signoff-flag-$flag";
 497+ if ( $signoff->isStruck() ) {
 498+ $class .= ' mw-codereview-struck';
 499+ $struckDate = $wgLang->timeanddate( $signoff->getTimestampStruck(), true );
 500+ $date = wfMsg( 'code-signoff-struckdate', $signoffDate, $struckDate );
 501+ } else {
 502+ $date = $signoffDate;
 503+ }
 504+ return "<tr class='$class'><td>$checkbox</td><td>$user</td><td>$flag</td><td>$date</td></tr>";
497505 }
498506
499507 protected function formatCommentInline( $comment ) {
@@ -655,14 +663,17 @@
656664 }
657665
658666 /** TODO : checkboxes should be disabled if user already has set the flag */
659 - protected function signoffForm() {
660 - $form = Xml::element( 'legend', array(), wfMsg( 'code-signoff-legend' ) );
 667+ protected function signoffButtons() {
 668+ $strikeButton = Xml::submitButton( wfMsg( 'code-signoff-strike' ), array( 'name' => 'wpStrikeSignoffs' ) );
 669+ $signoffText = wfMsgHtml( 'code-signoff-signoff' );
 670+ $signoffButton = Xml::submitButton( wfMsg( 'code-signoff-submit' ), array( 'name' => 'wpSignoff' ) );
 671+ $checks = '';
661672 foreach ( CodeRevision::getPossibleFlags() as $flag ) {
662 - $form .= Html::input( 'wpSignoffFlags[]', $flag, 'checkbox', array( 'id' => "wpSignoffFlags-$flag" ) ) .
663 - Xml::label( wfMsg( "code-signoff-flag-$flag" ), "wpSignoffFlags-$flag" ) . "\n";
 673+ $checks .= Html::input( 'wpSignoffFlags[]', $flag, 'checkbox', array( 'id' => "wpSignoffFlags-$flag" ) ) .
 674+ ' ' . Xml::label( wfMsg( "code-signoff-flag-$flag" ), "wpSignoffFlags-$flag" ) . ' ';
664675 }
665 - $form .= Xml::submitButton( wfMsg( 'code-signoff-submit' ) );
666 - return Xml::tags( 'fieldset', array(), $form );
 676+ return "<tr class='mw-codereview-signoffbuttons'><td colspan='4'>$strikeButton " .
 677+ "<div class='mw-codereview-signoffchecks'>$signoffText $checks $signoffButton</div></td></tr>";
667678 }
668679
669680 protected function addActionButtons() {

Follow-up revisions

RevisionCommit summaryAuthorDate
r77306Followup r77302: show signoff section even if there are no signoff, so people...catrope22:35, 25 November 2010
r77307Message documentation for r77302catrope22:43, 25 November 2010
r77308Message tweak for r77302 suggested by Reedycatrope22:45, 25 November 2010
r77310Some documentation for r77302, more tomorrow. Functional change: make getTime...catrope23:26, 25 November 2010
r77311Followup r77302, update ApiRevisionUpdate to give flag removalreedy23:41, 25 November 2010
r77326Document functions added in r75327, r77302 (sign-off feature), fix comments a...catrope12:18, 26 November 2010
r81060CodeReview: Update code_signoff.sql for r77302 (cs_timestamp_struck) so the e...catrope22:25, 26 January 2011

Status & tagging log