diff --git a/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php b/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php index 5473ec6e00..6ce996a002 100644 --- a/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php +++ b/src/applications/owners/conduit/OwnersQueryConduitAPIMethod.php @@ -1,162 +1,161 @@ 'optional string', 'projectOwner' => 'optional string', 'userAffiliated' => 'optional string', 'repositoryCallsign' => 'optional string', 'path' => 'optional string', ); } protected function defineReturnType() { return 'dict dict of package info>'; } protected function defineErrorTypes() { return array( 'ERR-INVALID-USAGE' => pht( 'Provide one of a single owner phid (user/project), a single '. 'affiliated user phid (user), or a repository/path.'), 'ERR-INVALID-PARAMETER' => pht('Parameter should be a phid.'), 'ERR_REP_NOT_FOUND' => pht('The repository callsign is not recognized.'), ); } protected static function queryAll() { return id(new PhabricatorOwnersPackage())->loadAll(); } protected static function queryByOwner($owner) { $is_valid_phid = phid_get_type($owner) == PhabricatorPeopleUserPHIDType::TYPECONST || phid_get_type($owner) == PhabricatorProjectProjectPHIDType::TYPECONST; if (!$is_valid_phid) { throw id(new ConduitException('ERR-INVALID-PARAMETER')) ->setErrorDescription( pht( 'Expected user/project PHID for owner, got %s.', $owner)); } $owners = id(new PhabricatorOwnersOwner())->loadAllWhere( 'userPHID = %s', $owner); $package_ids = mpull($owners, 'getPackageID'); $packages = array(); foreach ($package_ids as $id) { $packages[] = id(new PhabricatorOwnersPackage())->load($id); } return $packages; } private static function queryByPath( PhabricatorUser $viewer, $repo_callsign, $path) { $repository = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) ->withCallsigns(array($repo_callsign)) ->executeOne(); if (!$repository) { throw id(new ConduitException('ERR_REP_NOT_FOUND')) ->setErrorDescription( pht( 'Repository callsign %s not recognized', $repo_callsign)); } if ($path == null) { return PhabricatorOwnersPackage::loadPackagesForRepository($repository); } else { return PhabricatorOwnersPackage::loadOwningPackages( $repository, $path); } } public static function buildPackageInformationDictionaries($packages) { assert_instances_of($packages, 'PhabricatorOwnersPackage'); $result = array(); foreach ($packages as $package) { $p_owners = $package->loadOwners(); $p_paths = $package->loadPaths(); $owners = array_values(mpull($p_owners, 'getUserPHID')); $paths = array(); foreach ($p_paths as $p) { $paths[] = array($p->getRepositoryPHID(), $p->getPath()); } $result[$package->getPHID()] = array( 'phid' => $package->getPHID(), 'name' => $package->getName(), 'description' => $package->getDescription(), - 'primaryOwner' => $package->getPrimaryOwnerPHID(), 'owners' => $owners, 'paths' => $paths, ); } return $result; } protected function execute(ConduitAPIRequest $request) { $is_owner_query = ($request->getValue('userOwner') || $request->getValue('projectOwner')) ? 1 : 0; $is_affiliated_query = $request->getValue('userAffiliated') ? 1 : 0; $repo = $request->getValue('repositoryCallsign'); $path = $request->getValue('path'); $is_path_query = $repo ? 1 : 0; if ($is_owner_query + $is_path_query + $is_affiliated_query === 0) { // if no search terms are provided, return everything $packages = self::queryAll(); } else if ($is_owner_query + $is_path_query + $is_affiliated_query > 1) { // otherwise, exactly one of these should be provided throw new ConduitException('ERR-INVALID-USAGE'); } if ($is_affiliated_query) { $query = id(new PhabricatorOwnersPackageQuery()) ->setViewer($request->getUser()); $query->withOwnerPHIDs(array($request->getValue('userAffiliated'))); $packages = $query->execute(); } else if ($is_owner_query) { $owner = nonempty( $request->getValue('userOwner'), $request->getValue('projectOwner')); $packages = self::queryByOwner($owner); } else if ($is_path_query) { $packages = self::queryByPath($request->getUser(), $repo, $path); } return self::buildPackageInformationDictionaries($packages); } } diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index c3f0ec42f4..168d7cd07e 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -1,289 +1,281 @@ getViewer(); $package = id(new PhabricatorOwnersPackageQuery()) ->setViewer($viewer) ->withIDs(array($request->getURIData('id'))) ->needPaths(true) ->executeOne(); if (!$package) { return new Aphront404Response(); } $paths = $package->getPaths(); $repository_phids = array(); foreach ($paths as $path) { $repository_phids[$path->getRepositoryPHID()] = true; } if ($repository_phids) { $repositories = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) ->withPHIDs(array_keys($repository_phids)) ->execute(); $repositories = mpull($repositories, null, 'getPHID'); } else { $repositories = array(); } $actions = $this->buildPackageActionView($package); $properties = $this->buildPackagePropertyView($package); $properties->setActionList($actions); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($package->getName()) ->setPolicyObject($package); $panel = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); $commit_views = array(); $commit_uri = id(new PhutilURI('/audit/')) ->setQueryParams( array( 'auditorPHIDs' => $package->getPHID(), )); $attention_commits = id(new DiffusionCommitQuery()) ->setViewer($request->getUser()) ->withAuditorPHIDs(array($package->getPHID())) ->withAuditStatus(DiffusionCommitQuery::AUDIT_STATUS_CONCERN) ->needCommitData(true) ->setLimit(10) ->execute(); if ($attention_commits) { $view = id(new PhabricatorAuditListView()) ->setUser($viewer) ->setCommits($attention_commits); $commit_views[] = array( 'view' => $view, 'header' => pht('Commits in this Package that Need Attention'), 'button' => id(new PHUIButtonView()) ->setTag('a') ->setHref($commit_uri->alter('status', 'open')) ->setText(pht('View All Problem Commits')), ); } $all_commits = id(new DiffusionCommitQuery()) ->setViewer($request->getUser()) ->withAuditorPHIDs(array($package->getPHID())) ->needCommitData(true) ->setLimit(100) ->execute(); $view = id(new PhabricatorAuditListView()) ->setUser($viewer) ->setCommits($all_commits) ->setNoDataString(pht('No commits in this package.')); $commit_views[] = array( 'view' => $view, 'header' => pht('Recent Commits in Package'), 'button' => id(new PHUIButtonView()) ->setTag('a') ->setHref($commit_uri) ->setText(pht('View All Package Commits')), ); $phids = array(); foreach ($commit_views as $commit_view) { $phids[] = $commit_view['view']->getRequiredHandlePHIDs(); } $phids = array_mergev($phids); $handles = $this->loadViewerHandles($phids); $commit_panels = array(); foreach ($commit_views as $commit_view) { $commit_panel = new PHUIObjectBoxView(); $header = new PHUIHeaderView(); $header->setHeader($commit_view['header']); if (isset($commit_view['button'])) { $header->addActionLink($commit_view['button']); } $commit_view['view']->setHandles($handles); $commit_panel->setHeader($header); $commit_panel->appendChild($commit_view['view']); $commit_panels[] = $commit_panel; } $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($package->getName()); $timeline = $this->buildTransactionTimeline( $package, new PhabricatorOwnersPackageTransactionQuery()); $timeline->setShouldTerminate(true); return $this->buildApplicationPage( array( $crumbs, $panel, $this->renderPathsTable($paths, $repositories), $commit_panels, $timeline, ), array( 'title' => $package->getName(), )); } private function buildPackagePropertyView(PhabricatorOwnersPackage $package) { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer); - $primary_phid = $package->getPrimaryOwnerPHID(); - if ($primary_phid) { - $primary_owner = $viewer->renderHandle($primary_phid); - } else { - $primary_owner = phutil_tag('em', array(), pht('None')); - } - $view->addProperty(pht('Primary Owner'), $primary_owner); - // TODO: needOwners() this on the Query. $owners = $package->loadOwners(); if ($owners) { $owner_list = $viewer->renderHandleList(mpull($owners, 'getUserPHID')); } else { $owner_list = phutil_tag('em', array(), pht('None')); } $view->addProperty(pht('Owners'), $owner_list); if ($package->getAuditingEnabled()) { $auditing = pht('Enabled'); } else { $auditing = pht('Disabled'); } $view->addProperty(pht('Auditing'), $auditing); $description = $package->getDescription(); if (strlen($description)) { $view->addSectionHeader(pht('Description')); $view->addTextContent( $output = PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff())->setContent($description), 'default', $viewer)); } return $view; } private function buildPackageActionView(PhabricatorOwnersPackage $package) { $viewer = $this->getViewer(); // TODO: Implement this capability. $can_edit = true; $id = $package->getID(); $edit_uri = $this->getApplicationURI("/edit/{$id}/"); $paths_uri = $this->getApplicationURI("/paths/{$id}/"); $view = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObject($package) ->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Package')) ->setIcon('fa-pencil') ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit) ->setHref($edit_uri)) ->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Paths')) ->setIcon('fa-folder-open') ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit) ->setHref($paths_uri)); return $view; } private function renderPathsTable(array $paths, array $repositories) { $viewer = $this->getViewer(); $rows = array(); foreach ($paths as $path) { $repo = idx($repositories, $path->getRepositoryPHID()); if (!$repo) { continue; } $href = DiffusionRequest::generateDiffusionURI( array( 'callsign' => $repo->getCallsign(), 'branch' => $repo->getDefaultBranch(), 'path' => $path->getPath(), 'action' => 'browse', )); $path_link = phutil_tag( 'a', array( 'href' => (string)$href, ), $path->getPath()); $rows[] = array( ($path->getExcluded() ? '-' : '+'), $repo->getName(), $path_link, ); } $info = null; if (!$paths) { $info = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_WARNING) ->setErrors( array( pht( 'This package does not contain any paths yet. Use '. '"Edit Paths" to add some.'), )); } $table = id(new AphrontTableView($rows)) ->setHeaders( array( null, pht('Repository'), pht('Path'), )) ->setColumnClasses( array( null, null, 'wide', )); $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Paths')) ->setTable($table); if ($info) { $box->setInfoView($info); } return $box; } } diff --git a/src/applications/owners/controller/PhabricatorOwnersEditController.php b/src/applications/owners/controller/PhabricatorOwnersEditController.php index 9f1987814d..618f99456d 100644 --- a/src/applications/owners/controller/PhabricatorOwnersEditController.php +++ b/src/applications/owners/controller/PhabricatorOwnersEditController.php @@ -1,199 +1,171 @@ getUser(); $id = $request->getURIData('id'); if ($id) { $package = id(new PhabricatorOwnersPackageQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, // TODO: Support this capability. // PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$package) { return new Aphront404Response(); } $is_new = false; } else { $package = PhabricatorOwnersPackage::initializeNewPackage($viewer); $is_new = true; } $e_name = true; - $e_primary = true; $v_name = $package->getName(); - $v_primary = $package->getPrimaryOwnerPHID(); // TODO: Pull these off needOwners() on the Query. $v_owners = mpull($package->loadOwners(), 'getUserPHID'); $v_auditing = $package->getAuditingEnabled(); $v_description = $package->getDescription(); $errors = array(); if ($request->isFormPost()) { $xactions = array(); $v_name = $request->getStr('name'); - $v_primary = head($request->getArr('primary')); $v_owners = $request->getArr('owners'); $v_auditing = ($request->getStr('auditing') == 'enabled'); $v_description = $request->getStr('description'); - if ($v_primary) { - $v_owners[] = $v_primary; - $v_owners = array_unique($v_owners); - } - $type_name = PhabricatorOwnersPackageTransaction::TYPE_NAME; - $type_primary = PhabricatorOwnersPackageTransaction::TYPE_PRIMARY; $type_owners = PhabricatorOwnersPackageTransaction::TYPE_OWNERS; $type_auditing = PhabricatorOwnersPackageTransaction::TYPE_AUDITING; $type_description = PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION; $xactions[] = id(new PhabricatorOwnersPackageTransaction()) ->setTransactionType($type_name) ->setNewValue($v_name); - $xactions[] = id(new PhabricatorOwnersPackageTransaction()) - ->setTransactionType($type_primary) - ->setNewValue($v_primary); - $xactions[] = id(new PhabricatorOwnersPackageTransaction()) ->setTransactionType($type_owners) ->setNewValue($v_owners); $xactions[] = id(new PhabricatorOwnersPackageTransaction()) ->setTransactionType($type_auditing) ->setNewValue($v_auditing); $xactions[] = id(new PhabricatorOwnersPackageTransaction()) ->setTransactionType($type_description) ->setNewValue($v_description); $editor = id(new PhabricatorOwnersPackageTransactionEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); try { $editor->applyTransactions($package, $xactions); $id = $package->getID(); if ($is_new) { $next_uri = '/owners/paths/'.$id.'/'; } else { $next_uri = '/owners/package/'.$id.'/'; } return id(new AphrontRedirectResponse())->setURI($next_uri); } catch (AphrontDuplicateKeyQueryException $ex) { $e_name = pht('Duplicate'); $errors[] = pht('Package name must be unique.'); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $e_name = $ex->getShortMessage($type_name); - $e_primary = $ex->getShortMessage($type_primary); } } - if ($v_primary) { - $value_primary_owner = array($v_primary); - } else { - $value_primary_owner = array(); - } - if ($is_new) { $cancel_uri = '/owners/'; $title = pht('New Package'); $button_text = pht('Continue'); } else { $cancel_uri = '/owners/package/'.$package->getID().'/'; $title = pht('Edit Package'); $button_text = pht('Save Package'); } $form = id(new AphrontFormView()) ->setUser($viewer) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Name')) ->setName('name') ->setValue($v_name) ->setError($e_name)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorProjectOrUserDatasource()) - ->setLabel(pht('Primary Owner')) - ->setName('primary') - ->setLimit(1) - ->setValue($value_primary_owner) - ->setError($e_primary)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setDatasource(new PhabricatorProjectOrUserDatasource()) ->setLabel(pht('Owners')) ->setName('owners') ->setValue($v_owners)) ->appendChild( id(new AphrontFormSelectControl()) ->setName('auditing') ->setLabel(pht('Auditing')) ->setCaption( pht( 'With auditing enabled, all future commits that touch '. 'this package will be reviewed to make sure an owner '. 'of the package is involved and the commit message has '. 'a valid revision, reviewed by, and author.')) ->setOptions( array( 'disabled' => pht('Disabled'), 'enabled' => pht('Enabled'), )) ->setValue(($v_auditing ? 'enabled' : 'disabled'))) ->appendChild( id(new PhabricatorRemarkupControl()) ->setUser($viewer) ->setLabel(pht('Description')) ->setName('description') ->setValue($v_description)) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($button_text)); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setFormErrors($errors) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); if ($package->getID()) { $crumbs->addTextCrumb( $package->getName(), $this->getApplicationURI('package/'.$package->getID().'/')); $crumbs->addTextCrumb(pht('Edit')); } else { $crumbs->addTextCrumb(pht('New Package')); } return $this->buildApplicationPage( array( $crumbs, $form_box, ), array( 'title' => $title, )); } } diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php index 311cf642a0..79cce9e9af 100644 --- a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php +++ b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php @@ -1,288 +1,263 @@ getTransactionType()) { case PhabricatorOwnersPackageTransaction::TYPE_NAME: return $object->getName(); - case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: - return $object->getPrimaryOwnerPHID(); case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: // TODO: needOwners() this on the Query. $phids = mpull($object->loadOwners(), 'getUserPHID'); $phids = array_values($phids); return $phids; case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: return (int)$object->getAuditingEnabled(); case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: return $object->getDescription(); case PhabricatorOwnersPackageTransaction::TYPE_PATHS: $paths = $object->getPaths(); return mpull($paths, 'getRef'); } } protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorOwnersPackageTransaction::TYPE_NAME: - case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: case PhabricatorOwnersPackageTransaction::TYPE_PATHS: return $xaction->getNewValue(); case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: return (int)$xaction->getNewValue(); case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: $phids = $xaction->getNewValue(); $phids = array_unique($phids); $phids = array_values($phids); return $phids; } } protected function transactionHasEffect( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorOwnersPackageTransaction::TYPE_PATHS: $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); list($rem, $add) = $diffs; return ($rem || $add); } return parent::transactionHasEffect($object, $xaction); } protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorOwnersPackageTransaction::TYPE_NAME: $object->setName($xaction->getNewValue()); return; - case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: - $object->setPrimaryOwnerPHID($xaction->getNewValue()); - return; case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: $object->setDescription($xaction->getNewValue()); return; case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: $object->setAuditingEnabled($xaction->getNewValue()); return; case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: case PhabricatorOwnersPackageTransaction::TYPE_PATHS: return; } return parent::applyCustomInternalTransaction($object, $xaction); } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorOwnersPackageTransaction::TYPE_NAME: - case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: return; case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); // TODO: needOwners this $owners = $object->loadOwners(); $owners = mpull($owners, null, 'getUserPHID'); $rem = array_diff($old, $new); foreach ($rem as $phid) { if (isset($owners[$phid])) { $owners[$phid]->delete(); unset($owners[$phid]); } } $add = array_diff($new, $old); foreach ($add as $phid) { $owners[$phid] = id(new PhabricatorOwnersOwner()) ->setPackageID($object->getID()) ->setUserPHID($phid) ->save(); } // TODO: Attach owners here return; case PhabricatorOwnersPackageTransaction::TYPE_PATHS: $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); $paths = $object->getPaths(); $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); list($rem, $add) = $diffs; $set = PhabricatorOwnersPath::getSetFromTransactionValue($rem); foreach ($paths as $path) { $ref = $path->getRef(); if (PhabricatorOwnersPath::isRefInSet($ref, $set)) { $path->delete(); } } foreach ($add as $ref) { $path = PhabricatorOwnersPath::newFromRef($ref) ->setPackageID($object->getID()) ->save(); } return; } return parent::applyCustomExternalTransaction($object, $xaction); } protected function validateTransaction( PhabricatorLiskDAO $object, $type, array $xactions) { $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { case PhabricatorOwnersPackageTransaction::TYPE_NAME: $missing = $this->validateIsEmptyTextField( $object->getName(), $xactions); if ($missing) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), pht('Package name is required.'), nonempty(last($xactions), null)); $error->setIsMissingFieldError(true); $errors[] = $error; } break; - case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: - $missing = $this->validateIsEmptyTextField( - $object->getPrimaryOwnerPHID(), - $xactions); - - if ($missing) { - $error = new PhabricatorApplicationTransactionValidationError( - $type, - pht('Required'), - pht('Packages must have a primary owner.'), - nonempty(last($xactions), null)); - - $error->setIsMissingFieldError(true); - $errors[] = $error; - } - break; } return $errors; } protected function extractFilePHIDsFromCustomTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: return array($xaction->getNewValue()); } return parent::extractFilePHIDsFromCustomTransaction($object, $xaction); } protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { return true; } protected function getMailSubjectPrefix() { return PhabricatorEnv::getEnvConfig('metamta.package.subject-prefix'); } protected function getMailTo(PhabricatorLiskDAO $object) { return array( - $object->getPrimaryOwnerPHID(), $this->requireActor()->getPHID(), ); } protected function getMailCC(PhabricatorLiskDAO $object) { // TODO: needOwners() this return mpull($object->loadOwners(), 'getUserPHID'); } protected function buildReplyHandler(PhabricatorLiskDAO $object) { return id(new OwnersPackageReplyHandler()) ->setMailReceiver($object); } protected function buildMailTemplate(PhabricatorLiskDAO $object) { $id = $object->getID(); $name = $object->getName(); return id(new PhabricatorMetaMTAMail()) ->setSubject($name) ->addHeader('Thread-Topic', $object->getPHID()); } protected function buildMailBody( PhabricatorLiskDAO $object, array $xactions) { $body = parent::buildMailBody($object, $xactions); $detail_uri = PhabricatorEnv::getProductionURI( '/owners/package/'.$object->getID().'/'); $body->addLinkSection( pht('PACKAGE DETAIL'), $detail_uri); return $body; } } diff --git a/src/applications/owners/storage/PhabricatorOwnersPackage.php b/src/applications/owners/storage/PhabricatorOwnersPackage.php index 82c8ca9074..bfba5856ca 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackage.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackage.php @@ -1,275 +1,274 @@ setAuditingEnabled(0) - ->setPrimaryOwnerPHID($actor->getPHID()); + ->setAuditingEnabled(0); } public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, ); } public function getPolicy($capability) { return PhabricatorPolicies::POLICY_USER; } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return false; } public function describeAutomaticCapability($capability) { return null; } protected function getConfiguration() { return array( // This information is better available from the history table. self::CONFIG_TIMESTAMPS => false, self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'text128', 'originalName' => 'text255', 'description' => 'text', 'primaryOwnerPHID' => 'phid?', 'auditingEnabled' => 'bool', 'mailKey' => 'bytes20', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, 'phid' => array( 'columns' => array('phid'), 'unique' => true, ), 'name' => array( 'columns' => array('name'), 'unique' => true, ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorOwnersPackagePHIDType::TYPECONST); } public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); } return parent::save(); } public function setName($name) { $this->name = $name; if (!$this->getID()) { $this->originalName = $name; } return $this; } public function loadOwners() { if (!$this->getID()) { return array(); } return id(new PhabricatorOwnersOwner())->loadAllWhere( 'packageID = %d', $this->getID()); } public function loadPaths() { if (!$this->getID()) { return array(); } return id(new PhabricatorOwnersPath())->loadAllWhere( 'packageID = %d', $this->getID()); } public static function loadAffectedPackages( PhabricatorRepository $repository, array $paths) { if (!$paths) { return array(); } return self::loadPackagesForPaths($repository, $paths); } public static function loadOwningPackages($repository, $path) { if (empty($path)) { return array(); } return self::loadPackagesForPaths($repository, array($path), 1); } private static function loadPackagesForPaths( PhabricatorRepository $repository, array $paths, $limit = 0) { $fragments = array(); foreach ($paths as $path) { foreach (self::splitPath($path) as $fragment) { $fragments[$fragment][$path] = true; } } $package = new PhabricatorOwnersPackage(); $path = new PhabricatorOwnersPath(); $conn = $package->establishConnection('r'); $repository_clause = qsprintf( $conn, 'AND p.repositoryPHID = %s', $repository->getPHID()); // NOTE: The list of $paths may be very large if we're coming from // the OwnersWorker and processing, e.g., an SVN commit which created a new // branch. Break it apart so that it will fit within 'max_allowed_packet', // and then merge results in PHP. $rows = array(); foreach (array_chunk(array_keys($fragments), 128) as $chunk) { $rows[] = queryfx_all( $conn, 'SELECT pkg.id, p.excluded, p.path FROM %T pkg JOIN %T p ON p.packageID = pkg.id WHERE p.path IN (%Ls) %Q', $package->getTableName(), $path->getTableName(), $chunk, $repository_clause); } $rows = array_mergev($rows); $ids = self::findLongestPathsPerPackage($rows, $fragments); if (!$ids) { return array(); } arsort($ids); if ($limit) { $ids = array_slice($ids, 0, $limit, $preserve_keys = true); } $ids = array_keys($ids); $packages = $package->loadAllWhere('id in (%Ld)', $ids); $packages = array_select_keys($packages, $ids); return $packages; } public static function loadPackagesForRepository($repository) { $package = new PhabricatorOwnersPackage(); $ids = ipull( queryfx_all( $package->establishConnection('r'), 'SELECT DISTINCT packageID FROM %T WHERE repositoryPHID = %s', id(new PhabricatorOwnersPath())->getTableName(), $repository->getPHID()), 'packageID'); return $package->loadAllWhere('id in (%Ld)', $ids); } public static function findLongestPathsPerPackage(array $rows, array $paths) { $ids = array(); foreach (igroup($rows, 'id') as $id => $package_paths) { $relevant_paths = array_select_keys( $paths, ipull($package_paths, 'path')); // For every package, remove all excluded paths. $remove = array(); foreach ($package_paths as $package_path) { if ($package_path['excluded']) { $remove += idx($relevant_paths, $package_path['path'], array()); unset($relevant_paths[$package_path['path']]); } } if ($remove) { foreach ($relevant_paths as $fragment => $fragment_paths) { $relevant_paths[$fragment] = array_diff_key($fragment_paths, $remove); } } $relevant_paths = array_filter($relevant_paths); if ($relevant_paths) { $ids[$id] = max(array_map('strlen', array_keys($relevant_paths))); } } return $ids; } public static function splitPath($path) { $result = array('/'); $trailing_slash = preg_match('@/$@', $path) ? '/' : ''; $path = trim($path, '/'); $parts = explode('/', $path); while (count($parts)) { $result[] = '/'.implode('/', $parts).$trailing_slash; $trailing_slash = '/'; array_pop($parts); } return $result; } public function attachPaths(array $paths) { assert_instances_of($paths, 'PhabricatorOwnersPath'); $this->paths = $paths; return $this; } public function getPaths() { return $this->assertAttached($this->paths); } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new PhabricatorOwnersPackageTransactionEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new PhabricatorOwnersPackageTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } } diff --git a/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php index 3686125887..bf676f941b 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php @@ -1,211 +1,200 @@ getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { - case self::TYPE_PRIMARY: - if ($old) { - $phids[] = $old; - } - if ($new) { - $phids[] = $new; - } - break; case self::TYPE_OWNERS: $add = array_diff($new, $old); foreach ($add as $phid) { $phids[] = $phid; } $rem = array_diff($old, $new); foreach ($rem as $phid) { $phids[] = $phid; } break; } return $phids; } public function shouldHide() { $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_DESCRIPTION: return ($old === null); + case self::TYPE_PRIMARY: + // TODO: Eventually, remove these transactions entirely. + return true; } } public function getTitle() { $old = $this->getOldValue(); $new = $this->getNewValue(); $author_phid = $this->getAuthorPHID(); switch ($this->getTransactionType()) { case self::TYPE_NAME: if ($old === null) { return pht( '%s created this package.', $this->renderHandleLink($author_phid)); } else { return pht( '%s renamed this package from "%s" to "%s".', $this->renderHandleLink($author_phid), $old, $new); } - case self::TYPE_PRIMARY: - return pht( - '%s changed the primary owner for this package from %s to %s.', - $this->renderHandleLink($author_phid), - $this->renderHandleLink($old), - $this->renderHandleLink($new)); case self::TYPE_OWNERS: $add = array_diff($new, $old); $rem = array_diff($old, $new); if ($add && !$rem) { return pht( '%s added %s owner(s): %s.', $this->renderHandleLink($author_phid), count($add), $this->renderHandleList($add)); } else if ($rem && !$add) { return pht( '%s removed %s owner(s): %s.', $this->renderHandleLink($author_phid), count($rem), $this->renderHandleList($rem)); } else { return pht( '%s changed %s package owner(s), added %s: %s; removed %s: %s.', $this->renderHandleLink($author_phid), count($add) + count($rem), count($add), $this->renderHandleList($add), count($rem), $this->renderHandleList($rem)); } case self::TYPE_AUDITING: if ($new) { return pht( '%s enabled auditing for this package.', $this->renderHandleLink($author_phid)); } else { return pht( '%s disabled auditing for this package.', $this->renderHandleLink($author_phid)); } case self::TYPE_DESCRIPTION: return pht( '%s updated the description for this package.', $this->renderHandleLink($author_phid)); case self::TYPE_PATHS: // TODO: Flesh this out. return pht( '%s updated paths for this package.', $this->renderHandleLink($author_phid)); } return parent::getTitle(); } public function hasChangeDetails() { switch ($this->getTransactionType()) { case self::TYPE_DESCRIPTION: return ($this->getOldValue() !== null); case self::TYPE_PATHS: return true; } return parent::hasChangeDetails(); } public function renderChangeDetails(PhabricatorUser $viewer) { switch ($this->getTransactionType()) { case self::TYPE_DESCRIPTION: $old = $this->getOldValue(); $new = $this->getNewValue(); return $this->renderTextCorpusChangeDetails( $viewer, $old, $new); case self::TYPE_PATHS: $old = $this->getOldValue(); $new = $this->getNewValue(); $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); list($rem, $add) = $diffs; $rows = array(); foreach ($rem as $ref) { $rows[] = array( 'class' => 'diff-removed', 'change' => '-', ) + $ref; } foreach ($add as $ref) { $rows[] = array( 'class' => 'diff-added', 'change' => '+', ) + $ref; } $rowc = array(); foreach ($rows as $key => $row) { $rowc[] = $row['class']; $rows[$key] = array( $row['change'], $row['excluded'] ? pht('Exclude') : pht('Include'), $viewer->renderHandle($row['repositoryPHID']), $row['path'], ); } $table = id(new AphrontTableView($rows)) ->setRowClasses($rowc) ->setHeaders( array( null, pht('Type'), pht('Repository'), pht('Path'), )) ->setColumnClasses( array( null, null, null, 'wide', )); return $table; } return parent::renderChangeDetails($viewer); } }