Index: resources/sql/patches/20131020.harbormaster.sql =================================================================== --- /dev/null +++ resources/sql/patches/20131020.harbormaster.sql @@ -0,0 +1,74 @@ +CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildable ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, + buildablePHID VARCHAR(64) NOT NULL COLLATE utf8_bin, + containerPHID VARCHAR(64) COLLATE utf8_bin, + buildStatus VARCHAR(32) NOT NULL COLLATE utf8_bin, + buildableStatus VARCHAR(32) NOT NULL COLLATE utf8_bin, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + KEY `key_buildable` (buildablePHID), + KEY `key_container` (containerPHID), + UNIQUE KEY `key_phid` (phid) +) ENGINE=InnoDB, COLLATE utf8_general_ci; + +CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildartifact ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + buildablePHID VARCHAR(64) NOT NULL COLLATE utf8_bin, + artifactType VARCHAR(32) NOT NULL COLLATE utf8_bin, + artifactIndex VARCHAR(12) NOT NULL COLLATE utf8_bin, + artifactKey VARCHAR(255) NOT NULL COLLATE utf8_bin, + artifactData LONGTEXT NOT NULL COLLATE utf8_bin, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_artifact` (buildablePHID, artifactType, artifactIndex), + UNIQUE KEY `key_artifact_type` (artifactType, artifactIndex), + KEY `key_garbagecollect` (artifactType, dateCreated) +) ENGINE=InnoDB, COLLATE utf8_general_ci; + +CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildplan ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, + name VARCHAR(255) NOT NULL, + planStatus VARCHAR(32) NOT NULL COLLATE utf8_bin, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + KEY `key_status` (planStatus) +) ENGINE=InnoDB, COLLATE utf8_general_ci; + +CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildplantransaction ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, + authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, + objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, + viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, + editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, + commentPHID VARCHAR(64) COLLATE utf8_bin, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin, + oldValue LONGTEXT NOT NULL COLLATE utf8_bin, + newValue LONGTEXT NOT NULL COLLATE utf8_bin, + contentSource LONGTEXT NOT NULL COLLATE utf8_bin, + metadata LONGTEXT NOT NULL COLLATE utf8_bin, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + + UNIQUE KEY `key_phid` (phid), + KEY `key_object` (objectPHID) + +) ENGINE=InnoDB, COLLATE utf8_general_ci; + +CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_build ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, + buildablePHID varchar(64) NOT NULL COLLATE utf8_bin, + buildPlanPHID varchar(64) NOT NULL COLLATE utf8_bin, + buildStatus VARCHAR(32) NOT NULL COLLATE utf8_bin, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + KEY `key_buildable` (buildablePHID), + KEY `key_plan` (buildPlanPHID), + KEY `key_status` (buildStatus) +) ENGINE=InnoDB, COLLATE utf8_general_ci; Index: src/__phutil_library_map__.php =================================================================== --- src/__phutil_library_map__.php +++ src/__phutil_library_map__.php @@ -619,8 +619,46 @@ 'FileCreateMailReceiver' => 'applications/files/mail/FileCreateMailReceiver.php', 'FileMailReceiver' => 'applications/files/mail/FileMailReceiver.php', 'FileReplyHandler' => 'applications/files/mail/FileReplyHandler.php', + 'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php', + 'HarbormasterBuildArtifact' => 'applications/harbormaster/storage/build/HarbormasterBuildArtifact.php', + 'HarbormasterBuildItem' => 'applications/harbormaster/storage/build/HarbormasterBuildItem.php', + 'HarbormasterBuildItemQuery' => 'applications/harbormaster/query/HarbormasterBuildItemQuery.php', + 'HarbormasterBuildLog' => 'applications/harbormaster/storage/build/HarbormasterBuildLog.php', + 'HarbormasterBuildPlan' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php', + 'HarbormasterBuildPlanEditor' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditor.php', + 'HarbormasterBuildPlanQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanQuery.php', + 'HarbormasterBuildPlanSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php', + 'HarbormasterBuildPlanTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php', + 'HarbormasterBuildPlanTransactionComment' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransactionComment.php', + 'HarbormasterBuildPlanTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php', + 'HarbormasterBuildQuery' => 'applications/harbormaster/query/HarbormasterBuildQuery.php', + 'HarbormasterBuildStep' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStep.php', + 'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php', + 'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php', + 'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php', + 'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php', + 'HarbormasterBuildableArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php', + 'HarbormasterBuildableEditController' => 'applications/harbormaster/controller/HarbormasterBuildableEditController.php', + 'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php', + 'HarbormasterBuildableQuery' => 'applications/harbormaster/query/HarbormasterBuildableQuery.php', + 'HarbormasterBuildableSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildableSearchEngine.php', + 'HarbormasterBuildableViewController' => 'applications/harbormaster/controller/HarbormasterBuildableViewController.php', + 'HarbormasterCapabilityManagePlans' => 'applications/harbormaster/capability/HarbormasterCapabilityManagePlans.php', + 'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php', 'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php', 'HarbormasterObject' => 'applications/harbormaster/storage/HarbormasterObject.php', + 'HarbormasterPHIDTypeBuild' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuild.php', + 'HarbormasterPHIDTypeBuildItem' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildItem.php', + 'HarbormasterPHIDTypeBuildPlan' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildPlan.php', + 'HarbormasterPHIDTypeBuildStep' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildStep.php', + 'HarbormasterPHIDTypeBuildTarget' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildTarget.php', + 'HarbormasterPHIDTypeBuildable' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildable.php', + 'HarbormasterPlanController' => 'applications/harbormaster/controller/HarbormasterPlanController.php', + 'HarbormasterPlanEditController' => 'applications/harbormaster/controller/HarbormasterPlanEditController.php', + 'HarbormasterPlanExecuteController' => 'applications/harbormaster/controller/HarbormasterPlanExecuteController.php', + 'HarbormasterPlanListController' => 'applications/harbormaster/controller/HarbormasterPlanListController.php', + 'HarbormasterPlanViewController' => 'applications/harbormaster/controller/HarbormasterPlanViewController.php', + 'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php', 'HarbormasterRunnerWorker' => 'applications/harbormaster/worker/HarbormasterRunnerWorker.php', 'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php', 'HeraldAction' => 'applications/herald/storage/HeraldAction.php', @@ -850,6 +888,7 @@ 'PhabricatorApplicationFeed' => 'applications/feed/application/PhabricatorApplicationFeed.php', 'PhabricatorApplicationFiles' => 'applications/files/application/PhabricatorApplicationFiles.php', 'PhabricatorApplicationFlags' => 'applications/flag/application/PhabricatorApplicationFlags.php', + 'PhabricatorApplicationHarbormaster' => 'applications/harbormaster/application/PhabricatorApplicationHarbormaster.php', 'PhabricatorApplicationHerald' => 'applications/herald/application/PhabricatorApplicationHerald.php', 'PhabricatorApplicationLaunchView' => 'applications/meta/view/PhabricatorApplicationLaunchView.php', 'PhabricatorApplicationLegalpad' => 'applications/legalpad/application/PhabricatorApplicationLegalpad.php', @@ -2752,8 +2791,71 @@ 'FileCreateMailReceiver' => 'PhabricatorMailReceiver', 'FileMailReceiver' => 'PhabricatorObjectMailReceiver', 'FileReplyHandler' => 'PhabricatorMailReplyHandler', + 'HarbormasterBuild' => + array( + 0 => 'HarbormasterDAO', + 1 => 'PhabricatorPolicyInterface', + ), + 'HarbormasterBuildArtifact' => + array( + 0 => 'HarbormasterDAO', + 1 => 'PhabricatorPolicyInterface', + ), + 'HarbormasterBuildItem' => 'HarbormasterDAO', + 'HarbormasterBuildItemQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildLog' => 'HarbormasterDAO', + 'HarbormasterBuildPlan' => + array( + 0 => 'HarbormasterDAO', + 1 => 'PhabricatorPolicyInterface', + 2 => 'PhabricatorSubscribableInterface', + ), + 'HarbormasterBuildPlanEditor' => 'PhabricatorApplicationTransactionEditor', + 'HarbormasterBuildPlanQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildPlanSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'HarbormasterBuildPlanTransaction' => 'PhabricatorApplicationTransaction', + 'HarbormasterBuildPlanTransactionComment' => 'PhabricatorApplicationTransactionComment', + 'HarbormasterBuildPlanTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'HarbormasterBuildQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildStep' => 'HarbormasterDAO', + 'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildTarget' => 'HarbormasterDAO', + 'HarbormasterBuildTargetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildable' => + array( + 0 => 'HarbormasterDAO', + 1 => 'PhabricatorPolicyInterface', + ), + 'HarbormasterBuildableArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildableEditController' => 'HarbormasterController', + 'HarbormasterBuildableListController' => + array( + 0 => 'HarbormasterController', + 1 => 'PhabricatorApplicationSearchResultsControllerInterface', + ), + 'HarbormasterBuildableQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'HarbormasterBuildableSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'HarbormasterBuildableViewController' => 'HarbormasterController', + 'HarbormasterCapabilityManagePlans' => 'PhabricatorPolicyCapability', + 'HarbormasterController' => 'PhabricatorController', 'HarbormasterDAO' => 'PhabricatorLiskDAO', 'HarbormasterObject' => 'HarbormasterDAO', + 'HarbormasterPHIDTypeBuild' => 'PhabricatorPHIDType', + 'HarbormasterPHIDTypeBuildItem' => 'PhabricatorPHIDType', + 'HarbormasterPHIDTypeBuildPlan' => 'PhabricatorPHIDType', + 'HarbormasterPHIDTypeBuildStep' => 'PhabricatorPHIDType', + 'HarbormasterPHIDTypeBuildTarget' => 'PhabricatorPHIDType', + 'HarbormasterPHIDTypeBuildable' => 'PhabricatorPHIDType', + 'HarbormasterPlanController' => 'PhabricatorController', + 'HarbormasterPlanEditController' => 'HarbormasterPlanController', + 'HarbormasterPlanExecuteController' => 'HarbormasterPlanController', + 'HarbormasterPlanListController' => + array( + 0 => 'HarbormasterPlanController', + 1 => 'PhabricatorApplicationSearchResultsControllerInterface', + ), + 'HarbormasterPlanViewController' => 'HarbormasterPlanController', + 'HarbormasterRemarkupRule' => 'PhabricatorRemarkupRuleObject', 'HarbormasterRunnerWorker' => 'PhabricatorWorker', 'HarbormasterScratchTable' => 'HarbormasterDAO', 'HeraldAction' => 'HeraldDAO', @@ -3008,6 +3110,7 @@ 'PhabricatorApplicationFeed' => 'PhabricatorApplication', 'PhabricatorApplicationFiles' => 'PhabricatorApplication', 'PhabricatorApplicationFlags' => 'PhabricatorApplication', + 'PhabricatorApplicationHarbormaster' => 'PhabricatorApplication', 'PhabricatorApplicationHerald' => 'PhabricatorApplication', 'PhabricatorApplicationLaunchView' => 'AphrontView', 'PhabricatorApplicationLegalpad' => 'PhabricatorApplication', Index: src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php =================================================================== --- /dev/null +++ src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php @@ -0,0 +1,68 @@ +[1-9]\d*)' => 'HarbormasterBuildableViewController', + '/harbormaster/' => array( + '(?:query/(?P[^/]+)/)?' + => 'HarbormasterBuildableListController', + 'buildable/' => array( + 'edit/(?:(?P\d+)/)?' => 'HarbormasterBuildableEditController', + ), + 'plan/' => array( + '(?:query/(?P[^/]+)/)?' + => 'HarbormasterPlanListController', + 'edit/(?:(?P\d+)/)?' => 'HarbormasterPlanEditController', + '(?P\d+)/' => 'HarbormasterPlanViewController', + 'execute/(?P\d+)/' => 'HarbormasterPlanExecuteController', + ), + ), + ); + } + + public function getCustomCapabilities() { + return array( + HarbormasterCapabilityManagePlans::CAPABILITY => array( + 'caption' => pht('Can create and manage build plans.'), + 'default' => PhabricatorPolicies::POLICY_ADMIN, + ), + ); + } + +} Index: src/applications/harbormaster/capability/HarbormasterCapabilityManagePlans.php =================================================================== --- /dev/null +++ src/applications/harbormaster/capability/HarbormasterCapabilityManagePlans.php @@ -0,0 +1,21 @@ +id = idx($data, 'id'); + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $this->requireApplicationCapability( + HarbormasterCapabilityManagePlans::CAPABILITY); + + if ($this->id) { + $buildable = id(new HarbormasterBuildableQuery()) + ->setViewer($viewer) + ->withIDs(array($this->id)) + ->executeOne(); + if (!$buildable) { + return new Aphront404Response(); + } + } else { + $buildable = HarbormasterBuildable::initializeNewBuildable($viewer); + } + + $e_name = true; + $v_name = null; + + $errors = array(); + if ($request->isFormPost()) { + $v_name = $request->getStr('buildablePHID'); + + if ($v_name) { + $object = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withNames(array($v_name)) + ->executeOne(); + + if ($object instanceof DifferentialRevision) { + throw new Exception( + "TODO: We need to assign PHIDs to diffs before this will work."); + } else if ($object instanceof PhabricatorRepositoryCommit) { + $buildable + ->setBuildablePHID($object->getPHID()) + ->setContainerPHID($object->getRepository()->getPHID()); + } else { + $e_name = pht('Invalid'); + $errors[] = pht('Enter the name of a revision or commit.'); + } + } else { + $e_name = pht('Required'); + $errors[] = pht('You must choose a revision or commit to build.'); + } + + if (!$errors) { + $buildable->save(); + + $buildable_uri = '/B'.$buildable->getID(); + return id(new AphrontRedirectResponse())->setURI($buildable_uri); + } + } + + if ($errors) { + $errors = id(new AphrontErrorView())->setErrors($errors); + } + + $is_new = (!$buildable->getID()); + if ($is_new) { + $title = pht('New Buildable'); + $cancel_uri = $this->getApplicationURI(); + $save_button = pht('Create Buildable'); + } else { + $id = $buildable->getID(); + + $title = pht('Edit Buildable'); + $cancel_uri = "/B{$id}"; + $save_button = pht('Save Buildable'); + } + + $form = id(new AphrontFormView()) + ->setUser($viewer); + + if ($is_new) { + $form + ->appendRemarkupInstructions( + pht( + 'Enter the name of a commit or revision, like `rX123456789` '. + 'or `D123`.')) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('Buildable Name') + ->setName('buildablePHID') + ->setError($e_name) + ->setValue($v_name)); + } else { + $form->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Buildable')) + ->setValue($buildable->getBuildableHandle()->renderLink())); + } + + $form->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue($save_button) + ->addCancelButton($cancel_uri)); + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setFormError($errors) + ->setForm($form); + + $crumbs = $this->buildApplicationCrumbs(); + if ($is_new) { + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht('New Buildable'))); + } else { + $id = $buildable->getID(); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName("B{$id}") + ->setHref("/B{$id}")); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht('Edit'))); + } + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + ), + array( + 'title' => $title, + 'device' => true, + )); + } + +} Index: src/applications/harbormaster/controller/HarbormasterBuildableListController.php =================================================================== --- /dev/null +++ src/applications/harbormaster/controller/HarbormasterBuildableListController.php @@ -0,0 +1,78 @@ +queryKey = idx($data, 'queryKey'); + } + + public function processRequest() { + $request = $this->getRequest(); + $controller = id(new PhabricatorApplicationSearchController($request)) + ->setQueryKey($this->queryKey) + ->setSearchEngine(new HarbormasterBuildableSearchEngine()) + ->setNavigation($this->buildSideNavView()); + + return $this->delegateToController($controller); + } + + public function renderResultsList( + array $buildables, + PhabricatorSavedQuery $query) { + assert_instances_of($buildables, 'HarbormasterBuildable'); + + $viewer = $this->getRequest()->getUser(); + + $list = new PHUIObjectItemListView(); + foreach ($buildables as $buildable) { + $id = $buildable->getID(); + + $item = id(new PHUIObjectItemView()) + ->setHeader(pht('Build %d', $buildable->getID())); + + if ($id) { + $item->setHref("/B{$id}"); + } + + $list->addItem($item); + } + + return $list; + } + + public function buildSideNavView($for_app = false) { + $user = $this->getRequest()->getUser(); + + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); + + id(new HarbormasterBuildableSearchEngine()) + ->setViewer($user) + ->addNavigationItems($nav->getMenu()); + + if ($for_app) { + $nav->addFilter('new/', pht('New Build Plan')); + } + + $nav->addLabel('Utilities'); + $nav->addFilter('buildable/edit/', pht('New Manual Build')); + $nav->addFilter('plan/', pht('Manage Build Plans')); + + $nav->selectFilter(null); + + return $nav; + } + + public function buildApplicationMenu() { + return $this->buildSideNavView(true)->getMenu(); + } + +} Index: src/applications/harbormaster/controller/HarbormasterBuildableViewController.php =================================================================== --- /dev/null +++ src/applications/harbormaster/controller/HarbormasterBuildableViewController.php @@ -0,0 +1,105 @@ +id = $data['id']; + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $id = $this->id; + + $buildable = id(new HarbormasterBuildableQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->needBuildableHandles(true) + ->needContainerObjects(true) + ->executeOne(); + if (!$buildable) { + return new Aphront404Response(); + } + + $builds = id(new HarbormasterBuildQuery()) + ->setViewer($viewer) + ->withBuildablePHIDs(array($buildable->getPHID())) + ->needBuildPlans(true) + ->execute(); + + $build_list = id(new PHUIObjectItemListView()) + ->setUser($viewer); + foreach ($builds as $build) { + $item = id(new PHUIObjectItemView()) + ->setObjectName(pht('Build %d', $build->getID())) + ->setHeader($build->getName()); + $build_list->addItem($item); + } + + $title = pht("Buildable %d", $id); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setUser($viewer) + ->setPolicyObject($buildable); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header); + + $actions = $this->buildActionList($buildable); + $this->buildPropertyLists($box, $buildable, $actions); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName("B{$id}")); + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + $build_list, + ), + array( + 'title' => $title, + 'device' => true, + )); + } + + private function buildActionList(HarbormasterBuildable $buildable) { + $request = $this->getRequest(); + $viewer = $request->getUser(); + $id = $buildable->getID(); + + $list = id(new PhabricatorActionListView()) + ->setUser($viewer) + ->setObject($buildable) + ->setObjectURI("/B{$id}"); + + return $list; + } + + private function buildPropertyLists( + PHUIObjectBoxView $box, + HarbormasterBuildable $buildable, + PhabricatorActionListView $actions) { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setObject($buildable) + ->setActionList($actions); + $box->addPropertyList($properties); + + $properties->addProperty( + pht('Buildable'), + $buildable->getBuildableHandle()->renderLink()); + + } + +} Index: src/applications/harbormaster/controller/HarbormasterController.php =================================================================== --- /dev/null +++ src/applications/harbormaster/controller/HarbormasterController.php @@ -0,0 +1,17 @@ +addAction( + id(new PHUIListItemView()) + ->setName(pht('New Build Plan')) + ->setHref($this->getApplicationURI('plan/edit/')) + ->setIcon('create')); + + return $crumbs; + } + +} Index: src/applications/harbormaster/controller/HarbormasterPlanController.php =================================================================== --- /dev/null +++ src/applications/harbormaster/controller/HarbormasterPlanController.php @@ -0,0 +1,16 @@ +addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht('Build Plans')) + ->setHref($this->getApplicationURI('plan/'))); + + return $crumbs; + } + +} Index: src/applications/harbormaster/controller/HarbormasterPlanEditController.php =================================================================== --- /dev/null +++ src/applications/harbormaster/controller/HarbormasterPlanEditController.php @@ -0,0 +1,121 @@ +id = idx($data, 'id'); + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $this->requireApplicationCapability( + HarbormasterCapabilityManagePlans::CAPABILITY); + + if ($this->id) { + $plan = id(new HarbormasterBuildPlanQuery()) + ->setViewer($viewer) + ->withIDs(array($this->id)) + ->executeOne(); + if (!$plan) { + return new Aphront404Response(); + } + } else { + $plan = HarbormasterBuildPlan::initializeNewBuildPlan($viewer); + } + + $e_name = true; + $v_name = $plan->getName(); + $validation_exception = null; + if ($request->isFormPost()) { + $xactions = array(); + + $v_name = $request->getStr('name'); + $e_name = null; + $type_name = HarbormasterBuildPlanTransaction::TYPE_NAME; + + $xactions[] = id(new HarbormasterBuildPlanTransaction()) + ->setTransactionType($type_name) + ->setNewValue($v_name); + + $editor = id(new HarbormasterBuildPlanEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request); + + try { + $editor->applyTransactions($plan, $xactions); + return id(new AphrontRedirectResponse()) + ->setURI($this->getApplicationURI('plan/'.$plan->getID().'/')); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + + $e_name = $validation_exception->getShortMessage( + HarbormasterBuildPlanTransaction::TYPE_NAME); + } + + } + + $is_new = (!$plan->getID()); + if ($is_new) { + $title = pht('New Build Plan'); + $cancel_uri = $this->getApplicationURI(); + $save_button = pht('Create Build Plan'); + } else { + $id = $plan->getID(); + + $title = pht('Edit Build Plan'); + $cancel_uri = "/B{$id}"; + $save_button = pht('Save Build Plan'); + } + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('Plan Name') + ->setName('name') + ->setError($e_name) + ->setValue($v_name)); + + $form->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue($save_button) + ->addCancelButton($cancel_uri)); + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setValidationException($validation_exception) + ->setForm($form); + + $crumbs = $this->buildApplicationCrumbs(); + if ($is_new) { + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht('New Build Plan'))); + } else { + $id = $plan->getID(); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht("Plan %d", $id)) + ->setHref($this->getApplicationURI("plan/{$id}/"))); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht('Edit'))); + } + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + ), + array( + 'title' => $title, + 'device' => true, + )); + } + +} Index: src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php =================================================================== --- /dev/null +++ src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php @@ -0,0 +1,88 @@ +id = $data['id']; + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $this->requireApplicationCapability( + HarbormasterCapabilityManagePlans::CAPABILITY); + + $id = $this->id; + + $plan = id(new HarbormasterBuildPlanQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$plan) { + return new Aphront404Response(); + } + + $cancel_uri = $this->getApplicationURI("plan/{$id}/"); + + $v_buildable = null; + $e_buildable = null; + + $errors = array(); + if ($request->isFormPost()) { + $v_buildable = $request->getStr('buildable'); + + if ($v_buildable) { + $buildable = id(new HarbormasterBuildableQuery()) + ->setViewer($viewer) + ->withIDs(array(trim($v_buildable, 'B'))) + ->executeOne(); + if (!$buildable) { + $e_buildable = pht('Invalid'); + } + } else { + $e_buildable = pht('Required'); + $errors[] = pht('You must provide a buildable.'); + } + + if (!$errors) { + $build_plan = HarbormasterBuild::initializeNewBuild($viewer) + ->setBuildablePHID($buildable->getPHID()) + ->setBuildPlanPHID($plan->getPHID()) + ->save(); + + $buildable_id = $buildable->getID(); + + return id(new AphrontRedirectResponse()) + ->setURI("/B{$buildable_id}"); + } + } + + if ($errors) { + $errors = id(new AphrontErrorView())->setErrors($errors); + } + + $form = id(new PHUIFormLayoutView()) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Buildable')) + ->setName('buildable') + ->setValue($v_buildable) + ->setError($e_buildable)); + + $dialog = id(new AphrontDialogView()) + ->setUser($viewer) + ->setTitle(pht('Execute Build Plan')) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->appendChild($errors) + ->appendChild($form) + ->addSubmitButton(pht('Execute Build Plan')) + ->addCancelButton($cancel_uri); + + return id(new AphrontDialogResponse())->setDialog($dialog); + } + +} Index: src/applications/harbormaster/controller/HarbormasterPlanListController.php =================================================================== --- /dev/null +++ src/applications/harbormaster/controller/HarbormasterPlanListController.php @@ -0,0 +1,69 @@ +queryKey = idx($data, 'queryKey'); + } + + public function processRequest() { + $request = $this->getRequest(); + $controller = id(new PhabricatorApplicationSearchController($request)) + ->setQueryKey($this->queryKey) + ->setSearchEngine(new HarbormasterBuildPlanSearchEngine()) + ->setNavigation($this->buildSideNavView()); + + return $this->delegateToController($controller); + } + + public function renderResultsList( + array $plans, + PhabricatorSavedQuery $query) { + assert_instances_of($plans, 'HarbormasterBuildPlan'); + + $viewer = $this->getRequest()->getUser(); + + $list = new PHUIObjectItemListView(); + foreach ($plans as $plan) { + $id = $plan->getID(); + + $item = id(new PHUIObjectItemView()) + ->setObjectName(pht('Plan %d', $plan->getID())) + ->setHeader($plan->getName()); + + $item->setHref($this->getApplicationURI("plan/{$id}/")); + + $list->addItem($item); + } + + return $list; + } + + public function buildSideNavView($for_app = false) { + $user = $this->getRequest()->getUser(); + + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); + + id(new HarbormasterBuildPlanSearchEngine()) + ->setViewer($user) + ->addNavigationItems($nav->getMenu()); + + $nav->selectFilter(null); + + return $nav; + } + + public function buildApplicationMenu() { + return $this->buildSideNavView(true)->getMenu(); + } + +} Index: src/applications/harbormaster/controller/HarbormasterPlanViewController.php =================================================================== --- /dev/null +++ src/applications/harbormaster/controller/HarbormasterPlanViewController.php @@ -0,0 +1,121 @@ +id = $data['id']; + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $id = $this->id; + + $plan = id(new HarbormasterBuildPlanQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$plan) { + return new Aphront404Response(); + } + + $xactions = id(new HarbormasterBuildPlanTransactionQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($plan->getPHID())) + ->execute(); + + $engine = id(new PhabricatorMarkupEngine()) + ->setViewer($viewer); + + $xaction_view = id(new PhabricatorApplicationTransactionView()) + ->setUser($viewer) + ->setObjectPHID($plan->getPHID()) + ->setTransactions($xactions) + ->setMarkupEngine($engine); + + $title = pht("Plan %d", $id); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setUser($viewer) + ->setPolicyObject($plan); + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header); + + $actions = $this->buildActionList($plan); + $this->buildPropertyLists($box, $plan, $actions); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht("Plan %d", $id))); + + return $this->buildApplicationPage( + array( + $crumbs, + $box, + $xaction_view, + ), + array( + 'title' => $title, + 'device' => true, + )); + } + + private function buildActionList(HarbormasterBuildPlan $plan) { + $request = $this->getRequest(); + $viewer = $request->getUser(); + $id = $plan->getID(); + + $list = id(new PhabricatorActionListView()) + ->setUser($viewer) + ->setObject($plan) + ->setObjectURI($this->getApplicationURI("plan/{$id}/")); + + $can_edit = $this->hasApplicationCapability( + HarbormasterCapabilityManagePlans::CAPABILITY); + + $list->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Plan')) + ->setHref($this->getApplicationURI("plan/edit/{$id}/")) + ->setWorkflow(!$can_edit) + ->setDisabled(!$can_edit) + ->setIcon('edit')); + + $list->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Manually Execute Plan')) + ->setHref($this->getApplicationURI("plan/execute/{$id}/")) + ->setWorkflow(true) + ->setDisabled(!$can_edit) + ->setIcon('arrow_right')); + + return $list; + } + + private function buildPropertyLists( + PHUIObjectBoxView $box, + HarbormasterBuildPlan $plan, + PhabricatorActionListView $actions) { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setObject($plan) + ->setActionList($actions); + $box->addPropertyList($properties); + + $properties->addProperty( + pht('Created'), + phabricator_datetime($plan->getDateCreated(), $viewer)); + + } + +} Index: src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php =================================================================== --- /dev/null +++ src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php @@ -0,0 +1,91 @@ +getTransactionType()) { + case HarbormasterBuildPlanTransaction::TYPE_NAME: + if ($this->getIsNewObject()) { + return null; + } + return $object->getName(); + } + + return parent::getCustomTransactionOldValue($object, $xaction); + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + switch ($xaction->getTransactionType()) { + case HarbormasterBuildPlanTransaction::TYPE_NAME: + return $xaction->getNewValue(); + } + return parent::getCustomTransactionNewValue($object, $xaction); + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + switch ($xaction->getTransactionType()) { + case HarbormasterBuildPlanTransaction::TYPE_NAME: + $object->setName($xaction->getNewValue()); + return; + } + return parent::applyCustomInternalTransaction($object, $xaction); + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + switch ($xaction->getTransactionType()) { + case HarbormasterBuildPlanTransaction::TYPE_NAME: + return; + } + return parent::applyCustomExternalTransaction($object, $xaction); + } + + protected function validateTransaction( + PhabricatorLiskDAO $object, + $type, + array $xactions) { + + $errors = parent::validateTransaction($object, $type, $xactions); + + switch ($type) { + case HarbormasterBuildPlanTransaction::TYPE_NAME: + $missing_name = true; + if (strlen($object->getName()) && empty($xactions)) { + $missing_name = false; + } else if (strlen(last($xactions)->getNewValue())) { + $missing_name = false; + } + + if ($missing_name) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('Plan name is required.'), + last($xactions)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + } + break; + } + + return $errors; + } + + +} Index: src/applications/harbormaster/phid/HarbormasterPHIDTypeBuild.php =================================================================== --- /dev/null +++ src/applications/harbormaster/phid/HarbormasterPHIDTypeBuild.php @@ -0,0 +1,37 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $build_plan = $objects[$phid]; + } + } + +} Index: src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildItem.php =================================================================== --- /dev/null +++ src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildItem.php @@ -0,0 +1,37 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $build_item = $objects[$phid]; + } + } + +} Index: src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildPlan.php =================================================================== --- /dev/null +++ src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildPlan.php @@ -0,0 +1,37 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $build_plan = $objects[$phid]; + } + } + +} Index: src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildStep.php =================================================================== --- /dev/null +++ src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildStep.php @@ -0,0 +1,37 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $build_step = $objects[$phid]; + } + } + +} Index: src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildTarget.php =================================================================== --- /dev/null +++ src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildTarget.php @@ -0,0 +1,37 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $build_target = $objects[$phid]; + } + } + +} Index: src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildable.php =================================================================== --- /dev/null +++ src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildable.php @@ -0,0 +1,74 @@ +withPHIDs($phids) + ->needBuildableHandles(true); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $buildable = $objects[$phid]; + + $id = $buildable->getID(); + $target = $buildable->getBuildableHandle()->getFullName(); + + $handle->setURI("/B{$id}"); + $handle->setName("B{$id}"); + $handle->setFullName("B{$id}: ".$target); + } + } + + 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; + } + +} Index: src/applications/harbormaster/query/HarbormasterBuildItemQuery.php =================================================================== --- /dev/null +++ src/applications/harbormaster/query/HarbormasterBuildItemQuery.php @@ -0,0 +1,60 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + protected function loadPage() { + $table = new HarbormasterBuildItem(); + $conn_r = $table->establishConnection('r'); + + $data = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + return $table->loadAllFromArray($data); + } + + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { + $where = array(); + + if ($this->ids) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids) { + $where[] = qsprintf( + $conn_r, + 'phid in (%Ls)', + $this->phids); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationHarbormaster'; + } + +} Index: src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php =================================================================== --- /dev/null +++ src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php @@ -0,0 +1,61 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + protected function loadPage() { + $table = new HarbormasterBuildPlan(); + $conn_r = $table->establishConnection('r'); + + $data = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + return $table->loadAllFromArray($data); + } + + + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { + $where = array(); + + if ($this->ids) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids) { + $where[] = qsprintf( + $conn_r, + 'phid IN (%Ls)', + $this->phids); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationHarbormaster'; + } + +} Index: src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php =================================================================== --- /dev/null +++ src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php @@ -0,0 +1,49 @@ + pht('All Plans'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + +} Index: src/applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php =================================================================== --- /dev/null +++ src/applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php @@ -0,0 +1,10 @@ +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 withBuildPlanPHIDs(array $build_plan_phids) { + $this->buildPlanPHIDs = $build_plan_phids; + return $this; + } + + public function needBuildPlans($need_plans) { + $this->needBuildPlans = $need_plans; + return $this; + } + + protected function loadPage() { + $table = new HarbormasterBuild(); + $conn_r = $table->establishConnection('r'); + + $data = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + return $table->loadAllFromArray($data); + } + + 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 => $build) { + $buildable_phid = $build->getBuildablePHID(); + if (empty($buildables[$buildable_phid])) { + unset($page[$key]); + continue; + } + $build->attachBuildable($buildables[$buildable_phid]); + } + + return $page; + } + + protected function didFilterPage(array $page) { + if ($this->needBuildPlans) { + $plans = array(); + + $plan_phids = array_filter(mpull($page, 'getBuildPlanPHID')); + if ($plan_phids) { + $plans = id(new PhabricatorObjectQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs($plan_phids) + ->setParentQuery($this) + ->execute(); + $plans = mpull($plans, null, 'getPHID'); + } + + foreach ($page as $key => $build) { + $plan_phid = $build->getBuildPlanPHID(); + $build->attachBuildPlan(idx($plans, $plan_phid)); + } + } + + return $page; + } + + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { + $where = array(); + + if ($this->ids) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids) { + $where[] = qsprintf( + $conn_r, + 'phid in (%Ls)', + $this->phids); + } + + if ($this->buildablePHIDs) { + $where[] = qsprintf( + $conn_r, + 'buildablePHID IN (%Ls)', + $this->buildablePHIDs); + } + + if ($this->buildPlanPHIDs) { + $where[] = qsprintf( + $conn_r, + 'buildPlanPHID IN (%Ls)', + $this->buildPlanPHIDs); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationHarbormaster'; + } + +} Index: src/applications/harbormaster/query/HarbormasterBuildStepQuery.php =================================================================== --- /dev/null +++ src/applications/harbormaster/query/HarbormasterBuildStepQuery.php @@ -0,0 +1,60 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + protected function loadPage() { + $table = new HarbormasterBuildStep(); + $conn_r = $table->establishConnection('r'); + + $data = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + return $table->loadAllFromArray($data); + } + + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { + $where = array(); + + if ($this->ids) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids) { + $where[] = qsprintf( + $conn_r, + 'phid in (%Ls)', + $this->phids); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationHarbormaster'; + } + +} Index: src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php =================================================================== --- /dev/null +++ src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php @@ -0,0 +1,60 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + protected function loadPage() { + $table = new HarbormasterBuildTarget(); + $conn_r = $table->establishConnection('r'); + + $data = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + return $table->loadAllFromArray($data); + } + + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { + $where = array(); + + if ($this->ids) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids) { + $where[] = qsprintf( + $conn_r, + 'phid in (%Ls)', + $this->phids); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationHarbormaster'; + } + +} Index: src/applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php =================================================================== --- /dev/null +++ src/applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php @@ -0,0 +1,116 @@ +ids = $ids; + return $this; + } + + public function withBuildablePHIDs(array $buildable_phids) { + $this->buildablePHIDs = $buildable_phids; + return $this; + } + + public function withArtifactTypes(array $artifact_types) { + $this->artifactTypes = $artifact_types; + return $this; + } + + public function withArtifactKeys(array $artifact_keys) { + $this->artifactKeys = $artifact_keys; + return $this; + } + + protected function loadPage() { + $table = new HarbormasterBuildArtifact(); + $conn_r = $table->establishConnection('r'); + + $data = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + return $table->loadAllFromArray($data); + } + + 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 => $artifact) { + $buildable_phid = $artifact->getBuildablePHID(); + if (empty($buildables[$buildable_phid])) { + unset($page[$key]); + continue; + } + $artifact->attachBuildable($buildables[$buildable_phid]); + } + + return $page; + } + + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { + $where = array(); + + if ($this->ids) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->buildablePHIDs) { + $where[] = qsprintf( + $conn_r, + 'buildablePHID IN (%Ls)', + $this->buildablePHIDs); + } + + if ($this->artifactTypes) { + $where[] = qsprintf( + $conn_r, + 'artifactType in (%Ls)', + $this->artifactTypes); + } + + if ($this->artifactKeys) { + $indexes = array(); + foreach ($this->artifactKeys as $key) { + $indexes[] = PhabricatorHash::digestForIndex($key); + } + + $where[] = qsprintf( + $conn_r, + 'artifactIndex IN (%Ls)', + $indexes); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationHarbormaster'; + } + +} Index: src/applications/harbormaster/query/HarbormasterBuildableQuery.php =================================================================== --- /dev/null +++ src/applications/harbormaster/query/HarbormasterBuildableQuery.php @@ -0,0 +1,165 @@ +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 needContainerObjects($need) { + $this->needContainerObjects = $need; + return $this; + } + + public function needBuildableHandles($need) { + $this->needBuildableHandles = $need; + return $this; + } + + protected function loadPage() { + $table = new HarbormasterBuildable(); + $conn_r = $table->establishConnection('r'); + + $data = queryfx_all( + $conn_r, + 'SELECT * FROM %T %Q %Q %Q', + $table->getTableName(), + $this->buildWhereClause($conn_r), + $this->buildOrderClause($conn_r), + $this->buildLimitClause($conn_r)); + + return $table->loadAllFromArray($data); + } + + 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) { + $containers = array(); + + $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'); + } + + foreach ($page as $key => $buildable) { + $container_phid = $buildable->getContainerPHID(); + $buildable->attachContainerObject(idx($containers, $container_phid)); + } + } + + if ($this->needBuildableHandles) { + $handles = array(); + + $handle_phids = array_filter(mpull($page, 'getBuildablePHID')); + if ($handle_phids) { + $handles = id(new PhabricatorHandleQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs($handle_phids) + ->setParentQuery($this) + ->execute(); + } + + foreach ($page as $key => $buildable) { + $handle_phid = $buildable->getBuildablePHID(); + $buildable->attachBuildableHandle(idx($handles, $handle_phid)); + } + } + + return $page; + } + + private function buildWhereClause(AphrontDatabaseConnection $conn_r) { + $where = array(); + + if ($this->ids) { + $where[] = qsprintf( + $conn_r, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids) { + $where[] = qsprintf( + $conn_r, + 'phid IN (%Ls)', + $this->phids); + } + + if ($this->buildablePHIDs) { + $where[] = qsprintf( + $conn_r, + 'buildablePHID IN (%Ls)', + $this->buildablePHIDs); + } + + if ($this->containerPHIDs) { + $where[] = qsprintf( + $conn_r, + 'containerPHID in (%Ls)', + $this->containerPHIDs); + } + + $where[] = $this->buildPagingClause($conn_r); + + return $this->formatWhereClause($where); + } + + public function getQueryApplicationClass() { + return 'PhabricatorApplicationHarbormaster'; + } + +} Index: src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php =================================================================== --- /dev/null +++ src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php @@ -0,0 +1,49 @@ + pht('All Buildables'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + +} Index: src/applications/harbormaster/remarkup/HarbormasterRemarkupRule.php =================================================================== --- /dev/null +++ src/applications/harbormaster/remarkup/HarbormasterRemarkupRule.php @@ -0,0 +1,18 @@ +getEngine()->getConfig('viewer'); + return id(new HarbormasterBuildableQuery()) + ->setViewer($viewer) + ->withIDs($ids) + ->execute(); + } + +} Index: src/applications/harbormaster/storage/HarbormasterBuildable.php =================================================================== --- /dev/null +++ src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -0,0 +1,85 @@ +setBuildStatus('new') // TODO: Define these. + ->setBuildableStatus('active'); // TODO: Define these, too. + } + + public function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + HarbormasterPHIDTypeBuildable::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 attachBuildableHandle($buildable_handle) { + $this->buildableHandle = $buildable_handle; + return $this; + } + + public function getBuildableHandle() { + return $this->assertAttached($this->buildableHandle); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + 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( + 'Users must be able to see the revision or repository to see a '. + 'buildable.'); + } + +} Index: src/applications/harbormaster/storage/build/HarbormasterBuild.php =================================================================== --- /dev/null +++ src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -0,0 +1,80 @@ +setBuildStatus('building'); // TODO: Sort this. + } + + public function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + HarbormasterPHIDTypeBuildPlan::TYPECONST); + } + + public function attachBuildable(HarbormasterBuildable $buildable) { + $this->buildable = $buildable; + return $this; + } + + public function getBuildable() { + return $this->assertAttached($this->buildable); + } + + public function getName() { + if ($this->getBuildPlan()) { + return $this->getBuildPlan()->getName(); + } + return pht('Build'); + } + + public function attachBuildPlan( + HarbormasterBuildPlan $build_plan = null) { + $this->buildPlan = $build_plan; + return $this; + } + + public function getBuildPlan() { + return $this->assertAttached($this->buildPlan); + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return $this->getBuildable()->getPolicy($capability); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return $this->getBuildable()->hasAutomaticCapability( + $capability, + $viewer); + } + + public function describeAutomaticCapability($capability) { + return pht( + 'Users must be able to see a buildable to view its build plans.'); + } + +} Index: src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php =================================================================== --- /dev/null +++ src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php @@ -0,0 +1,60 @@ + array( + 'artifactData' => self::SERIALIZATION_JSON, + ), + ) + parent::getConfiguration(); + } + + public function attachBuildable(HarbormasterBuildable $buildable) { + $this->buildable = $buildable; + return $this; + } + + public function getBuildable() { + return $this->assertAttached($this->buildable); + } + + public function setArtifactKey($key) { + $this->artifactIndex = PhabricatorHash::digestForIndex($key); + $this->artifactKey = $key; + return $this; + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return $this->getBuildable()->getPolicy($capability); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return $this->getBuildable()->hasAutomaticCapability( + $capability, + $viewer); + } + + public function describeAutomaticCapability($capability) { + return pht( + 'Users must be able to see a buildable to see its artifacts.'); + } + +} Index: src/applications/harbormaster/storage/build/HarbormasterBuildItem.php =================================================================== --- /dev/null +++ src/applications/harbormaster/storage/build/HarbormasterBuildItem.php @@ -0,0 +1,18 @@ + true, + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + HarbormasterPHIDTypeBuildItem::TYPECONST); + } + +} Index: src/applications/harbormaster/storage/build/HarbormasterBuildLog.php =================================================================== --- /dev/null +++ src/applications/harbormaster/storage/build/HarbormasterBuildLog.php @@ -0,0 +1,7 @@ + true, + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + HarbormasterPHIDTypeBuildTarget::TYPECONST); + } + +} Index: src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php =================================================================== --- /dev/null +++ src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php @@ -0,0 +1,71 @@ +setPlanStatus('active'); // TODO: Figure this out. + } + + public function getConfiguration() { + return array( + self::CONFIG_AUX_PHID => true, + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + HarbormasterPHIDTypeBuildPlan::TYPECONST); + } + + public function attachBuildSteps(array $steps) { + assert_instances_of($steps, 'HarbormasterBuildStep'); + $this->buildSteps = $steps; + return $this; + } + + public function getBuildSteps() { + return $this->assertAttached($this->buildSteps); + } + + +/* -( PhabricatorSubscribableInterface )----------------------------------- */ + + + public function isAutomaticallySubscribed($phid) { + return false; + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + return PhabricatorPolicies::getMostOpenPolicy(); + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } +} Index: src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php =================================================================== --- /dev/null +++ src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php @@ -0,0 +1,74 @@ +getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_NAME: + if ($old === null) { + return 'create'; + } + break; + } + + return parent::getIcon(); + } + + public function getColor() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_NAME: + if ($old === null) { + return 'green'; + } + break; + } + + return parent::getIcon(); + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + $author_handle = $this->renderHandleLink($this->getAuthorPHID()); + + switch ($this->getTransactionType()) { + case self::TYPE_NAME: + if ($old === null) { + return pht( + '%s created this build plan.', + $author_handle); + } else { + return pht( + '%s renamed this build plan from "%s" to "%s".', + $author_handle, + $old, + $new); + } + break; + } + + return parent::getTitle(); + } + +} Index: src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransactionComment.php =================================================================== --- /dev/null +++ src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransactionComment.php @@ -0,0 +1,10 @@ + true, + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + HarbormasterPHIDTypeBuildStep::TYPECONST); + } + + public function attachBuildPlan(HarbormasterBuildPlan $plan) { + $this->buildPlan = $plan; + return $this; + } + + public function getBuildPlan() { + return $this->assertAttached($this->buildPlan); + } + +} Index: src/applications/policy/controller/PhabricatorPolicyExplainController.php =================================================================== --- src/applications/policy/controller/PhabricatorPolicyExplainController.php +++ src/applications/policy/controller/PhabricatorPolicyExplainController.php @@ -43,7 +43,7 @@ ->setViewer($viewer) ->withPHIDs(array($phid)) ->executeOne(); - $object_uri = $handle->getURI(); + $object_uri = nonempty($handle->getURI(), '/'); $explanation = PhabricatorPolicy::getPolicyExplanation( $viewer, Index: src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php =================================================================== --- src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -1700,6 +1700,10 @@ 'type' => 'php', 'name' => $this->getPatchPath('20131020.pxactionmig.php'), ), + '20131020.harbormaster.sql' => array( + 'type' => 'sql', + 'name' => $this->getPatchPath('20131020.harbormaster.sql'), + ), ); } }