diff --git a/src/applications/harbormaster/phid/HarbormasterBuildablePHIDType.php b/src/applications/harbormaster/phid/HarbormasterBuildablePHIDType.php index fbc08d4ef3..674393dfc7 100644 --- a/src/applications/harbormaster/phid/HarbormasterBuildablePHIDType.php +++ b/src/applications/harbormaster/phid/HarbormasterBuildablePHIDType.php @@ -1,88 +1,88 @@ withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { - $viewer = $this->getViewer(); + $viewer = $query->getViewer(); $target_phids = array(); foreach ($objects as $phid => $object) { $target_phids[] = $object->getBuildablePHID(); } $target_handles = $viewer->loadHandles($target_phids); foreach ($handles as $phid => $handle) { $buildable = $objects[$phid]; $id = $buildable->getID(); $buildable_phid = $buildable->getBuildablePHID(); $target = $target_handles[$buildable_phid]; $target_name = $target->getFullName(); $uri = $buildable->getURI(); $monogram = $buildable->getMonogram(); $handle ->setURI($uri) ->setName($monogram) ->setFullName("{$monogram}: {$target_name}"); } } public function canLoadNamedObject($name) { return preg_match('/^B\d*[1-9]\d*$/i', $name); } public function loadNamedObjects( PhabricatorObjectQuery $query, array $names) { $id_map = array(); foreach ($names as $name) { $id = (int)substr($name, 1); $id_map[$id][] = $name; } $objects = id(new HarbormasterBuildableQuery()) ->setViewer($query->getViewer()) ->withIDs(array_keys($id_map)) ->execute(); $results = array(); foreach ($objects as $id => $object) { foreach (idx($id_map, $id, array()) as $name) { $results[$name] = $object; } } return $results; } } diff --git a/src/applications/harbormaster/query/HarbormasterBuildableQuery.php b/src/applications/harbormaster/query/HarbormasterBuildableQuery.php index fc069bd031..b1a643cac7 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildableQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildableQuery.php @@ -1,171 +1,184 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withBuildablePHIDs(array $buildable_phids) { $this->buildablePHIDs = $buildable_phids; return $this; } public function withContainerPHIDs(array $container_phids) { $this->containerPHIDs = $container_phids; return $this; } public function withManualBuildables($manual) { $this->manualBuildables = $manual; return $this; } public function needContainerObjects($need) { $this->needContainerObjects = $need; return $this; } + public function withStatuses(array $statuses) { + $this->statuses = $statuses; + return $this; + } + public function needBuilds($need) { $this->needBuilds = $need; return $this; } public function needTargets($need) { $this->needTargets = $need; return $this; } public function newResultObject() { return new HarbormasterBuildable(); } protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $page) { $buildables = array(); $buildable_phids = array_filter(mpull($page, 'getBuildablePHID')); if ($buildable_phids) { $buildables = id(new PhabricatorObjectQuery()) ->setViewer($this->getViewer()) ->withPHIDs($buildable_phids) ->setParentQuery($this) ->execute(); $buildables = mpull($buildables, null, 'getPHID'); } foreach ($page as $key => $buildable) { $buildable_phid = $buildable->getBuildablePHID(); if (empty($buildables[$buildable_phid])) { unset($page[$key]); continue; } $buildable->attachBuildableObject($buildables[$buildable_phid]); } return $page; } protected function didFilterPage(array $page) { if ($this->needContainerObjects) { $container_phids = array_filter(mpull($page, 'getContainerPHID')); if ($container_phids) { $containers = id(new PhabricatorObjectQuery()) ->setViewer($this->getViewer()) ->withPHIDs($container_phids) ->setParentQuery($this) ->execute(); $containers = mpull($containers, null, 'getPHID'); } else { $containers = array(); } foreach ($page as $key => $buildable) { $container_phid = $buildable->getContainerPHID(); $buildable->attachContainerObject(idx($containers, $container_phid)); } } if ($this->needBuilds || $this->needTargets) { $builds = id(new HarbormasterBuildQuery()) ->setViewer($this->getViewer()) ->setParentQuery($this) ->withBuildablePHIDs(mpull($page, 'getPHID')) ->needBuildTargets($this->needTargets) ->execute(); $builds = mgroup($builds, 'getBuildablePHID'); foreach ($page as $key => $buildable) { $buildable->attachBuilds(idx($builds, $buildable->getPHID(), array())); } } return $page; } protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, 'phid IN (%Ls)', $this->phids); } if ($this->buildablePHIDs !== null) { $where[] = qsprintf( $conn, 'buildablePHID IN (%Ls)', $this->buildablePHIDs); } if ($this->containerPHIDs !== null) { $where[] = qsprintf( $conn, 'containerPHID in (%Ls)', $this->containerPHIDs); } + if ($this->statuses !== null) { + $where[] = qsprintf( + $conn, + 'buildableStatus in (%Ls)', + $this->statuses); + } + if ($this->manualBuildables !== null) { $where[] = qsprintf( $conn, 'isManualBuildable = %d', (int)$this->manualBuildables); } return $where; } public function getQueryApplicationClass() { return 'PhabricatorHarbormasterApplication'; } } diff --git a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php index 937816eb19..cfff27b1aa 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php +++ b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php @@ -1,249 +1,190 @@ readPHIDsFromRequest( - $request, - 'revisions', - array( - DifferentialRevisionPHIDType::TYPECONST, - )); - - $repositories = $this->readPHIDsFromRequest( - $request, - 'repositories', - array( - PhabricatorRepositoryRepositoryPHIDType::TYPECONST, - )); - - $container_phids = array_merge($revisions, $repositories); - $saved->setParameter('containerPHIDs', $container_phids); - - $commits = $this->readPHIDsFromRequest( - $request, - 'commits', - array( - PhabricatorRepositoryCommitPHIDType::TYPECONST, - )); - - $diffs = $this->readListFromRequest($request, 'diffs'); - if ($diffs) { - $diffs = id(new DifferentialDiffQuery()) - ->setViewer($this->requireViewer()) - ->withIDs($diffs) - ->execute(); - $diffs = mpull($diffs, 'getPHID', 'getPHID'); - } - - $buildable_phids = array_merge($commits, $diffs); - $saved->setParameter('buildablePHIDs', $buildable_phids); - - $saved->setParameter( - 'manual', - $this->readBoolFromRequest($request, 'manual')); + public function newQuery() { + return new HarbormasterBuildableQuery(); + } - return $saved; + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchStringListField()) + ->setKey('objectPHIDs') + ->setAliases(array('objects')) + ->setLabel(pht('Objects')) + ->setPlaceholder(pht('rXabcdef, PHID-DIFF-1234, ...')) + ->setDescription(pht('Search for builds of particular objects.')), + id(new PhabricatorSearchStringListField()) + ->setKey('containerPHIDs') + ->setAliases(array('containers')) + ->setLabel(pht('Containers')) + ->setPlaceholder(pht('rXYZ, R123, D456, ...')) + ->setDescription( + pht('Search for builds by containing revision or repository.')), + id(new PhabricatorSearchCheckboxesField()) + ->setKey('statuses') + ->setLabel(pht('Statuses')) + ->setOptions(HarbormasterBuildable::getBuildStatusMap()) + ->setDescription(pht('Search for builds by buildable status.')), + id(new PhabricatorSearchThreeStateField()) + ->setLabel(pht('Manual')) + ->setKey('manual') + ->setDescription( + pht('Search for only manual or automatic buildables.')) + ->setOptions( + pht('(Show All)'), + pht('Show Only Manual Builds'), + pht('Show Only Automated Builds')), + ); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new HarbormasterBuildableQuery()); + private function resolvePHIDs(array $names) { + $viewer = $this->requireViewer(); - $container_phids = $saved->getParameter('containerPHIDs', array()); - if ($container_phids) { - $query->withContainerPHIDs($container_phids); + $objects = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withNames($names) + ->execute(); + + // TODO: Instead of using string lists, we should ideally be using some + // kind of smart field with resolver logic that can help users type the + // right stuff. For now, just return a bogus value here so nothing matches + // but the form doesn't explode. + if (!$objects) { + return array('-'); } - $buildable_phids = $saved->getParameter('buildablePHIDs', array()); + return mpull($objects, 'getPHID'); + } + + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); - if ($buildable_phids) { - $query->withBuildablePHIDs($buildable_phids); + if ($map['objectPHIDs']) { + $phids = $this->resolvePHIDs($map['objectPHIDs']); + if ($phids) { + $query->withBuildablePHIDs($phids); + } } - $manual = $saved->getParameter('manual'); - if ($manual !== null) { - $query->withManualBuildables($manual); + if ($map['containerPHIDs']) { + $phids = $this->resolvePHIDs($map['containerPHIDs']); + if ($phids) { + $query->withContainerPHIDs($phids); + } } - return $query; - } + if ($map['statuses']) { + $query->withStatuses($map['statuses']); + } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved_query) { - - $container_phids = $saved_query->getParameter('containerPHIDs', array()); - $buildable_phids = $saved_query->getParameter('buildablePHIDs', array()); - - $all_phids = array_merge($container_phids, $buildable_phids); - - $revision_names = array(); - $diff_names = array(); - $repository_names = array(); - $commit_names = array(); - - if ($all_phids) { - $objects = id(new PhabricatorObjectQuery()) - ->setViewer($this->requireViewer()) - ->withPHIDs($all_phids) - ->execute(); - - foreach ($all_phids as $phid) { - $object = idx($objects, $phid); - if (!$object) { - continue; - } - - if ($object instanceof DifferentialRevision) { - $revision_names[] = 'D'.$object->getID(); - } else if ($object instanceof DifferentialDiff) { - $diff_names[] = $object->getID(); - } else if ($object instanceof PhabricatorRepository) { - $repository_names[] = $object->getMonogram(); - } else if ($object instanceof PhabricatorRepositoryCommit) { - $repository = $object->getRepository(); - $commit_names[] = $repository->formatCommitName( - $object->getCommitIdentifier()); - } - } + if ($map['manual'] !== null) { + $query->withManualBuildables($map['manual']); } - $form - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Differential Revisions')) - ->setName('revisions') - ->setValue(implode(', ', $revision_names))) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Differential Diffs')) - ->setName('diffs') - ->setValue(implode(', ', $diff_names))) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Repositories')) - ->setName('repositories') - ->setValue(implode(', ', $repository_names))) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Commits')) - ->setName('commits') - ->setValue(implode(', ', $commit_names))) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Origin')) - ->setName('manual') - ->setValue($this->getBoolFromQuery($saved_query, 'manual')) - ->setOptions( - array( - '' => pht('(All Origins)'), - 'true' => pht('Manual Buildables'), - 'false' => pht('Automatic Buildables'), - ))); + return $query; } protected function getURI($path) { return '/harbormaster/'.$path; } protected function getBuiltinQueryNames() { return array( 'all' => pht('All Buildables'), ); } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query; } return parent::buildSavedQueryFromBuiltin($query_key); } protected function renderResultList( array $buildables, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($buildables, 'HarbormasterBuildable'); $viewer = $this->requireViewer(); $phids = array(); foreach ($buildables as $buildable) { $phids[] = $buildable->getBuildableObject() ->getHarbormasterBuildableDisplayPHID(); $phids[] = $buildable->getContainerPHID(); $phids[] = $buildable->getBuildablePHID(); } $handles = $viewer->loadHandles($phids); $list = new PHUIObjectItemListView(); foreach ($buildables as $buildable) { $id = $buildable->getID(); $display_phid = $buildable->getBuildableObject() ->getHarbormasterBuildableDisplayPHID(); $container_phid = $buildable->getContainerPHID(); $buildable_phid = $buildable->getBuildablePHID(); $item = id(new PHUIObjectItemView()) ->setObjectName(pht('Buildable %d', $buildable->getID())); if ($display_phid) { $handle = $handles[$display_phid]; $item->setHeader($handle->getFullName()); } if ($container_phid && ($container_phid != $display_phid)) { $handle = $handles[$container_phid]; $item->addAttribute($handle->getName()); } if ($buildable_phid && ($buildable_phid != $display_phid)) { $handle = $handles[$buildable_phid]; $item->addAttribute($handle->getFullName()); } $item->setHref($buildable->getURI()); if ($buildable->getIsManualBuildable()) { $item->addIcon('fa-wrench grey', pht('Manual')); } $status = $buildable->getBuildableStatus(); $status_icon = HarbormasterBuildable::getBuildableStatusIcon($status); $status_color = HarbormasterBuildable::getBuildableStatusColor($status); $status_label = HarbormasterBuildable::getBuildableStatusName($status); $item->setStatusIcon("{$status_icon} {$status_color}", $status_label); $list->addItem($item); } $result = new PhabricatorApplicationSearchResultView(); $result->setObjectList($list); $result->setNoDataString(pht('No buildables found.')); return $result; } } diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php index f02fff270b..7a7b32618c 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildable.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -1,329 +1,329 @@ pht('Building'), + self::STATUS_PASSED => pht('Passed'), + self::STATUS_FAILED => pht('Failed'), + ); } public static function getBuildableStatusIcon($status) { switch ($status) { case self::STATUS_BUILDING: return PHUIStatusItemView::ICON_RIGHT; case self::STATUS_PASSED: return PHUIStatusItemView::ICON_ACCEPT; case self::STATUS_FAILED: return PHUIStatusItemView::ICON_REJECT; default: return PHUIStatusItemView::ICON_QUESTION; } } public static function getBuildableStatusColor($status) { switch ($status) { case self::STATUS_BUILDING: return 'blue'; case self::STATUS_PASSED: return 'green'; case self::STATUS_FAILED: return 'red'; default: return 'bluegrey'; } } public static function initializeNewBuildable(PhabricatorUser $actor) { return id(new HarbormasterBuildable()) ->setIsManualBuildable(0) ->setBuildableStatus(self::STATUS_BUILDING); } public function getMonogram() { return 'B'.$this->getID(); } public function getURI() { return '/'.$this->getMonogram(); } /** * Returns an existing buildable for the object's PHID or creates a * new buildable implicitly if needed. */ public static function createOrLoadExisting( PhabricatorUser $actor, $buildable_object_phid, $container_object_phid) { $buildable = id(new HarbormasterBuildableQuery()) ->setViewer($actor) ->withBuildablePHIDs(array($buildable_object_phid)) ->withManualBuildables(false) ->setLimit(1) ->executeOne(); if ($buildable) { return $buildable; } $buildable = self::initializeNewBuildable($actor) ->setBuildablePHID($buildable_object_phid) ->setContainerPHID($container_object_phid); $buildable->save(); return $buildable; } /** * Start builds for a given buildable. * * @param phid PHID of the object to build. * @param phid Container PHID for the buildable. * @param list List of builds to perform. * @return void */ public static function applyBuildPlans( $phid, $container_phid, array $requests) { assert_instances_of($requests, 'HarbormasterBuildRequest'); if (!$requests) { return; } // Skip all of this logic if the Harbormaster application // isn't currently installed. $harbormaster_app = 'PhabricatorHarbormasterApplication'; if (!PhabricatorApplication::isClassInstalled($harbormaster_app)) { return; } $viewer = PhabricatorUser::getOmnipotentUser(); $buildable = self::createOrLoadExisting( $viewer, $phid, $container_phid); $plan_phids = mpull($requests, 'getBuildPlanPHID'); $plans = id(new HarbormasterBuildPlanQuery()) ->setViewer($viewer) ->withPHIDs($plan_phids) ->execute(); $plans = mpull($plans, null, 'getPHID'); foreach ($requests as $request) { $plan_phid = $request->getBuildPlanPHID(); $plan = idx($plans, $plan_phid); if (!$plan) { throw new Exception( pht( 'Failed to load build plan ("%s").', $plan_phid)); } if ($plan->isDisabled()) { // TODO: This should be communicated more clearly -- maybe we should // create the build but set the status to "disabled" or "derelict". continue; } $parameters = $request->getBuildParameters(); $buildable->applyPlan($plan, $parameters, $request->getInitiatorPHID()); } } public function applyPlan( HarbormasterBuildPlan $plan, array $parameters, $initiator_phid) { $viewer = PhabricatorUser::getOmnipotentUser(); $build = HarbormasterBuild::initializeNewBuild($viewer) ->setBuildablePHID($this->getPHID()) ->setBuildPlanPHID($plan->getPHID()) ->setBuildParameters($parameters) ->setBuildStatus(HarbormasterBuild::STATUS_PENDING); if ($initiator_phid) { $build->setInitiatorPHID($initiator_phid); } $auto_key = $plan->getPlanAutoKey(); if ($auto_key) { $build->setPlanAutoKey($auto_key); } $build->save(); PhabricatorWorker::scheduleTask( 'HarbormasterBuildWorker', array( 'buildID' => $build->getID(), ), array( 'objectPHID' => $build->getPHID(), )); return $build; } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'containerPHID' => 'phid?', 'buildableStatus' => 'text32', 'isManualBuildable' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( 'key_buildable' => array( 'columns' => array('buildablePHID'), ), 'key_container' => array( 'columns' => array('containerPHID'), ), 'key_manual' => array( 'columns' => array('isManualBuildable'), ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( HarbormasterBuildablePHIDType::TYPECONST); } public function attachBuildableObject($buildable_object) { $this->buildableObject = $buildable_object; return $this; } public function getBuildableObject() { return $this->assertAttached($this->buildableObject); } public function attachContainerObject($container_object) { $this->containerObject = $container_object; return $this; } public function getContainerObject() { return $this->assertAttached($this->containerObject); } public function attachBuilds(array $builds) { assert_instances_of($builds, 'HarbormasterBuild'); $this->builds = $builds; return $this; } public function getBuilds() { return $this->assertAttached($this->builds); } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new HarbormasterBuildableTransactionEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new HarbormasterBuildableTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { return $this->getBuildableObject()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getBuildableObject()->hasAutomaticCapability( $capability, $viewer); } public function describeAutomaticCapability($capability) { return pht('A buildable inherits policies from the underlying object.'); } /* -( HarbormasterBuildableInterface )------------------------------------- */ public function getHarbormasterBuildableDisplayPHID() { return $this->getBuildableObject()->getHarbormasterBuildableDisplayPHID(); } public function getHarbormasterBuildablePHID() { // NOTE: This is essentially just for convenience, as it allows you create // a copy of a buildable by specifying `B123` without bothering to go // look up the underlying object. return $this->getBuildablePHID(); } public function getHarbormasterContainerPHID() { return $this->getContainerPHID(); } public function getBuildVariables() { return array(); } public function getAvailableBuildVariables() { return array(); } } diff --git a/src/applications/search/field/PhabricatorSearchStringListField.php b/src/applications/search/field/PhabricatorSearchStringListField.php index 415caf7ea2..2dd9517a3e 100644 --- a/src/applications/search/field/PhabricatorSearchStringListField.php +++ b/src/applications/search/field/PhabricatorSearchStringListField.php @@ -1,26 +1,44 @@ placeholder = $placeholder; + return $this; + } + + public function getPlaceholder() { + return $this->placeholder; + } + protected function getDefaultValue() { return array(); } protected function getValueFromRequest(AphrontRequest $request, $key) { return $request->getStrList($key); } protected function newControl() { - return new AphrontFormTextControl(); + $control = new AphrontFormTextControl(); + + $placeholder = $this->getPlaceholder(); + if ($placeholder !== null) { + $control->setPlaceholder($placeholder); + } + + return $control; } protected function getValueForControl() { return implode(', ', parent::getValueForControl()); } protected function newConduitParameterType() { return new ConduitStringListParameterType(); } }