diff --git a/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php b/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php index 6872be835b..54b9c4ee16 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php @@ -1,133 +1,213 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withBuildPHIDs(array $build_phids) { $this->buildPHIDs = $build_phids; return $this; } public function withBuildGenerations(array $build_generations) { $this->buildGenerations = $build_generations; return $this; } + public function withDateCreatedBetween($min, $max) { + $this->dateCreatedMin = $min; + $this->dateCreatedMax = $max; + return $this; + } + + public function withDateStartedBetween($min, $max) { + $this->dateStartedMin = $min; + $this->dateStartedMax = $max; + return $this; + } + + public function withDateCompletedBetween($min, $max) { + $this->dateCompletedMin = $min; + $this->dateCompletedMax = $max; + return $this; + } + + public function withTargetStatuses(array $statuses) { + $this->statuses = $statuses; + return $this; + } + public function needBuildSteps($need_build_steps) { $this->needBuildSteps = $need_build_steps; return $this; } public function newResultObject() { return new HarbormasterBuildTarget(); } protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } 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->buildPHIDs !== null) { $where[] = qsprintf( $conn, 'buildPHID in (%Ls)', $this->buildPHIDs); } if ($this->buildGenerations !== null) { $where[] = qsprintf( $conn, 'buildGeneration in (%Ld)', $this->buildGenerations); } + if ($this->dateCreatedMin !== null) { + $where[] = qsprintf( + $conn, + 'dateCreated >= %d', + $this->dateCreatedMin); + } + + if ($this->dateCreatedMax !== null) { + $where[] = qsprintf( + $conn, + 'dateCreated <= %d', + $this->dateCreatedMax); + } + + if ($this->dateStartedMin !== null) { + $where[] = qsprintf( + $conn, + 'dateStarted >= %d', + $this->dateStartedMin); + } + + if ($this->dateStartedMax !== null) { + $where[] = qsprintf( + $conn, + 'dateStarted <= %d', + $this->dateStartedMax); + } + + if ($this->dateCompletedMin !== null) { + $where[] = qsprintf( + $conn, + 'dateCompleted >= %d', + $this->dateCompletedMin); + } + + if ($this->dateCompletedMax !== null) { + $where[] = qsprintf( + $conn, + 'dateCompleted <= %d', + $this->dateCompletedMax); + } + + if ($this->statuses !== null) { + $where[] = qsprintf( + $conn, + 'targetStatus IN (%Ls)', + $this->statuses); + } + return $where; } protected function didFilterPage(array $page) { if ($this->needBuildSteps) { $step_phids = array(); foreach ($page as $target) { $step_phids[] = $target->getBuildStepPHID(); } $steps = id(new HarbormasterBuildStepQuery()) ->setViewer($this->getViewer()) ->setParentQuery($this) ->withPHIDs($step_phids) ->execute(); $steps = mpull($steps, null, 'getPHID'); foreach ($page as $target) { $target->attachBuildStep( idx($steps, $target->getBuildStepPHID())); } } return $page; } protected function willFilterPage(array $page) { $builds = array(); $build_phids = array_filter(mpull($page, 'getBuildPHID')); if ($build_phids) { $builds = id(new PhabricatorObjectQuery()) ->setViewer($this->getViewer()) ->withPHIDs($build_phids) ->setParentQuery($this) ->execute(); $builds = mpull($builds, null, 'getPHID'); } foreach ($page as $key => $build_target) { $build_phid = $build_target->getBuildPHID(); if (empty($builds[$build_phid])) { unset($page[$key]); continue; } $build_target->attachBuild($builds[$build_phid]); } return $page; } public function getQueryApplicationClass() { return 'PhabricatorHarbormasterApplication'; } } diff --git a/src/applications/harbormaster/query/HarbormasterBuildTargetSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildTargetSearchEngine.php index b224808322..db7d6f7c87 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildTargetSearchEngine.php +++ b/src/applications/harbormaster/query/HarbormasterBuildTargetSearchEngine.php @@ -1,73 +1,131 @@ setLabel(pht('Builds')) ->setKey('buildPHIDs') ->setAliases(array('build', 'builds', 'buildPHID')) ->setDescription( pht('Search for targets of a given build.')) ->setDatasource(new HarbormasterBuildPlanDatasource()), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Created After')) + ->setKey('createdStart') + ->setDescription( + pht('Search for targets created on or after a particular date.')), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Created Before')) + ->setKey('createdEnd') + ->setDescription( + pht('Search for targets created on or before a particular date.')), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Started After')) + ->setKey('startedStart') + ->setDescription( + pht('Search for targets started on or after a particular date.')), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Started Before')) + ->setKey('startedEnd') + ->setDescription( + pht('Search for targets started on or before a particular date.')), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Completed After')) + ->setKey('completedStart') + ->setDescription( + pht('Search for targets completed on or after a particular date.')), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Completed Before')) + ->setKey('completedEnd') + ->setDescription( + pht('Search for targets completed on or before a particular date.')), + id(new PhabricatorSearchStringListField()) + ->setLabel(pht('Statuses')) + ->setKey('statuses') + ->setAliases(array('status')) + ->setDescription( + pht('Search for targets with given statuses.')), ); } protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); if ($map['buildPHIDs']) { $query->withBuildPHIDs($map['buildPHIDs']); } + if ($map['createdStart'] !== null || $map['createdEnd'] !== null) { + $query->withDateCreatedBetween( + $map['createdStart'], + $map['createdEnd']); + } + + if ($map['startedStart'] !== null || $map['startedEnd'] !== null) { + $query->withDateStartedBetween( + $map['startedStart'], + $map['startedEnd']); + } + + if ($map['completedStart'] !== null || $map['completedEnd'] !== null) { + $query->withDateCompletedBetween( + $map['completedStart'], + $map['completedEnd']); + } + + if ($map['statuses']) { + $query->withTargetStatuses($map['statuses']); + } + return $query; } protected function getURI($path) { return '/harbormaster/target/'.$path; } protected function getBuiltinQueryNames() { return array( 'all' => pht('All Targets'), ); } 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 $builds, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($builds, 'HarbormasterBuildTarget'); // Currently, this only supports the "harbormaster.target.search" // API method. throw new PhutilMethodNotImplementedException(); } } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php index 30b1bd79e4..fa838288c1 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php @@ -1,497 +1,506 @@ setName($build_step->getName()) ->setBuildPHID($build->getPHID()) ->setBuildStepPHID($build_step->getPHID()) ->setClassName($build_step->getClassName()) ->setDetails($build_step->getDetails()) ->setTargetStatus(self::STATUS_PENDING) ->setVariables($variables) ->setBuildGeneration($build->getBuildGeneration()); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'details' => self::SERIALIZATION_JSON, 'variables' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'className' => 'text255', 'targetStatus' => 'text64', 'dateStarted' => 'epoch?', 'dateCompleted' => 'epoch?', 'buildGeneration' => 'uint32', // T6203/NULLABILITY // This should not be nullable. 'name' => 'text255?', ), self::CONFIG_KEY_SCHEMA => array( 'key_build' => array( 'columns' => array('buildPHID', 'buildStepPHID'), ), + 'key_started' => array( + 'columns' => array('dateStarted'), + ), + 'key_completed' => array( + 'columns' => array('dateCompleted'), + ), + 'key_created' => array( + 'columns' => array('dateCreated'), + ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( HarbormasterBuildTargetPHIDType::TYPECONST); } public function attachBuild(HarbormasterBuild $build) { $this->build = $build; return $this; } public function getBuild() { return $this->assertAttached($this->build); } public function attachBuildStep(HarbormasterBuildStep $step = null) { $this->buildStep = $step; return $this; } public function getBuildStep() { return $this->assertAttached($this->buildStep); } public function getDetail($key, $default = null) { return idx($this->details, $key, $default); } public function setDetail($key, $value) { $this->details[$key] = $value; return $this; } public function getVariables() { return parent::getVariables() + $this->getBuildTargetVariables(); } public function getVariable($key, $default = null) { return idx($this->variables, $key, $default); } public function setVariable($key, $value) { $this->variables[$key] = $value; return $this; } public function getImplementation() { if ($this->implementation === null) { $obj = HarbormasterBuildStepImplementation::requireImplementation( $this->className); $obj->loadSettings($this); $this->implementation = $obj; } return $this->implementation; } public function isAutotarget() { try { return (bool)$this->getImplementation()->getBuildStepAutotargetPlanKey(); } catch (Exception $e) { return false; } } public function getName() { if (strlen($this->name) && !$this->isAutotarget()) { return $this->name; } try { return $this->getImplementation()->getName(); } catch (Exception $e) { return $this->getClassName(); } } private function getBuildTargetVariables() { return array( 'target.phid' => $this->getPHID(), ); } public function createArtifact( PhabricatorUser $actor, $artifact_key, $artifact_type, array $artifact_data) { $impl = HarbormasterArtifact::getArtifactType($artifact_type); if (!$impl) { throw new Exception( pht( 'There is no implementation available for artifacts of type "%s".', $artifact_type)); } $impl->validateArtifactData($artifact_data); $artifact = HarbormasterBuildArtifact::initializeNewBuildArtifact($this) ->setArtifactKey($artifact_key) ->setArtifactType($artifact_type) ->setArtifactData($artifact_data); $impl = $artifact->getArtifactImplementation(); $impl->willCreateArtifact($actor); return $artifact->save(); } public function loadArtifact($artifact_key) { $indexes = array(); $indexes[] = HarbormasterBuildArtifact::getArtifactIndex( $this, $artifact_key); $artifact = id(new HarbormasterBuildArtifactQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withArtifactIndexes($indexes) ->executeOne(); if ($artifact === null) { throw new Exception( pht( 'Artifact "%s" not found!', $artifact_key)); } return $artifact; } public function newLog($log_source, $log_type) { $log_source = id(new PhutilUTF8StringTruncator()) ->setMaximumBytes(250) ->truncateString($log_source); $log = HarbormasterBuildLog::initializeNewBuildLog($this) ->setLogSource($log_source) ->setLogType($log_type) ->openBuildLog(); return $log; } public function getFieldValue($key) { $field_list = PhabricatorCustomField::getObjectFields( $this->getBuildStep(), PhabricatorCustomField::ROLE_VIEW); $fields = $field_list->getFields(); $full_key = "std:harbormaster:core:{$key}"; $field = idx($fields, $full_key); if (!$field) { throw new Exception( pht( 'Unknown build step field "%s"!', $key)); } $field = clone $field; $field->setValueFromStorage($this->getDetail($key)); return $field->getBuildTargetFieldValue(); } /* -( Status )------------------------------------------------------------- */ public function isComplete() { switch ($this->getTargetStatus()) { case self::STATUS_PASSED: case self::STATUS_FAILED: case self::STATUS_ABORTED: return true; } return false; } public function isFailed() { switch ($this->getTargetStatus()) { case self::STATUS_FAILED: case self::STATUS_ABORTED: return true; } return false; } public function isWaiting() { switch ($this->getTargetStatus()) { case self::STATUS_WAITING: return true; } return false; } public function isUnderway() { switch ($this->getTargetStatus()) { case self::STATUS_PENDING: case self::STATUS_BUILDING: return true; } return false; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, ); } public function getPolicy($capability) { return $this->getBuild()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getBuild()->hasAutomaticCapability( $capability, $viewer); } public function describeAutomaticCapability($capability) { return pht('Users must be able to see a build to view its build targets.'); } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $viewer = $engine->getViewer(); $this->openTransaction(); $lint_message = new HarbormasterBuildLintMessage(); $conn = $lint_message->establishConnection('w'); queryfx( $conn, 'DELETE FROM %T WHERE buildTargetPHID = %s', $lint_message->getTableName(), $this->getPHID()); $unit_message = new HarbormasterBuildUnitMessage(); $conn = $unit_message->establishConnection('w'); queryfx( $conn, 'DELETE FROM %T WHERE buildTargetPHID = %s', $unit_message->getTableName(), $this->getPHID()); $logs = id(new HarbormasterBuildLogQuery()) ->setViewer($viewer) ->withBuildTargetPHIDs(array($this->getPHID())) ->execute(); foreach ($logs as $log) { $engine->destroyObject($log); } $artifacts = id(new HarbormasterBuildArtifactQuery()) ->setViewer($viewer) ->withBuildTargetPHIDs(array($this->getPHID())) ->execute(); foreach ($artifacts as $artifact) { $engine->destroyObject($artifact); } $messages = id(new HarbormasterBuildMessageQuery()) ->setViewer($viewer) ->withReceiverPHIDs(array($this->getPHID())) ->execute(); foreach ($messages as $message) { $engine->destroyObject($message); } $this->delete(); $this->saveTransaction(); } /* -( PhabricatorConduitResultInterface )---------------------------------- */ public function getFieldSpecificationsForConduit() { return array( id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('name') ->setType('string') ->setDescription(pht('The name of the build target.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('buildPHID') ->setType('phid') ->setDescription(pht('The build the target is associated with.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('buildStepPHID') ->setType('phid') ->setDescription(pht('The build step the target runs.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('status') ->setType('map') ->setDescription(pht('Status for the build target.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('epochStarted') ->setType('epoch?') ->setDescription( pht( 'Epoch timestamp for target start, if the target '. 'has started.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('epochCompleted') ->setType('epoch?') ->setDescription( pht( 'Epoch timestamp for target completion, if the target '. 'has completed.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('buildGeneration') ->setType('int') ->setDescription( pht( 'Build generation this target belongs to. When builds '. 'restart, a new generation with new targets is created.')), ); } public function getFieldValuesForConduit() { $status = $this->getTargetStatus(); $epoch_started = $this->getDateStarted(); if ($epoch_started) { $epoch_started = (int)$epoch_started; } else { $epoch_started = null; } $epoch_completed = $this->getDateCompleted(); if ($epoch_completed) { $epoch_completed = (int)$epoch_completed; } else { $epoch_completed = null; } return array( 'name' => $this->getName(), 'buildPHID' => $this->getBuildPHID(), 'buildStepPHID' => $this->getBuildStepPHID(), 'status' => array( 'value' => $status, 'name' => self::getBuildTargetStatusName($status), ), 'epochStarted' => $epoch_started, 'epochCompleted' => $epoch_completed, 'buildGeneration' => (int)$this->getBuildGeneration(), ); } public function getConduitSearchAttachments() { return array(); } }