@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

Prepare owners package audit rules to become more flexible

Summary:
Ref T13244. See PHI1055. (Earlier, see D20091 and PHI1047.) Previously, we expanded the Owners package autoreview rules from "Yes/No" to several "Review (Blocking) If Non-Owner Author Not Subscribed via Package" kinds of rules. The sky didn't fall and this feature didn't turn into "Herald-in-Owners", so I'm comfortable doing something similar to the "Audit" rules.

PHI1055 is a request for a way to configure slightly different audit behavior, and expanding the options seems like a good approach to satisfy the use case.

Prepare to add more options by moving everything into a class that defines all the behavior of different states, and converting the "0/1" boolean column to a text column.

Test Plan:
- Created several packages, some with and some without auditing.
- Inspected database for: package state; and associated transactions.
- Ran the migrations.
- Inspected database to confirm that state and transactions migrated correctly.
- Reviewed transaction logs.
- Created and edited packages and audit state.
- Viewed the "Package List" element in Diffusion.
- Pulled package information with `owners.search`, got sensible results.
- Edited package audit status with `owners.edit`.

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13244

Differential Revision: https://secure.phabricator.com/D20124

+181 -69
+2
resources/sql/autopatches/20190207.packages.01.state.sql
··· 1 + ALTER TABLE {$NAMESPACE}_owners.owners_package 2 + ADD auditingState VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT};
+2
resources/sql/autopatches/20190207.packages.02.migrate.sql
··· 1 + UPDATE {$NAMESPACE}_owners.owners_package 2 + SET auditingState = IF(auditingEnabled = 0, 'none', 'audit');
+2
resources/sql/autopatches/20190207.packages.03.drop.sql
··· 1 + ALTER TABLE {$NAMESPACE}_owners.owners_package 2 + DROP auditingEnabled;
+41
resources/sql/autopatches/20190207.packages.04.xactions.php
··· 1 + <?php 2 + 3 + $table = new PhabricatorOwnersPackageTransaction(); 4 + $conn = $table->establishConnection('w'); 5 + $iterator = new LiskRawMigrationIterator($conn, $table->getTableName()); 6 + 7 + // Migrate "Auditing State" transactions for Owners Packages from old values 8 + // (which were "0" or "1", as JSON integer literals, without quotes) to new 9 + // values (which are JSON strings, with quotes). 10 + 11 + foreach ($iterator as $row) { 12 + if ($row['transactionType'] !== 'owners.auditing') { 13 + continue; 14 + } 15 + 16 + $old_value = (int)$row['oldValue']; 17 + $new_value = (int)$row['newValue']; 18 + 19 + if (!$old_value) { 20 + $old_value = 'none'; 21 + } else { 22 + $old_value = 'audit'; 23 + } 24 + 25 + if (!$new_value) { 26 + $new_value = 'none'; 27 + } else { 28 + $new_value = 'audit'; 29 + } 30 + 31 + $old_value = phutil_json_encode($old_value); 32 + $new_value = phutil_json_encode($new_value); 33 + 34 + queryfx( 35 + $conn, 36 + 'UPDATE %R SET oldValue = %s, newValue = %s WHERE id = %d', 37 + $table, 38 + $old_value, 39 + $new_value, 40 + $row['id']); 41 + }
+2
src/__phutil_library_map__.php
··· 3668 3668 'PhabricatorOwnerPathQuery' => 'applications/owners/query/PhabricatorOwnerPathQuery.php', 3669 3669 'PhabricatorOwnersApplication' => 'applications/owners/application/PhabricatorOwnersApplication.php', 3670 3670 'PhabricatorOwnersArchiveController' => 'applications/owners/controller/PhabricatorOwnersArchiveController.php', 3671 + 'PhabricatorOwnersAuditRule' => 'applications/owners/constants/PhabricatorOwnersAuditRule.php', 3671 3672 'PhabricatorOwnersConfigOptions' => 'applications/owners/config/PhabricatorOwnersConfigOptions.php', 3672 3673 'PhabricatorOwnersConfiguredCustomField' => 'applications/owners/customfield/PhabricatorOwnersConfiguredCustomField.php', 3673 3674 'PhabricatorOwnersController' => 'applications/owners/controller/PhabricatorOwnersController.php', ··· 9613 9614 'PhabricatorOwnerPathQuery' => 'Phobject', 9614 9615 'PhabricatorOwnersApplication' => 'PhabricatorApplication', 9615 9616 'PhabricatorOwnersArchiveController' => 'PhabricatorOwnersController', 9617 + 'PhabricatorOwnersAuditRule' => 'Phobject', 9616 9618 'PhabricatorOwnersConfigOptions' => 'PhabricatorApplicationConfigOptions', 9617 9619 'PhabricatorOwnersConfiguredCustomField' => array( 9618 9620 'PhabricatorOwnersCustomField',
+2 -5
src/applications/diffusion/controller/DiffusionBrowseController.php
··· 566 566 $name = idx($spec, 'name', $auto); 567 567 $item->addIcon('fa-code', $name); 568 568 569 - if ($package->getAuditingEnabled()) { 570 - $item->addIcon('fa-check', pht('Auditing Enabled')); 571 - } else { 572 - $item->addIcon('fa-ban', pht('No Auditing')); 573 - } 569 + $rule = $package->newAuditingRule(); 570 + $item->addIcon($rule->getIconIcon(), $rule->getDisplayName()); 574 571 575 572 if ($package->isArchived()) { 576 573 $item->setDisabled(true);
+101
src/applications/owners/constants/PhabricatorOwnersAuditRule.php
··· 1 + <?php 2 + 3 + final class PhabricatorOwnersAuditRule 4 + extends Phobject { 5 + 6 + const AUDITING_NONE = 'none'; 7 + const AUDITING_AUDIT = 'audit'; 8 + 9 + private $key; 10 + private $spec; 11 + 12 + public static function newFromState($key) { 13 + $specs = self::newSpecifications(); 14 + $spec = idx($specs, $key, array()); 15 + 16 + $rule = new self(); 17 + $rule->key = $key; 18 + $rule->spec = $spec; 19 + 20 + return $rule; 21 + } 22 + 23 + public function getKey() { 24 + return $this->key; 25 + } 26 + 27 + public function getDisplayName() { 28 + return idx($this->spec, 'name', $this->key); 29 + } 30 + 31 + public function getIconIcon() { 32 + return idx($this->spec, 'icon.icon'); 33 + } 34 + 35 + public static function newSelectControlMap() { 36 + $specs = self::newSpecifications(); 37 + return ipull($specs, 'name'); 38 + } 39 + 40 + public static function getStorageValueFromAPIValue($value) { 41 + $specs = self::newSpecifications(); 42 + 43 + $map = array(); 44 + foreach ($specs as $key => $spec) { 45 + $deprecated = idx($spec, 'deprecated', array()); 46 + if (isset($deprecated[$value])) { 47 + return $key; 48 + } 49 + } 50 + 51 + return $value; 52 + } 53 + 54 + public static function getModernValueMap() { 55 + $specs = self::newSpecifications(); 56 + 57 + $map = array(); 58 + foreach ($specs as $key => $spec) { 59 + $map[$key] = pht('"%s"', $key); 60 + } 61 + 62 + return $map; 63 + } 64 + 65 + public static function getDeprecatedValueMap() { 66 + $specs = self::newSpecifications(); 67 + 68 + $map = array(); 69 + foreach ($specs as $key => $spec) { 70 + $deprecated_map = idx($spec, 'deprecated', array()); 71 + foreach ($deprecated_map as $deprecated_key => $label) { 72 + $map[$deprecated_key] = $label; 73 + } 74 + } 75 + 76 + return $map; 77 + } 78 + 79 + private static function newSpecifications() { 80 + return array( 81 + self::AUDITING_NONE => array( 82 + 'name' => pht('No Auditing'), 83 + 'icon.icon' => 'fa-ban', 84 + 'deprecated' => array( 85 + '' => pht('"" (empty string)'), 86 + '0' => '"0"', 87 + ), 88 + ), 89 + self::AUDITING_AUDIT => array( 90 + 'name' => pht('Audit Commits'), 91 + 'icon.icon' => 'fa-check', 92 + 'deprecated' => array( 93 + '1' => '"1"', 94 + ), 95 + ), 96 + ); 97 + } 98 + 99 + 100 + 101 + }
+2 -6
src/applications/owners/controller/PhabricatorOwnersDetailController.php
··· 194 194 $name = idx($spec, 'name', $auto); 195 195 $view->addProperty(pht('Auto Review'), $name); 196 196 197 - if ($package->getAuditingEnabled()) { 198 - $auditing = pht('Enabled'); 199 - } else { 200 - $auditing = pht('Disabled'); 201 - } 202 - $view->addProperty(pht('Auditing'), $auditing); 197 + $rule = $package->newAuditingRule(); 198 + $view->addProperty(pht('Auditing'), $rule->getDisplayName()); 203 199 204 200 $ignored = $package->getIgnoredPathAttributes(); 205 201 $ignored = array_keys($ignored);
+1 -5
src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php
··· 141 141 PhabricatorOwnersPackageAuditingTransaction::TRANSACTIONTYPE) 142 142 ->setIsCopyable(true) 143 143 ->setValue($object->getAuditingState()) 144 - ->setOptions( 145 - array( 146 - PhabricatorOwnersPackage::AUDITING_NONE => pht('No Auditing'), 147 - PhabricatorOwnersPackage::AUDITING_AUDIT => pht('Audit Commits'), 148 - )), 144 + ->setOptions(PhabricatorOwnersAuditRule::newSelectControlMap()), 149 145 id(new PhabricatorRemarkupEditField()) 150 146 ->setKey('description') 151 147 ->setLabel(pht('Description'))
+8 -20
src/applications/owners/storage/PhabricatorOwnersPackage.php
··· 13 13 PhabricatorNgramsInterface { 14 14 15 15 protected $name; 16 - protected $auditingEnabled; 17 16 protected $autoReview; 18 17 protected $description; 19 18 protected $status; ··· 21 20 protected $editPolicy; 22 21 protected $dominion; 23 22 protected $properties = array(); 23 + protected $auditingState; 24 24 25 25 private $paths = self::ATTACHABLE; 26 26 private $owners = self::ATTACHABLE; ··· 38 38 const AUTOREVIEW_BLOCK = 'block'; 39 39 const AUTOREVIEW_BLOCK_ALWAYS = 'block-always'; 40 40 41 - const AUDITING_NONE = 'none'; 42 - const AUDITING_AUDIT = 'audit'; 43 - 44 41 const DOMINION_STRONG = 'strong'; 45 42 const DOMINION_WEAK = 'weak'; 46 43 ··· 58 55 PhabricatorOwnersDefaultEditCapability::CAPABILITY); 59 56 60 57 return id(new PhabricatorOwnersPackage()) 61 - ->setAuditingEnabled(0) 58 + ->setAuditingState(PhabricatorOwnersAuditRule::AUDITING_NONE) 62 59 ->setAutoReview(self::AUTOREVIEW_NONE) 63 60 ->setDominion(self::DOMINION_STRONG) 64 61 ->setViewPolicy($view_policy) ··· 129 126 self::CONFIG_COLUMN_SCHEMA => array( 130 127 'name' => 'sort', 131 128 'description' => 'text', 132 - 'auditingEnabled' => 'bool', 129 + 'auditingState' => 'text32', 133 130 'status' => 'text32', 134 131 'autoReview' => 'text32', 135 132 'dominion' => 'text32', ··· 567 564 return '/owners/package/'.$this->getID().'/'; 568 565 } 569 566 570 - public function getAuditingState() { 571 - if ($this->getAuditingEnabled()) { 572 - return self::AUDITING_AUDIT; 573 - } else { 574 - return self::AUDITING_NONE; 575 - } 567 + public function newAuditingRule() { 568 + return PhabricatorOwnersAuditRule::newFromState($this->getAuditingState()); 576 569 } 577 570 578 571 /* -( PhabricatorPolicyInterface )----------------------------------------- */ ··· 731 724 'label' => $review_label, 732 725 ); 733 726 734 - $audit_value = $this->getAuditingState(); 735 - if ($this->getAuditingEnabled()) { 736 - $audit_label = pht('Auditing Enabled'); 737 - } else { 738 - $audit_label = pht('No Auditing'); 739 - } 727 + $audit_rule = $this->newAuditingRule(); 740 728 741 729 $audit = array( 742 - 'value' => $audit_value, 743 - 'label' => $audit_label, 730 + 'value' => $audit_rule->getKey(), 731 + 'label' => $audit_rule->getDisplayName(), 744 732 ); 745 733 746 734 $dominion_value = $this->getDominion();
+16 -32
src/applications/owners/xaction/PhabricatorOwnersPackageAuditingTransaction.php
··· 6 6 const TRANSACTIONTYPE = 'owners.auditing'; 7 7 8 8 public function generateOldValue($object) { 9 - return (int)$object->getAuditingEnabled(); 9 + return $object->getAuditingState(); 10 10 } 11 11 12 12 public function generateNewValue($object, $value) { 13 - switch ($value) { 14 - case PhabricatorOwnersPackage::AUDITING_AUDIT: 15 - return 1; 16 - case '1': 17 - // TODO: Remove, deprecated. 18 - return 1; 19 - default: 20 - return 0; 21 - } 13 + return PhabricatorOwnersAuditRule::getStorageValueFromAPIValue($value); 22 14 } 23 15 24 16 public function applyInternalEffects($object, $value) { 25 - $object->setAuditingEnabled($value); 17 + $object->setAuditingState($value); 26 18 } 27 19 28 20 public function getTitle() { 29 - if ($this->getNewValue()) { 30 - return pht( 31 - '%s enabled auditing for this package.', 32 - $this->renderAuthor()); 33 - } else { 34 - return pht( 35 - '%s disabled auditing for this package.', 36 - $this->renderAuthor()); 37 - } 21 + $old_value = $this->getOldValue(); 22 + $new_value = $this->getNewValue(); 23 + 24 + $old_rule = PhabricatorOwnersAuditRule::newFromState($old_value); 25 + $new_rule = PhabricatorOwnersAuditRule::newFromState($new_value); 26 + 27 + return pht( 28 + '%s changed the audit rule for this package from %s to %s.', 29 + $this->renderAuthor(), 30 + $this->renderValue($old_rule->getDisplayName()), 31 + $this->renderValue($new_rule->getDisplayName())); 38 32 } 39 33 40 34 public function validateTransactions($object, array $xactions) { ··· 43 37 // See PHI1047. This transaction type accepted some weird stuff. Continue 44 38 // supporting it for now, but move toward sensible consistency. 45 39 46 - $modern_options = array( 47 - PhabricatorOwnersPackage::AUDITING_NONE => 48 - sprintf('"%s"', PhabricatorOwnersPackage::AUDITING_NONE), 49 - PhabricatorOwnersPackage::AUDITING_AUDIT => 50 - sprintf('"%s"', PhabricatorOwnersPackage::AUDITING_AUDIT), 51 - ); 52 - 53 - $deprecated_options = array( 54 - '0' => '"0"', 55 - '1' => '"1"', 56 - '' => pht('"" (empty string)'), 57 - ); 40 + $modern_options = PhabricatorOwnersAuditRule::getModernValueMap(); 41 + $deprecated_options = PhabricatorOwnersAuditRule::getDeprecatedValueMap(); 58 42 59 43 foreach ($xactions as $xaction) { 60 44 $new_value = $xaction->getNewValue();
+2 -1
src/applications/repository/worker/PhabricatorRepositoryCommitOwnersWorker.php
··· 133 133 $revision) { 134 134 135 135 // Don't trigger an audit if auditing isn't enabled for the package. 136 - if (!$package->getAuditingEnabled()) { 136 + $rule = $package->newAuditingRule(); 137 + if ($rule->getKey() === PhabricatorOwnersAuditRule::AUDITING_NONE) { 137 138 return false; 138 139 } 139 140