Index: trunk/extensions/CodeReview/CodeReview.i18n.php |
— | — | @@ -99,6 +99,14 @@ |
100 | 100 | 'code-status-desc-deferred' => 'Revision does not require review.', |
101 | 101 | 'code-status-old' => 'old', |
102 | 102 | 'code-status-desc-old' => 'Old revision with potential bugs but which are not worth the effort of reviewing them.', |
| 103 | + 'code-signoffs' => 'Sign-offs', |
| 104 | + 'code-signoff-legend' => 'Add sign-off', |
| 105 | + 'code-signoff-submit' => 'Sign off', |
| 106 | + 'code-signoff-flag-inspected' => 'Inspected', |
| 107 | + 'code-signoff-flag-tested' => 'Tested', |
| 108 | + 'code-signoff-field-user' => 'User', |
| 109 | + 'code-signoff-field-flag' => 'Flag', |
| 110 | + 'code-signoff-field-date' => 'Date', |
103 | 111 | 'code-pathsearch-legend' => 'Search revisions in this repo by path', |
104 | 112 | 'code-pathsearch-path' => 'Path:', |
105 | 113 | 'code-pathsearch-filter' => 'Filter applied:', |
— | — | @@ -189,6 +197,7 @@ |
190 | 198 | 'right-codereview-remove-tag' => 'Remove tags from revisions', |
191 | 199 | 'right-codereview-post-comment' => 'Add comments on revisions', |
192 | 200 | 'right-codereview-set-status' => 'Change revisions status', |
| 201 | + 'right-codereview-signoff' => 'Sign off on revisions', |
193 | 202 | 'right-codereview-link-user' => 'Link authors to wiki users', |
194 | 203 | |
195 | 204 | 'specialpages-group-developer' => 'Developer tools', |
Index: trunk/extensions/CodeReview/CodeReview.php |
— | — | @@ -52,6 +52,7 @@ |
53 | 53 | $wgAutoloadClasses['CodeCommentLinkerHtml'] = $dir . 'backend/CodeCommentLinker.php'; |
54 | 54 | $wgAutoloadClasses['CodeCommentLinkerWiki'] = $dir . 'backend/CodeCommentLinker.php'; |
55 | 55 | $wgAutoloadClasses['CodePropChange'] = $dir . 'backend/CodePropChange.php'; |
| 56 | +$wgAutoloadClasses['CodeSignoff'] = $dir . 'backend/CodeSignoff.php'; |
56 | 57 | $wgAutoloadClasses['RepoStats'] = $dir . 'backend/RepoStats.php'; |
57 | 58 | |
58 | 59 | $wgAutoloadClasses['CodeRepoListView'] = $dir . 'ui/CodeRepoListView.php'; |
— | — | @@ -93,6 +94,7 @@ |
94 | 95 | $wgAvailableRights[] = 'codereview-remove-tag'; |
95 | 96 | $wgAvailableRights[] = 'codereview-post-comment'; |
96 | 97 | $wgAvailableRights[] = 'codereview-set-status'; |
| 98 | +$wgAvailableRights[] = 'codereview-signoff'; |
97 | 99 | $wgAvailableRights[] = 'codereview-link-user'; |
98 | 100 | |
99 | 101 | $wgGroupPermissions['*']['codereview-use'] = true; |
— | — | @@ -102,6 +104,7 @@ |
103 | 105 | $wgGroupPermissions['user']['codereview-post-comment'] = true; |
104 | 106 | $wgGroupPermissions['user']['codereview-set-status'] = true; |
105 | 107 | $wgGroupPermissions['user']['codereview-link-user'] = true; |
| 108 | +$wgGroupPermissions['user']['codereview-signoff'] = true; |
106 | 109 | |
107 | 110 | $wgGroupPermissions['steward']['repoadmin'] = true; // temp |
108 | 111 | |
— | — | @@ -178,6 +181,7 @@ |
179 | 182 | } |
180 | 183 | |
181 | 184 | $updater->addExtensionUpdate( array( 'addTable', 'code_bugs', "$base/archives/code_bugs.sql", true ) ); |
| 185 | + $updater->addExtensionUpdate( array( 'addTable', 'code_signoffs', "$base/archives/code_signoffs.sql", true ) ); |
182 | 186 | break; |
183 | 187 | case 'sqlite': |
184 | 188 | $updater->addExtensionUpdate( array( 'addTable', 'code_rev', "$base/codereview.sql", true ) ); |
Index: trunk/extensions/CodeReview/backend/CodeRevision.php |
— | — | @@ -141,6 +141,10 @@ |
142 | 142 | public static function getPossibleStates() { |
143 | 143 | return array( 'new', 'fixme', 'reverted', 'resolved', 'ok', 'verified', 'deferred', 'old' ); |
144 | 144 | } |
| 145 | + |
| 146 | + public static function getPossibleFlags() { |
| 147 | + return array( 'inspected', 'tested' ); |
| 148 | + } |
145 | 149 | |
146 | 150 | public function isValidStatus( $status ) { |
147 | 151 | return in_array( $status, self::getPossibleStates(), true ); |
— | — | @@ -628,6 +632,40 @@ |
629 | 633 | return $refs; |
630 | 634 | } |
631 | 635 | |
| 636 | + public function getSignoffs( $from = DB_SLAVE ) { |
| 637 | + $db = wfGetDB( $from ); |
| 638 | + $result = $db->select( 'code_signoffs', |
| 639 | + array( 'cs_user_text', 'cs_flag', 'cs_timestamp' ), |
| 640 | + array( |
| 641 | + 'cs_repo_id' => $this->mRepoId, |
| 642 | + 'cs_rev_id' => $this->mId, |
| 643 | + ), |
| 644 | + __METHOD__, |
| 645 | + array( 'ORDER BY' => 'cs_timestamp' ) |
| 646 | + ); |
| 647 | + |
| 648 | + $signoffs = array(); |
| 649 | + foreach ( $result as $row ) { |
| 650 | + $signoffs[] = CodeSignoff::newFromRow( $this, $row ); |
| 651 | + } |
| 652 | + return $signoffs; |
| 653 | + } |
| 654 | + |
| 655 | + public function addSignoff( $user, $flags ) { |
| 656 | + $dbw = wfGetDB( DB_MASTER ); |
| 657 | + $rows = array(); |
| 658 | + foreach ( $flags as $flag ) { |
| 659 | + $rows[] = array( |
| 660 | + 'cs_repo_id' => $this->mRepoId, |
| 661 | + 'cs_rev_id' => $this->mId, |
| 662 | + 'cs_user_text' => $user->getName(), |
| 663 | + 'cs_flag' => $flag, |
| 664 | + 'cs_timestamp' => $dbw->timestamp(), |
| 665 | + ); |
| 666 | + } |
| 667 | + $dbw->insert( 'code_signoffs', $rows, __METHOD__ ); |
| 668 | + } |
| 669 | + |
632 | 670 | public function getTags( $from = DB_SLAVE ) { |
633 | 671 | $db = wfGetDB( $from ); |
634 | 672 | $result = $db->select( 'code_tags', |
Index: trunk/extensions/CodeReview/codereview.sql |
— | — | @@ -54,7 +54,7 @@ |
55 | 55 | -- 'verified': Reviewed and tested, no issues spotted |
56 | 56 | -- 'deferred': Not reviewed at this time (usually non-Wikimedia extension) |
57 | 57 | -- 'old': Predates the extension/doesn't require review |
58 | | - -- See CodeRevision::getPossibleStates() (in backend\CodeRevision.php) for most up to date list |
| 58 | + -- See CodeRevision::getPossibleStates() (in backend/CodeRevision.php) for most up to date list |
59 | 59 | cr_status varchar(25) not null default 'new', |
60 | 60 | |
61 | 61 | -- Base path of this revision : |
— | — | @@ -221,3 +221,21 @@ |
222 | 222 | |
223 | 223 | CREATE INDEX /*i*/cpc_repo_rev_time ON /*_*/code_prop_changes (cpc_repo_id, cpc_rev_id, cpc_timestamp); |
224 | 224 | CREATE INDEX /*i*/cpc_repo_time ON /*_*/code_prop_changes (cpc_repo_id, cpc_timestamp); |
| 225 | + |
| 226 | +CREATE TABLE /*_*/code_signoffs ( |
| 227 | + -- Repository ID and revision ID |
| 228 | + cs_repo_id int not null, |
| 229 | + cs_rev_id int not null, |
| 230 | + |
| 231 | + -- User that signed off |
| 232 | + cs_user_text varchar(255) not null, |
| 233 | + |
| 234 | + -- Type of signoff. Current values: 'inspected', 'tested' |
| 235 | + -- See CodeRevision::getPossibleFlags() (in backend/CodeRevision.php) for most up to date list |
| 236 | + cs_flag varchar(25) not null, |
| 237 | + |
| 238 | + -- Timestamp of the sign-off |
| 239 | + cs_timestamp binary(14) not null default '' |
| 240 | +) /*$wgDBTableOptions*/; |
| 241 | +CREATE UNIQUE INDEX /*i*/cs_repo_rev_user_flag ON /*_*/code_signoffs (cs_repo_id, cs_rev_id, cs_user_text, cs_flag); |
| 242 | +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 |
— | — | @@ -40,6 +40,10 @@ |
41 | 41 | if ( count( $addTags ) || count( $removeTags ) ) { |
42 | 42 | $this->mRev->changeTags( $addTags, $removeTags, $wgUser ); |
43 | 43 | } |
| 44 | + // Add any signoffs |
| 45 | + if ( $this->validPost( 'codereview-signoff' ) && count( $this->mSignoffFlags ) ) { |
| 46 | + $this->mRev->addSignoff( $wgUser, $this->mSignoffFlags ); |
| 47 | + } |
44 | 48 | // Add any comments |
45 | 49 | $commentAdded = false; |
46 | 50 | if ( $this->validPost( 'codereview-post-comment' ) && strlen( $this->text ) ) { |
Index: trunk/extensions/CodeReview/ui/CodeRevisionView.php |
— | — | @@ -28,6 +28,7 @@ |
29 | 29 | # Make tag arrays |
30 | 30 | $this->mAddTags = $this->splitTags( $this->mAddTags ); |
31 | 31 | $this->mRemoveTags = $this->splitTags( $this->mRemoveTags ); |
| 32 | + $this->mSignoffFlags = $wgRequest->getArray( 'wpSignoffFlags' ); |
32 | 33 | } |
33 | 34 | |
34 | 35 | function execute() { |
— | — | @@ -108,6 +109,15 @@ |
109 | 110 | "<div class='mw-codereview-diff' id='mw-codereview-diff'>" . $diffHtml . "</div>\n"; |
110 | 111 | $html .= $this->formatImgDiff(); |
111 | 112 | } |
| 113 | + # Show sign-offs |
| 114 | + $signoffs = $this->formatSignoffs(); |
| 115 | + if ( $signoffs ) { |
| 116 | + $html .= "<h2 id='code-signoffs'>" . wfMsgHtml( 'code-signoffs' ) . |
| 117 | + "</h2>\n" . $signoffs; |
| 118 | + } |
| 119 | + if( $this->canSignoff() ) { |
| 120 | + $html .= $this->signoffForm(); |
| 121 | + } |
112 | 122 | # Show code relations |
113 | 123 | $relations = $this->formatReferences(); |
114 | 124 | if ( $relations ) { |
— | — | @@ -193,6 +203,11 @@ |
194 | 204 | return $wgUser->isAllowed( 'codereview-post-comment' ) && !$wgUser->isBlocked(); |
195 | 205 | } |
196 | 206 | |
| 207 | + protected function canSignoff() { |
| 208 | + global $wgUser; |
| 209 | + return $wgUser->isAllowed( 'codereview-signoff' ) && !$wgUser->isBlocked(); |
| 210 | + } |
| 211 | + |
197 | 212 | protected function formatPathLine( $path, $action ) { |
198 | 213 | // Uses messages 'code-rev-modified-a', 'code-rev-modified-r', 'code-rev-modified-d', 'code-rev-modified-m' |
199 | 214 | $desc = wfMsgHtml( 'code-rev-modified-' . strtolower( $action ) ); |
— | — | @@ -396,6 +411,19 @@ |
397 | 412 | );" ); |
398 | 413 | return wfMsg( 'code-load-diff' ); |
399 | 414 | } |
| 415 | + |
| 416 | + protected function formatSignoffs() { |
| 417 | + $signoffs = implode( "\n", |
| 418 | + array_map( array( $this, 'formatSignoffInline' ), $this->mRev->getSignoffs() ) |
| 419 | + ); |
| 420 | + if ( !$signoffs ) { |
| 421 | + return false; |
| 422 | + } |
| 423 | + $header = '<th>' . wfMsg( 'code-signoff-field-user' ) . '</th>'; |
| 424 | + $header .= '<th>' . wfMsg( 'code-signoff-field-flag' ). '</th>'; |
| 425 | + $header .= '<th>' . wfMsg( 'code-signoff-field-date' ). '</th>'; |
| 426 | + return "<table border='1' class='TablePager'><tr>$header</tr>$signoffs</table>"; |
| 427 | + } |
400 | 428 | |
401 | 429 | protected function formatComments() { |
402 | 430 | $comments = implode( "\n", |
— | — | @@ -434,6 +462,15 @@ |
435 | 463 | return "<table border='1' class='TablePager'><tr>{$header}</tr>{$refs}</table>"; |
436 | 464 | } |
437 | 465 | |
| 466 | + protected function formatSignoffInline( $signoff ) { |
| 467 | + global $wgLang; |
| 468 | + $user = htmlspecialchars( $signoff->user ); |
| 469 | + $flag = htmlspecialchars( $signoff->flag ); |
| 470 | + $date = $wgLang->timeanddate( $signoff->timestamp, true ); |
| 471 | + $class = "mw-codereview-signoff-$flag"; |
| 472 | + return "<tr class='$class'><td>$user</td><td>$flag</td><td>$date</td></tr>"; |
| 473 | + } |
| 474 | + |
438 | 475 | protected function formatCommentInline( $comment ) { |
439 | 476 | if ( $comment->id === $this->mReplyTarget ) { |
440 | 477 | return $this->formatComment( $comment, |
— | — | @@ -593,6 +630,16 @@ |
594 | 631 | '</div>'; |
595 | 632 | } |
596 | 633 | |
| 634 | + protected function signoffForm() { |
| 635 | + $form = Xml::element( 'legend', array(), wfMsg( 'code-signoff-legend' ) ); |
| 636 | + foreach ( CodeRevision::getPossibleFlags() as $flag ) { |
| 637 | + $form .= Html::input( 'wpSignoffFlags[]', $flag, 'checkbox', array( 'id' => "wpSignoffFlags-$flag" ) ) . |
| 638 | + Xml::label( wfMsg( "code-signoff-flag-$flag" ), "wpSignoffFlags-$flag" ) . "\n"; |
| 639 | + } |
| 640 | + $form .= Xml::submitButton( wfMsg( 'code-signoff-submit' ) ); |
| 641 | + return Xml::tags( 'fieldset', array(), $form ); |
| 642 | + } |
| 643 | + |
597 | 644 | protected function addActionButtons() { |
598 | 645 | return '<div>' . |
599 | 646 | Xml::submitButton( wfMsg( 'code-rev-submit' ), |