1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-27 16:00:59 +01:00

Add a "Needs Verification" state to Audit

Summary:
Fixes T2393. This allows authors to explicitly say "I think I fixed everything, please accept my commit now thank you".

Also improves behavior of "re-accept" and "re-reject" after new auditors you have authority over get added.

Test Plan:
  - Kicked a commit back and forth between an author and auditor by alternately using "Request Verification" and "Raise Concern".
  - Verified it showed up properly in bucketing for both users.
  - Accepted, added a project, accepted again (works now; didn't before).
  - Audited on behalf of projects / packages.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T2393

Differential Revision: https://secure.phabricator.com/D17252
This commit is contained in:
epriestley 2017-01-25 11:05:49 -08:00
parent ca182c7f48
commit 97cac83e9b
10 changed files with 191 additions and 32 deletions

View file

@ -674,6 +674,7 @@ phutil_register_library_map(array(
'DiffusionCommitStateTransaction' => 'applications/diffusion/xaction/DiffusionCommitStateTransaction.php',
'DiffusionCommitTagsController' => 'applications/diffusion/controller/DiffusionCommitTagsController.php',
'DiffusionCommitTransactionType' => 'applications/diffusion/xaction/DiffusionCommitTransactionType.php',
'DiffusionCommitVerifyTransaction' => 'applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php',
'DiffusionCompareController' => 'applications/diffusion/controller/DiffusionCompareController.php',
'DiffusionConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionConduitAPIMethod.php',
'DiffusionController' => 'applications/diffusion/controller/DiffusionController.php',
@ -5382,6 +5383,7 @@ phutil_register_library_map(array(
'DiffusionCommitStateTransaction' => 'DiffusionCommitTransactionType',
'DiffusionCommitTagsController' => 'DiffusionController',
'DiffusionCommitTransactionType' => 'PhabricatorModularTransactionType',
'DiffusionCommitVerifyTransaction' => 'DiffusionCommitAuditTransaction',
'DiffusionCompareController' => 'DiffusionController',
'DiffusionConduitAPIMethod' => 'ConduitAPIMethod',
'DiffusionController' => 'PhabricatorController',

View file

@ -7,12 +7,14 @@ final class PhabricatorAuditCommitStatusConstants extends Phobject {
const CONCERN_RAISED = 2;
const PARTIALLY_AUDITED = 3;
const FULLY_AUDITED = 4;
const NEEDS_VERIFICATION = 5;
public static function getStatusNameMap() {
$map = array(
self::NONE => pht('No Audits'),
self::NEEDS_AUDIT => pht('Audit Required'),
self::CONCERN_RAISED => pht('Concern Raised'),
self::NEEDS_VERIFICATION => pht('Needs Verification'),
self::PARTIALLY_AUDITED => pht('Partially Audited'),
self::FULLY_AUDITED => pht('Audited'),
);
@ -28,6 +30,7 @@ final class PhabricatorAuditCommitStatusConstants extends Phobject {
return array(
self::CONCERN_RAISED,
self::NEEDS_AUDIT,
self::NEEDS_VERIFICATION,
self::PARTIALLY_AUDITED,
);
}
@ -49,6 +52,9 @@ final class PhabricatorAuditCommitStatusConstants extends Phobject {
case self::NONE:
$color = 'bluegrey';
break;
case self::NEEDS_VERIFICATION:
$color = 'indigo';
break;
default:
$color = null;
break;
@ -56,7 +62,7 @@ final class PhabricatorAuditCommitStatusConstants extends Phobject {
return $color;
}
public static function getStatusIcon($code) {
public static function getStatusIcon($code) {
switch ($code) {
case self::CONCERN_RAISED:
$icon = 'fa-times-circle';
@ -73,6 +79,9 @@ final class PhabricatorAuditCommitStatusConstants extends Phobject {
case self::NONE:
$icon = 'fa-check';
break;
case self::NEEDS_VERIFICATION:
$icon = 'fa-refresh';
break;
default:
$icon = null;
break;

View file

@ -11,6 +11,7 @@ final class PhabricatorAuditEditor
private $auditorPHIDs = array();
private $didExpandInlineState = false;
private $oldAuditStatus = null;
public function addAuditReason($phid, $reason) {
if (!isset($this->auditReasonMap[$phid])) {
@ -78,6 +79,8 @@ final class PhabricatorAuditEditor
}
}
$this->oldAuditStatus = $object->getAuditStatus();
$object->loadAndAttachAuditAuthority(
$this->getActor(),
$this->getActingAsPHID());
@ -269,7 +272,7 @@ final class PhabricatorAuditEditor
}
}
$old_status = $object->getAuditStatus();
$old_status = $this->oldAuditStatus;
$requests = $object->getAudits();
$object->updateAuditStatus($requests);

View file

@ -33,6 +33,11 @@ final class DiffusionCommitRequiredActionResultBucket
->setNoDataString(pht('None of your commits have active concerns.'))
->setObjects($this->filterConcernRaised($phids));
$groups[] = $this->newGroup()
->setName(pht('Needs Verification'))
->setNoDataString(pht('No commits are awaiting your verification.'))
->setObjects($this->filterNeedsVerification($phids));
$groups[] = $this->newGroup()
->setName(pht('Ready to Audit'))
->setNoDataString(pht('No commits are waiting for you to audit them.'))
@ -82,6 +87,36 @@ final class DiffusionCommitRequiredActionResultBucket
return $results;
}
private function filterNeedsVerification(array $phids) {
$results = array();
$objects = $this->objects;
$status_verify = PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION;
$has_concern = array(
PhabricatorAuditStatusConstants::CONCERNED,
);
$has_concern = array_fuse($has_concern);
foreach ($objects as $key => $object) {
if (isset($phids[$object->getAuthorPHID()])) {
continue;
}
if ($object->getAuditStatus() != $status_verify) {
continue;
}
if (!$this->hasAuditorsWithStatus($object, $phids, $has_concern)) {
continue;
}
$results[$key] = $object;
unset($this->objects[$key]);
}
return $results;
}
private function filterShouldAudit(array $phids) {
$results = array();
$objects = $this->objects;
@ -132,6 +167,7 @@ final class DiffusionCommitRequiredActionResultBucket
$status_waiting = array(
PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT,
PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION,
PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED,
);
$status_waiting = array_fuse($status_waiting);

View file

@ -30,11 +30,6 @@ final class DiffusionCommitAcceptTransaction
return pht('Accepted');
}
public function generateOldValue($object) {
$actor = $this->getActor();
return $this->isViewerAcceptingAuditor($object, $actor);
}
public function applyExternalEffects($object, $value) {
$status = PhabricatorAuditStatusConstants::ACCEPTED;
$actor = $this->getActor();
@ -54,7 +49,7 @@ final class DiffusionCommitAcceptTransaction
}
}
if ($this->isViewerAcceptingAuditor($object, $viewer)) {
if ($this->isViewerFullyAccepted($object, $viewer)) {
throw new Exception(
pht(
'You can not accept this commit because you have already '.

View file

@ -7,6 +7,10 @@ abstract class DiffusionCommitAuditTransaction
return DiffusionCommitEditEngine::ACTIONGROUP_AUDIT;
}
public function generateOldValue($object) {
return false;
}
protected function isViewerAnyAuditor(
PhabricatorRepositoryCommit $commit,
PhabricatorUser $viewer) {
@ -18,22 +22,23 @@ abstract class DiffusionCommitAuditTransaction
PhabricatorUser $viewer) {
// This omits various inactive states like "Resigned" and "Not Required".
$active = array(
PhabricatorAuditStatusConstants::AUDIT_REQUIRED,
PhabricatorAuditStatusConstants::CONCERNED,
PhabricatorAuditStatusConstants::ACCEPTED,
PhabricatorAuditStatusConstants::AUDIT_REQUESTED,
);
$active = array_fuse($active);
return $this->isViewerAuditStatusAmong(
$commit,
$viewer,
array(
PhabricatorAuditStatusConstants::AUDIT_REQUIRED,
PhabricatorAuditStatusConstants::CONCERNED,
PhabricatorAuditStatusConstants::ACCEPTED,
PhabricatorAuditStatusConstants::AUDIT_REQUESTED,
));
$viewer_status = $this->getViewerAuditStatus($commit, $viewer);
return isset($active[$viewer_status]);
}
protected function isViewerAcceptingAuditor(
protected function isViewerFullyAccepted(
PhabricatorRepositoryCommit $commit,
PhabricatorUser $viewer) {
return $this->isViewerAuditStatusAmong(
return $this->isViewerAuditStatusFullyAmong(
$commit,
$viewer,
array(
@ -41,10 +46,10 @@ abstract class DiffusionCommitAuditTransaction
));
}
protected function isViewerRejectingAuditor(
protected function isViewerFullyRejected(
PhabricatorRepositoryCommit $commit,
PhabricatorUser $viewer) {
return $this->isViewerAuditStatusAmong(
return $this->isViewerAuditStatusFullyAmong(
$commit,
$viewer,
array(
@ -71,7 +76,7 @@ abstract class DiffusionCommitAuditTransaction
return null;
}
protected function isViewerAuditStatusAmong(
protected function isViewerAuditStatusFullyAmong(
PhabricatorRepositoryCommit $commit,
PhabricatorUser $viewer,
array $status_list) {
@ -82,7 +87,20 @@ abstract class DiffusionCommitAuditTransaction
}
$status_map = array_fuse($status_list);
return isset($status_map[$status]);
foreach ($commit->getAudits() as $audit) {
if (!$commit->hasAuditAuthority($viewer, $audit)) {
continue;
}
$status = $audit->getAuditStatus();
if (isset($status_map[$status])) {
continue;
}
return false;
}
return true;
}
protected function applyAuditorEffect(

View file

@ -30,9 +30,11 @@ final class DiffusionCommitConcernTransaction
return pht('Raised Concern');
}
public function generateOldValue($object) {
$actor = $this->getActor();
return $this->isViewerRejectingAuditor($object, $actor);
public function applyInternalEffects($object, $value) {
// NOTE: We force the commit directly into "Concern Raised" so that we
// override a possible "Needs Verification" state.
$object->setAuditStatus(
PhabricatorAuditCommitStatusConstants::CONCERN_RAISED);
}
public function applyExternalEffects($object, $value) {
@ -50,11 +52,17 @@ final class DiffusionCommitConcernTransaction
'you did not author.'));
}
if ($this->isViewerRejectingAuditor($object, $viewer)) {
throw new Exception(
pht(
'You can not raise a concern with this commit because you have '.
'already raised a concern with it.'));
// Even if you've already raised a concern, you can raise again as long
// as the author requsted you verify.
$state_verify = PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION;
if ($this->isViewerFullyRejected($object, $viewer)) {
if ($object->getAuditStatus() != $state_verify) {
throw new Exception(
pht(
'You can not raise a concern with this commit because you have '.
'already raised a concern with it.'));
}
}
}

View file

@ -31,6 +31,8 @@ final class DiffusionCommitStateTransaction
return pht('This commit now requires audit.');
case PhabricatorAuditCommitStatusConstants::CONCERN_RAISED:
return pht('This commit now has outstanding concerns.');
case PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION:
return pht('This commit now requires verification by auditors.');
case PhabricatorAuditCommitStatusConstants::FULLY_AUDITED:
return pht('All concerns with this commit have now been addressed.');
}
@ -54,6 +56,10 @@ final class DiffusionCommitStateTransaction
return pht(
'%s now has outstanding concerns.',
$this->renderObject());
case PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION:
return pht(
'%s now requires verification by auditors.',
$this->renderObject());
case PhabricatorAuditCommitStatusConstants::FULLY_AUDITED:
return pht(
'All concerns with %s have now been addressed.',

View file

@ -0,0 +1,73 @@
<?php
final class DiffusionCommitVerifyTransaction
extends DiffusionCommitAuditTransaction {
const TRANSACTIONTYPE = 'diffusion.commit.verify';
const ACTIONKEY = 'verify';
protected function getCommitActionLabel() {
return pht('Request Verification');
}
protected function getCommitActionDescription() {
return pht(
'Auditors will be asked to verify that concerns have been addressed.');
}
protected function getCommitActionGroupKey() {
return DiffusionCommitEditEngine::ACTIONGROUP_COMMIT;
}
public function getIcon() {
return 'fa-refresh';
}
public function getColor() {
return 'indigo';
}
protected function getCommitActionOrder() {
return 600;
}
public function getActionName() {
return pht('Requested Verification');
}
public function applyInternalEffects($object, $value) {
$object->setAuditStatus(
PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION);
}
protected function validateAction($object, PhabricatorUser $viewer) {
if (!$this->isViewerCommitAuthor($object, $viewer)) {
throw new Exception(
pht(
'You can not request verification of this commit because you '.
'are not the author.'));
}
$status = $object->getAuditStatus();
if ($status != PhabricatorAuditCommitStatusConstants::CONCERN_RAISED) {
throw new Exception(
pht(
'You can not request verification of this commit because no '.
'auditors have raised conerns with it.'));
}
}
public function getTitle() {
return pht(
'%s requested verification of this commit.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s requested verification of %s.',
$this->renderAuthor(),
$this->renderObject());
}
}

View file

@ -327,8 +327,17 @@ final class PhabricatorRepositoryCommit
}
}
$current_status = $this->getAuditStatus();
$status_verify = PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION;
if ($any_concern) {
$status = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED;
if ($current_status == $status_verify) {
// If the change is in "Needs Verification", we keep it there as
// long as any auditors still have concerns.
$status = $status_verify;
} else {
$status = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED;
}
} else if ($any_accept) {
if ($any_need) {
$status = PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED;