mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-25 16:22:43 +01:00
Restructure Drydock so that blueprints are instances in the DB
Summary: //(this diff used to be about applying policies to blueprints)// This restructures Drydock so that blueprints are instances in the DB, with an associated implementation class. Thus resources now have a `blueprintPHID` instead of `blueprintClass` and DrydockBlueprint becomes a DAO. The old DrydockBlueprint is renamed to DrydockBlueprintImplementation, and the DrydockBlueprint DAO has a `blueprintClass` column on it. This now just implements CAN_VIEW and CAN_EDIT policies for blueprints, although they are probably not enforced in all of the places they could be. Test Plan: Used the `create-resource` and `lease` commands. Closed resources and leases in the UI. Clicked around the new and old lists to make sure everything is still working. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley CC: Korvin, epriestley, aran Maniphest Tasks: T4111, T2015 Differential Revision: https://secure.phabricator.com/D7638
This commit is contained in:
parent
f93c6985ad
commit
ba16df0fed
25 changed files with 729 additions and 41 deletions
11
resources/sql/patches/20131123.drydockblueprintpolicy.sql
Normal file
11
resources/sql/patches/20131123.drydockblueprintpolicy.sql
Normal file
|
@ -0,0 +1,11 @@
|
|||
CREATE TABLE {$NAMESPACE}_drydock.drydock_blueprint (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
className VARCHAR(255) NOT NULL COLLATE utf8_bin,
|
||||
viewPolicy VARCHAR(64) NOT NULL,
|
||||
editPolicy VARCHAR(64) NOT NULL,
|
||||
details LONGTEXT CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
UNIQUE KEY `key_phid` (phid)
|
||||
) ENGINE=InnoDB, COLLATE utf8_general_ci;
|
|
@ -0,0 +1,5 @@
|
|||
ALTER TABLE {$NAMESPACE}_drydock.drydock_resource
|
||||
ADD COLUMN blueprintPHID VARCHAR(64) NOT NULL COLLATE utf8_bin;
|
||||
|
||||
ALTER TABLE {$NAMESPACE}_drydock.drydock_resource
|
||||
DROP COLUMN blueprintClass;
|
|
@ -626,8 +626,14 @@ phutil_register_library_map(array(
|
|||
'DoorkeeperTagsController' => 'applications/doorkeeper/controller/DoorkeeperTagsController.php',
|
||||
'DrydockAllocatorWorker' => 'applications/drydock/worker/DrydockAllocatorWorker.php',
|
||||
'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php',
|
||||
'DrydockBlueprint' => 'applications/drydock/blueprint/DrydockBlueprint.php',
|
||||
'DrydockBlueprint' => 'applications/drydock/storage/DrydockBlueprint.php',
|
||||
'DrydockBlueprintCreateController' => 'applications/drydock/controller/DrydockBlueprintCreateController.php',
|
||||
'DrydockBlueprintEditController' => 'applications/drydock/controller/DrydockBlueprintEditController.php',
|
||||
'DrydockBlueprintImplementation' => 'applications/drydock/blueprint/DrydockBlueprintImplementation.php',
|
||||
'DrydockBlueprintListController' => 'applications/drydock/controller/DrydockBlueprintListController.php',
|
||||
'DrydockBlueprintQuery' => 'applications/drydock/query/DrydockBlueprintQuery.php',
|
||||
'DrydockBlueprintScopeGuard' => 'applications/drydock/util/DrydockBlueprintScopeGuard.php',
|
||||
'DrydockBlueprintViewController' => 'applications/drydock/controller/DrydockBlueprintViewController.php',
|
||||
'DrydockCommandInterface' => 'applications/drydock/interface/command/DrydockCommandInterface.php',
|
||||
'DrydockConstants' => 'applications/drydock/constants/DrydockConstants.php',
|
||||
'DrydockController' => 'applications/drydock/controller/DrydockController.php',
|
||||
|
@ -640,7 +646,7 @@ phutil_register_library_map(array(
|
|||
'DrydockLeaseStatus' => 'applications/drydock/constants/DrydockLeaseStatus.php',
|
||||
'DrydockLeaseViewController' => 'applications/drydock/controller/DrydockLeaseViewController.php',
|
||||
'DrydockLocalCommandInterface' => 'applications/drydock/interface/command/DrydockLocalCommandInterface.php',
|
||||
'DrydockLocalHostBlueprint' => 'applications/drydock/blueprint/DrydockLocalHostBlueprint.php',
|
||||
'DrydockLocalHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockLocalHostBlueprintImplementation.php',
|
||||
'DrydockLog' => 'applications/drydock/storage/DrydockLog.php',
|
||||
'DrydockLogController' => 'applications/drydock/controller/DrydockLogController.php',
|
||||
'DrydockLogQuery' => 'applications/drydock/query/DrydockLogQuery.php',
|
||||
|
@ -650,7 +656,8 @@ phutil_register_library_map(array(
|
|||
'DrydockManagementReleaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseWorkflow.php',
|
||||
'DrydockManagementWaitForLeaseWorkflow' => 'applications/drydock/management/DrydockManagementWaitForLeaseWorkflow.php',
|
||||
'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php',
|
||||
'DrydockPreallocatedHostBlueprint' => 'applications/drydock/blueprint/DrydockPreallocatedHostBlueprint.php',
|
||||
'DrydockPHIDTypeBlueprint' => 'applications/drydock/phid/DrydockPHIDTypeBlueprint.php',
|
||||
'DrydockPreallocatedHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php',
|
||||
'DrydockResource' => 'applications/drydock/storage/DrydockResource.php',
|
||||
'DrydockResourceCloseController' => 'applications/drydock/controller/DrydockResourceCloseController.php',
|
||||
'DrydockResourceListController' => 'applications/drydock/controller/DrydockResourceListController.php',
|
||||
|
@ -659,7 +666,7 @@ phutil_register_library_map(array(
|
|||
'DrydockResourceViewController' => 'applications/drydock/controller/DrydockResourceViewController.php',
|
||||
'DrydockSSHCommandInterface' => 'applications/drydock/interface/command/DrydockSSHCommandInterface.php',
|
||||
'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php',
|
||||
'DrydockWorkingCopyBlueprint' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprint.php',
|
||||
'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php',
|
||||
'FeedPublisherHTTPWorker' => 'applications/feed/worker/FeedPublisherHTTPWorker.php',
|
||||
'FeedPublisherWorker' => 'applications/feed/worker/FeedPublisherWorker.php',
|
||||
'FeedPushWorker' => 'applications/feed/worker/FeedPushWorker.php',
|
||||
|
@ -2951,6 +2958,16 @@ phutil_register_library_map(array(
|
|||
'DoorkeeperTagsController' => 'PhabricatorController',
|
||||
'DrydockAllocatorWorker' => 'PhabricatorWorker',
|
||||
'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface',
|
||||
'DrydockBlueprint' =>
|
||||
array(
|
||||
0 => 'DrydockDAO',
|
||||
1 => 'PhabricatorPolicyInterface',
|
||||
),
|
||||
'DrydockBlueprintCreateController' => 'DrydockController',
|
||||
'DrydockBlueprintEditController' => 'DrydockController',
|
||||
'DrydockBlueprintListController' => 'DrydockController',
|
||||
'DrydockBlueprintQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'DrydockBlueprintViewController' => 'DrydockController',
|
||||
'DrydockCommandInterface' => 'DrydockInterface',
|
||||
'DrydockController' => 'PhabricatorController',
|
||||
'DrydockDAO' => 'PhabricatorLiskDAO',
|
||||
|
@ -2961,7 +2978,7 @@ phutil_register_library_map(array(
|
|||
'DrydockLeaseStatus' => 'DrydockConstants',
|
||||
'DrydockLeaseViewController' => 'DrydockController',
|
||||
'DrydockLocalCommandInterface' => 'DrydockCommandInterface',
|
||||
'DrydockLocalHostBlueprint' => 'DrydockBlueprint',
|
||||
'DrydockLocalHostBlueprintImplementation' => 'DrydockBlueprintImplementation',
|
||||
'DrydockLog' => 'DrydockDAO',
|
||||
'DrydockLogController' => 'DrydockController',
|
||||
'DrydockLogQuery' => 'PhabricatorOffsetPagedQuery',
|
||||
|
@ -2971,7 +2988,8 @@ phutil_register_library_map(array(
|
|||
'DrydockManagementReleaseWorkflow' => 'DrydockManagementWorkflow',
|
||||
'DrydockManagementWaitForLeaseWorkflow' => 'DrydockManagementWorkflow',
|
||||
'DrydockManagementWorkflow' => 'PhutilArgumentWorkflow',
|
||||
'DrydockPreallocatedHostBlueprint' => 'DrydockBlueprint',
|
||||
'DrydockPHIDTypeBlueprint' => 'PhabricatorPHIDType',
|
||||
'DrydockPreallocatedHostBlueprintImplementation' => 'DrydockBlueprintImplementation',
|
||||
'DrydockResource' =>
|
||||
array(
|
||||
0 => 'DrydockDAO',
|
||||
|
@ -2984,7 +3002,7 @@ phutil_register_library_map(array(
|
|||
'DrydockResourceViewController' => 'DrydockController',
|
||||
'DrydockSSHCommandInterface' => 'DrydockCommandInterface',
|
||||
'DrydockWebrootInterface' => 'DrydockInterface',
|
||||
'DrydockWorkingCopyBlueprint' => 'DrydockBlueprint',
|
||||
'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation',
|
||||
'FeedPublisherHTTPWorker' => 'FeedPushWorker',
|
||||
'FeedPublisherWorker' => 'FeedPushWorker',
|
||||
'FeedPushWorker' => 'PhabricatorWorker',
|
||||
|
|
|
@ -34,6 +34,12 @@ final class PhabricatorApplicationDrydock extends PhabricatorApplication {
|
|||
return array(
|
||||
'/drydock/' => array(
|
||||
'' => 'DrydockResourceListController',
|
||||
'blueprint/' => array(
|
||||
'' => 'DrydockBlueprintListController',
|
||||
'(?P<id>[1-9]\d*)/' => 'DrydockBlueprintViewController',
|
||||
'create/' => 'DrydockBlueprintCreateController',
|
||||
'edit/(?P<id>[1-9]\d*)/' => 'DrydockBlueprintEditController',
|
||||
),
|
||||
'resource/' => array(
|
||||
'' => 'DrydockResourceListController',
|
||||
'(?P<id>[1-9]\d*)/' => 'DrydockResourceViewController',
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
* @task resource Resource Allocation
|
||||
* @task log Logging
|
||||
*/
|
||||
abstract class DrydockBlueprint {
|
||||
abstract class DrydockBlueprintImplementation {
|
||||
|
||||
private $activeResource;
|
||||
private $activeLease;
|
||||
private $instance;
|
||||
|
||||
abstract public function getType();
|
||||
abstract public function getInterface(
|
||||
|
@ -18,6 +19,8 @@ abstract class DrydockBlueprint {
|
|||
|
||||
abstract public function isEnabled();
|
||||
|
||||
abstract public function getDescription();
|
||||
|
||||
public function getBlueprintClass() {
|
||||
return get_class($this);
|
||||
}
|
||||
|
@ -37,6 +40,20 @@ abstract class DrydockBlueprint {
|
|||
return $lease;
|
||||
}
|
||||
|
||||
protected function getInstance() {
|
||||
if (!$this->instance) {
|
||||
throw new Exception(
|
||||
"Attach the blueprint instance to the implementation.");
|
||||
}
|
||||
|
||||
return $this->instance;
|
||||
}
|
||||
|
||||
public function attachInstance(DrydockBlueprint $instance) {
|
||||
$this->instance = $instance;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/* -( Lease Acquisition )-------------------------------------------------- */
|
||||
|
||||
|
@ -343,13 +360,13 @@ abstract class DrydockBlueprint {
|
|||
}
|
||||
|
||||
|
||||
public static function getAllBlueprints() {
|
||||
public static function getAllBlueprintImplementations() {
|
||||
static $list = null;
|
||||
|
||||
if ($list === null) {
|
||||
$blueprints = id(new PhutilSymbolLoader())
|
||||
->setType('class')
|
||||
->setAncestorClass('DrydockBlueprint')
|
||||
->setAncestorClass('DrydockBlueprintImplementation')
|
||||
->setConcreteOnly(true)
|
||||
->selectAndLoadSymbols();
|
||||
$list = ipull($blueprints, 'name', 'name');
|
||||
|
@ -361,16 +378,17 @@ abstract class DrydockBlueprint {
|
|||
return $list;
|
||||
}
|
||||
|
||||
public static function getAllBlueprintsForResource($type) {
|
||||
public static function getAllBlueprintImplementationsForResource($type) {
|
||||
static $groups = null;
|
||||
if ($groups === null) {
|
||||
$groups = mgroup(self::getAllBlueprints(), 'getType');
|
||||
$groups = mgroup(self::getAllBlueprintImplementations(), 'getType');
|
||||
}
|
||||
return idx($groups, $type, array());
|
||||
}
|
||||
|
||||
protected function newResourceTemplate($name) {
|
||||
$resource = new DrydockResource();
|
||||
$resource->setBlueprintPHID($this->getInstance()->getPHID());
|
||||
$resource->setBlueprintClass($this->getBlueprintClass());
|
||||
$resource->setType($this->getType());
|
||||
$resource->setStatus(DrydockResourceStatus::STATUS_PENDING);
|
|
@ -1,11 +1,16 @@
|
|||
<?php
|
||||
|
||||
final class DrydockLocalHostBlueprint extends DrydockBlueprint {
|
||||
final class DrydockLocalHostBlueprintImplementation
|
||||
extends DrydockBlueprintImplementation {
|
||||
|
||||
public function isEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getDescription() {
|
||||
return pht('Allocates storage on the local host.');
|
||||
}
|
||||
|
||||
public function canAllocateMoreResources(array $pool) {
|
||||
assert_instances_of($pool, 'DrydockResource');
|
||||
|
|
@ -1,11 +1,16 @@
|
|||
<?php
|
||||
|
||||
final class DrydockPreallocatedHostBlueprint extends DrydockBlueprint {
|
||||
final class DrydockPreallocatedHostBlueprintImplementation
|
||||
extends DrydockBlueprintImplementation {
|
||||
|
||||
public function isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getDescription() {
|
||||
return pht('Leases out preallocated, remote hosts.');
|
||||
}
|
||||
|
||||
public function canAllocateMoreResources(array $pool) {
|
||||
return false;
|
||||
}
|
|
@ -1,11 +1,16 @@
|
|||
<?php
|
||||
|
||||
final class DrydockWorkingCopyBlueprint extends DrydockBlueprint {
|
||||
final class DrydockWorkingCopyBlueprintImplementation
|
||||
extends DrydockBlueprintImplementation {
|
||||
|
||||
public function isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getDescription() {
|
||||
return pht('Allocates out working copies of repositories.');
|
||||
}
|
||||
|
||||
protected function canAllocateLease(
|
||||
DrydockResource $resource,
|
||||
DrydockLease $lease) {
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
final class DrydockBlueprintCreateController
|
||||
extends DrydockController {
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$implementations =
|
||||
DrydockBlueprintImplementation::getAllBlueprintImplementations();
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$class = $request->getStr('blueprint-type');
|
||||
if (!isset($implementations[$class])) {
|
||||
return $this->createDialog($implementations);
|
||||
}
|
||||
|
||||
$blueprint = new DrydockBlueprint();
|
||||
$blueprint->setClassName($class);
|
||||
$blueprint->setDetails(array());
|
||||
$blueprint->setViewPolicy(PhabricatorPolicies::POLICY_ADMIN);
|
||||
$blueprint->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN);
|
||||
$blueprint->save();
|
||||
|
||||
$edit_uri = $this->getApplicationURI(
|
||||
"blueprint/edit/".$blueprint->getID()."/");
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($edit_uri);
|
||||
}
|
||||
|
||||
return $this->createDialog($implementations);
|
||||
}
|
||||
|
||||
function createDialog(array $implementations) {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$control = id(new AphrontFormRadioButtonControl())
|
||||
->setName('blueprint-type');
|
||||
|
||||
foreach ($implementations as $implementation_name => $implementation) {
|
||||
$control
|
||||
->addButton(
|
||||
$implementation_name,
|
||||
$implementation->getBlueprintClass(),
|
||||
$implementation->getDescription());
|
||||
}
|
||||
|
||||
$dialog = new AphrontDialogView();
|
||||
$dialog->setTitle(pht('Create New Blueprint'))
|
||||
->setUser($viewer)
|
||||
->addSubmitButton(pht('Create Blueprint'))
|
||||
->addCancelButton($this->getApplicationURI('blueprint/'));
|
||||
$dialog->appendChild(
|
||||
phutil_tag(
|
||||
'p',
|
||||
array(),
|
||||
pht(
|
||||
'Select what type of blueprint you want to create: ')));
|
||||
$dialog->appendChild($control);
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
final class DrydockBlueprintEditController extends DrydockController {
|
||||
|
||||
private $id;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = idx($data, 'id');
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
if ($this->id) {
|
||||
$blueprint = id(new DrydockBlueprintQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($this->id))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$blueprint) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
} else {
|
||||
$blueprint = new DrydockBlueprint();
|
||||
}
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$v_view_policy = $request->getStr('viewPolicy');
|
||||
$v_edit_policy = $request->getStr('editPolicy');
|
||||
|
||||
// TODO: Should we use transactions here?
|
||||
$blueprint->setViewPolicy($v_view_policy);
|
||||
$blueprint->setEditPolicy($v_edit_policy);
|
||||
|
||||
$blueprint->save();
|
||||
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI('/drydock/blueprint/');
|
||||
}
|
||||
|
||||
$policies = id(new PhabricatorPolicyQuery())
|
||||
->setViewer($viewer)
|
||||
->setObject($blueprint)
|
||||
->execute();
|
||||
|
||||
if ($request->isAjax()) {
|
||||
$form = id(new PHUIFormLayoutView())
|
||||
->setUser($viewer);
|
||||
} else {
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer);
|
||||
}
|
||||
|
||||
$form
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setName('className')
|
||||
->setLabel(pht('Implementation'))
|
||||
->setValue($blueprint->getClassName())
|
||||
->setDisabled(true))
|
||||
->appendChild(
|
||||
id(new AphrontFormPolicyControl())
|
||||
->setName('viewPolicy')
|
||||
->setPolicyObject($blueprint)
|
||||
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
|
||||
->setPolicies($policies))
|
||||
->appendChild(
|
||||
id(new AphrontFormPolicyControl())
|
||||
->setName('editPolicy')
|
||||
->setPolicyObject($blueprint)
|
||||
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
|
||||
->setPolicies($policies));
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
|
||||
$title = pht('Edit Blueprint');
|
||||
$header = pht('Edit Blueprint %d', $blueprint->getID());
|
||||
$crumbs->addCrumb(
|
||||
id(new PhabricatorCrumbView())
|
||||
->setName(pht('Blueprint %d', $blueprint->getID())));
|
||||
$crumbs->addCrumb(
|
||||
id(new PhabricatorCrumbView())
|
||||
->setName(pht('Edit')));
|
||||
|
||||
if ($request->isAjax()) {
|
||||
$dialog = id(new AphrontDialogView())
|
||||
->setUser($viewer)
|
||||
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||
->setTitle($title)
|
||||
->appendChild($form)
|
||||
->addSubmitButton(pht('Edit Blueprint'))
|
||||
->addCancelButton($this->getApplicationURI());
|
||||
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
|
||||
$form->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue(pht('Save'))
|
||||
->addCancelButton($this->getApplicationURI()));
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText($header)
|
||||
->setForm($form);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$box,
|
||||
),
|
||||
array(
|
||||
'title' => $title,
|
||||
'device' => true,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
final class DrydockBlueprintListController extends DrydockController {
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
|
||||
$title = pht('Blueprints');
|
||||
|
||||
$blueprint_header = id(new PHUIHeaderView())
|
||||
->setHeader($title);
|
||||
|
||||
$blueprints = id(new DrydockBlueprintQuery())
|
||||
->setViewer($user)
|
||||
->execute();
|
||||
|
||||
$blueprint_list = $this->buildBlueprintListView($blueprints);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addCrumb(
|
||||
id(new PhabricatorCrumbView())
|
||||
->setName($title)
|
||||
->setHref($request->getRequestURI()));
|
||||
|
||||
$crumbs->addAction(
|
||||
id(new PHUIListItemView())
|
||||
->setName(pht('New Blueprint'))
|
||||
->setHref($this->getApplicationURI('blueprint/create/'))
|
||||
->setIcon('create'));
|
||||
|
||||
$nav = $this->buildSideNav('blueprint');
|
||||
$nav->setCrumbs($crumbs);
|
||||
$nav->appendChild(
|
||||
array(
|
||||
$blueprint_header,
|
||||
$blueprint_list
|
||||
));
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
$nav,
|
||||
array(
|
||||
'title' => $title,
|
||||
'device' => true,
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
protected function buildBlueprintListView(array $blueprints) {
|
||||
assert_instances_of($blueprints, 'DrydockBlueprint');
|
||||
|
||||
$user = $this->getRequest()->getUser();
|
||||
$view = new PHUIObjectItemListView();
|
||||
|
||||
foreach ($blueprints as $blueprint) {
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setHeader($blueprint->getClassName())
|
||||
->setHref($this->getApplicationURI('/blueprint/'.$blueprint->getID()))
|
||||
->setObjectName(pht('Blueprint %d', $blueprint->getID()));
|
||||
|
||||
if ($blueprint->getImplementation()->isEnabled()) {
|
||||
$item->addAttribute(pht('Enabled'));
|
||||
$item->setBarColor('green');
|
||||
} else {
|
||||
$item->addAttribute(pht('Disabled'));
|
||||
$item->setBarColor('red');
|
||||
}
|
||||
|
||||
$item->addAttribute($blueprint->getImplementation()->getDescription());
|
||||
|
||||
$view->addItem($item);
|
||||
}
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
final class DrydockBlueprintViewController extends DrydockController {
|
||||
|
||||
private $id;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = $data['id'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
|
||||
$blueprint = id(new DrydockBlueprint())->load($this->id);
|
||||
if (!$blueprint) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$title = 'Blueprint '.$blueprint->getID().' '.$blueprint->getClassName();
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($title);
|
||||
|
||||
$actions = $this->buildActionListView($blueprint);
|
||||
$properties = $this->buildPropertyListView($blueprint, $actions);
|
||||
|
||||
$blueprint_uri = 'blueprint/'.$blueprint->getID().'/';
|
||||
$blueprint_uri = $this->getApplicationURI($blueprint_uri);
|
||||
|
||||
$resources = id(new DrydockResourceQuery())
|
||||
->withBlueprintPHIDs(array($blueprint->getPHID()))
|
||||
->setViewer($user)
|
||||
->execute();
|
||||
|
||||
$resource_list = $this->buildResourceListView($resources);
|
||||
$resource_list->setNoDataString(pht('This blueprint has no resources.'));
|
||||
|
||||
$pager = new AphrontPagerView();
|
||||
$pager->setURI(new PhutilURI($blueprint_uri), 'offset');
|
||||
$pager->setOffset($request->getInt('offset'));
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->setActionList($actions);
|
||||
$crumbs->addCrumb(
|
||||
id(new PhabricatorCrumbView())
|
||||
->setName(pht('Blueprint %d', $blueprint->getID())));
|
||||
|
||||
$object_box = id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->addPropertyList($properties);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$object_box,
|
||||
$resource_list
|
||||
),
|
||||
array(
|
||||
'device' => true,
|
||||
'title' => $title,
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
private function buildActionListView(DrydockBlueprint $blueprint) {
|
||||
$view = id(new PhabricatorActionListView())
|
||||
->setUser($this->getRequest()->getUser())
|
||||
->setObjectURI($this->getRequest()->getRequestURI())
|
||||
->setObject($blueprint);
|
||||
|
||||
$uri = '/blueprint/edit/'.$blueprint->getID().'/';
|
||||
$uri = $this->getApplicationURI($uri);
|
||||
|
||||
$view->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setHref($uri)
|
||||
->setName(pht('Edit Blueprint Policies'))
|
||||
->setIcon('edit')
|
||||
->setWorkflow(true)
|
||||
->setDisabled(false));
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
private function buildPropertyListView(
|
||||
DrydockBlueprint $blueprint,
|
||||
PhabricatorActionListView $actions) {
|
||||
|
||||
$view = new PHUIPropertyListView();
|
||||
$view->setActionList($actions);
|
||||
|
||||
$view->addProperty(
|
||||
pht('Implementation'),
|
||||
$blueprint->getClassName());
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
}
|
|
@ -5,9 +5,10 @@ abstract class DrydockController extends PhabricatorController {
|
|||
final protected function buildSideNav($selected) {
|
||||
$nav = new AphrontSideNavFilterView();
|
||||
$nav->setBaseURI(new PhutilURI('/drydock/'));
|
||||
$nav->addFilter('resource', 'Resources');
|
||||
$nav->addFilter('lease', 'Leases');
|
||||
$nav->addFilter('log', 'Logs');
|
||||
$nav->addFilter('blueprint', 'Blueprints');
|
||||
$nav->addFilter('resource', 'Resources');
|
||||
$nav->addFilter('lease', 'Leases');
|
||||
$nav->addFilter('log', 'Logs');
|
||||
|
||||
$nav->selectFilter($selected, 'resource');
|
||||
|
||||
|
|
|
@ -33,9 +33,6 @@ final class DrydockResourceViewController extends DrydockController {
|
|||
->needResources(true)
|
||||
->execute();
|
||||
|
||||
$lease_header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Leases'));
|
||||
|
||||
$lease_list = $this->buildLeaseListView($leases);
|
||||
$lease_list->setNoDataString(pht('This resource has no leases.'));
|
||||
|
||||
|
@ -64,7 +61,6 @@ final class DrydockResourceViewController extends DrydockController {
|
|||
array(
|
||||
$crumbs,
|
||||
$object_box,
|
||||
$lease_header,
|
||||
$lease_list,
|
||||
$log_table,
|
||||
),
|
||||
|
@ -114,6 +110,11 @@ final class DrydockResourceViewController extends DrydockController {
|
|||
pht('Resource Type'),
|
||||
$resource->getType());
|
||||
|
||||
// TODO: Load handle.
|
||||
$view->addProperty(
|
||||
pht('Blueprint'),
|
||||
$resource->getBlueprintPHID());
|
||||
|
||||
$attributes = $resource->getAttributes();
|
||||
if ($attributes) {
|
||||
$view->addSectionHeader(pht('Attributes'));
|
||||
|
|
|
@ -16,8 +16,8 @@ final class DrydockManagementCreateResourceWorkflow
|
|||
),
|
||||
array(
|
||||
'name' => 'blueprint',
|
||||
'param' => 'blueprint_type',
|
||||
'help' => 'Blueprint type.',
|
||||
'param' => 'blueprint_id',
|
||||
'help' => 'Blueprint ID.',
|
||||
),
|
||||
array(
|
||||
'name' => 'attributes',
|
||||
|
@ -36,10 +36,10 @@ final class DrydockManagementCreateResourceWorkflow
|
|||
"Specify a resource name with `--name`.");
|
||||
}
|
||||
|
||||
$blueprint_type = $args->getArg('blueprint');
|
||||
if (!$blueprint_type) {
|
||||
$blueprint_id = $args->getArg('blueprint');
|
||||
if (!$blueprint_id) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
"Specify a blueprint type with `--blueprint`.");
|
||||
"Specify a blueprint ID with `--blueprint`.");
|
||||
}
|
||||
|
||||
$attributes = $args->getArg('attributes');
|
||||
|
@ -49,9 +49,15 @@ final class DrydockManagementCreateResourceWorkflow
|
|||
$attributes = $options->parse($attributes);
|
||||
}
|
||||
|
||||
$blueprint = id(new DrydockBlueprint())->load((int)$blueprint_id);
|
||||
if (!$blueprint) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
"Specified blueprint does not exist.");
|
||||
}
|
||||
|
||||
$resource = new DrydockResource();
|
||||
$resource->setBlueprintClass($blueprint_type);
|
||||
$resource->setType(id(new $blueprint_type())->getType());
|
||||
$resource->setBlueprintPHID($blueprint->getPHID());
|
||||
$resource->setType($blueprint->getImplementation()->getType());
|
||||
$resource->setName($resource_name);
|
||||
$resource->setStatus(DrydockResourceStatus::STATUS_OPEN);
|
||||
if ($attributes) {
|
||||
|
|
34
src/applications/drydock/phid/DrydockPHIDTypeBlueprint.php
Normal file
34
src/applications/drydock/phid/DrydockPHIDTypeBlueprint.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
final class DrydockPHIDTypeBlueprint extends PhabricatorPHIDType {
|
||||
|
||||
const TYPECONST = 'DRYB';
|
||||
|
||||
public function getTypeConstant() {
|
||||
return self::TYPECONST;
|
||||
}
|
||||
|
||||
public function getTypeName() {
|
||||
return pht('Blueprint');
|
||||
}
|
||||
|
||||
public function newObject() {
|
||||
return new DrydockBlueprint();
|
||||
}
|
||||
|
||||
protected function buildQueryForObjects(
|
||||
PhabricatorObjectQuery $query,
|
||||
array $phids) {
|
||||
|
||||
return id(new DrydockBlueprintQuery())
|
||||
->withPHIDs($phids);
|
||||
}
|
||||
|
||||
public function loadHandles(
|
||||
PhabricatorHandleQuery $query,
|
||||
array $handles,
|
||||
array $objects) {
|
||||
|
||||
}
|
||||
|
||||
}
|
70
src/applications/drydock/query/DrydockBlueprintQuery.php
Normal file
70
src/applications/drydock/query/DrydockBlueprintQuery.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
final class DrydockBlueprintQuery
|
||||
extends PhabricatorCursorPagedPolicyAwareQuery {
|
||||
|
||||
private $ids;
|
||||
private $phids;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withPHIDs(array $phids) {
|
||||
$this->phids = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function loadPage() {
|
||||
$table = new DrydockBlueprint();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
|
||||
$data = queryfx_all(
|
||||
$conn_r,
|
||||
'SELECT blueprint.* FROM %T blueprint %Q %Q %Q',
|
||||
$table->getTableName(),
|
||||
$this->buildWhereClause($conn_r),
|
||||
$this->buildOrderClause($conn_r),
|
||||
$this->buildLimitClause($conn_r));
|
||||
|
||||
$blueprints = $table->loadAllFromArray($data);
|
||||
|
||||
$implementations =
|
||||
DrydockBlueprintImplementation::getAllBlueprintImplementations();
|
||||
|
||||
foreach ($blueprints as $blueprint) {
|
||||
if (array_key_exists($implementations, $blueprint->getClassName())) {
|
||||
$blueprint->attachImplementation(
|
||||
$implementations[$blueprint->getClassName()]);
|
||||
}
|
||||
}
|
||||
|
||||
return $blueprints;
|
||||
}
|
||||
|
||||
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 (%Ld)',
|
||||
$this->phids);
|
||||
}
|
||||
|
||||
return $this->formatWhereClause($where);
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
return 'PhabricatorApplicationDrydock';
|
||||
}
|
||||
|
||||
}
|
|
@ -40,12 +40,16 @@ final class DrydockLeaseQuery extends PhabricatorOffsetPagedQuery {
|
|||
'id IN (%Ld)',
|
||||
mpull($leases, 'getResourceID'));
|
||||
|
||||
foreach ($leases as $lease) {
|
||||
foreach ($leases as $key => $lease) {
|
||||
if ($lease->getResourceID()) {
|
||||
$resource = idx($resources, $lease->getResourceID());
|
||||
if ($resource) {
|
||||
$lease->attachResource($resource);
|
||||
} else {
|
||||
unset($leases[$key]);
|
||||
}
|
||||
} else {
|
||||
unset($leases[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ final class DrydockResourceQuery
|
|||
private $ids;
|
||||
private $statuses;
|
||||
private $types;
|
||||
private $blueprintPHIDs;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
|
@ -22,6 +23,11 @@ final class DrydockResourceQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withBlueprintPHIDs(array $blueprint_phids) {
|
||||
$this->blueprintPHIDs = $blueprint_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function loadPage() {
|
||||
$table = new DrydockResource();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
|
@ -63,6 +69,13 @@ final class DrydockResourceQuery
|
|||
$this->statuses);
|
||||
}
|
||||
|
||||
if ($this->blueprintPHIDs) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'blueprintPHID IN (%Ls)',
|
||||
$this->blueprintPHIDs);
|
||||
}
|
||||
|
||||
$where[] = $this->buildPagingClause($conn_r);
|
||||
|
||||
return $this->formatWhereClause($where);
|
||||
|
|
72
src/applications/drydock/storage/DrydockBlueprint.php
Normal file
72
src/applications/drydock/storage/DrydockBlueprint.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
final class DrydockBlueprint extends DrydockDAO
|
||||
implements PhabricatorPolicyInterface {
|
||||
|
||||
protected $phid;
|
||||
protected $className;
|
||||
protected $viewPolicy;
|
||||
protected $editPolicy;
|
||||
protected $details;
|
||||
|
||||
public function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'details' => self::SERIALIZATION_JSON,
|
||||
)
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public function generatePHID() {
|
||||
return PhabricatorPHID::generateNewPHID(
|
||||
DrydockPHIDTypeBlueprint::TYPECONST);
|
||||
}
|
||||
|
||||
public function getImplementation() {
|
||||
$class = $this->className;
|
||||
$implementations =
|
||||
DrydockBlueprintImplementation::getAllBlueprintImplementations();
|
||||
if (!isset($implementations[$class])) {
|
||||
throw new Exception(
|
||||
"Invalid class name for blueprint (got '".$class."')");
|
||||
}
|
||||
return id(new $class())->attachInstance($this);
|
||||
}
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
||||
public function getCapabilities() {
|
||||
return array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
);
|
||||
}
|
||||
|
||||
public function getPolicy($capability) {
|
||||
switch ($capability) {
|
||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
||||
return $this->getViewPolicy();
|
||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
||||
return $this->getEditPolicy();
|
||||
}
|
||||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
switch ($capability) {
|
||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
||||
return $viewer->getIsAdmin();
|
||||
}
|
||||
}
|
||||
|
||||
public function describeAutomaticCapability($capability) {
|
||||
switch ($capability) {
|
||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
||||
return pht('Administrators can always view blueprints.');
|
||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
||||
return pht('Administrators can always edit blueprints.');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ final class DrydockResource extends DrydockDAO
|
|||
|
||||
protected $id;
|
||||
protected $phid;
|
||||
protected $blueprintClass;
|
||||
protected $blueprintPHID;
|
||||
protected $status;
|
||||
|
||||
protected $type;
|
||||
|
@ -50,7 +50,9 @@ final class DrydockResource extends DrydockDAO
|
|||
|
||||
public function getBlueprint() {
|
||||
if (empty($this->blueprint)) {
|
||||
$this->blueprint = newv($this->blueprintClass, array());
|
||||
$blueprint = id(new DrydockBlueprint())
|
||||
->loadOneWhere('phid = %s', $this->blueprintPHID);
|
||||
$this->blueprint = $blueprint->getImplementation();
|
||||
}
|
||||
return $this->blueprint;
|
||||
}
|
||||
|
@ -76,7 +78,7 @@ final class DrydockResource extends DrydockDAO
|
|||
$lease->setStatus(DrydockLeaseStatus::STATUS_RELEASED);
|
||||
break;
|
||||
}
|
||||
DrydockBlueprint::writeLog($this, $lease, $message);
|
||||
DrydockBlueprintImplementation::writeLog($this, $lease, $message);
|
||||
$lease->save();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
final class DrydockBlueprintScopeGuard {
|
||||
|
||||
public function __construct(DrydockBlueprint $blueprint) {
|
||||
public function __construct(DrydockBlueprintImplementation $blueprint) {
|
||||
$this->blueprint = $blueprint;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ final class DrydockAllocatorWorker extends PhabricatorWorker {
|
|||
}
|
||||
|
||||
private function logToDrydock($message) {
|
||||
DrydockBlueprint::writeLog(
|
||||
DrydockBlueprintImplementation::writeLog(
|
||||
null,
|
||||
$this->loadLease(),
|
||||
$message);
|
||||
|
@ -52,9 +52,20 @@ final class DrydockAllocatorWorker extends PhabricatorWorker {
|
|||
}
|
||||
}
|
||||
|
||||
private function loadAllBlueprints() {
|
||||
$instances = id(new DrydockBlueprint())->loadAll();
|
||||
$blueprints = array();
|
||||
foreach ($instances as $instance) {
|
||||
$blueprints[$instance->getPHID()] = $instance;
|
||||
}
|
||||
return $blueprints;
|
||||
}
|
||||
|
||||
private function allocateLease(DrydockLease $lease) {
|
||||
$type = $lease->getResourceType();
|
||||
|
||||
$blueprints = $this->loadAllBlueprints();
|
||||
|
||||
$pool = id(new DrydockResource())->loadAllWhere(
|
||||
'type = %s AND status = %s',
|
||||
$lease->getResourceType(),
|
||||
|
@ -65,14 +76,15 @@ final class DrydockAllocatorWorker extends PhabricatorWorker {
|
|||
|
||||
$candidates = array();
|
||||
foreach ($pool as $key => $candidate) {
|
||||
try {
|
||||
$blueprint = $candidate->getBlueprint();
|
||||
} catch (Exception $ex) {
|
||||
if (!isset($blueprints[$candidate->getBlueprintPHID()])) {
|
||||
unset($pool[$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($blueprint->filterResource($candidate, $lease)) {
|
||||
$blueprint = $blueprints[$candidate->getBlueprintPHID()];
|
||||
$implementation = $blueprint->getImplementation();
|
||||
|
||||
if ($implementation->filterResource($candidate, $lease)) {
|
||||
$candidates[] = $candidate;
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +95,8 @@ final class DrydockAllocatorWorker extends PhabricatorWorker {
|
|||
if ($candidates) {
|
||||
shuffle($candidates);
|
||||
foreach ($candidates as $candidate_resource) {
|
||||
$blueprint = $candidate_resource->getBlueprint();
|
||||
$blueprint = $blueprints[$candidate_resource->getBlueprintPHID()]
|
||||
->getImplementation();
|
||||
if ($blueprint->allocateLease($candidate_resource, $lease)) {
|
||||
$resource = $candidate_resource;
|
||||
break;
|
||||
|
@ -92,7 +105,8 @@ final class DrydockAllocatorWorker extends PhabricatorWorker {
|
|||
}
|
||||
|
||||
if (!$resource) {
|
||||
$blueprints = DrydockBlueprint::getAllBlueprintsForResource($type);
|
||||
$blueprints = DrydockBlueprintImplementation
|
||||
::getAllBlueprintImplementationsForResource($type);
|
||||
|
||||
$this->logToDrydock(
|
||||
pht('Found %d Blueprints', count($blueprints)));
|
||||
|
|
|
@ -1788,6 +1788,14 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
|
|||
'type' => 'sql',
|
||||
'name' => $this->getPatchPath('20131122.repomirror.sql'),
|
||||
),
|
||||
'20131123.drydockblueprintpolicy.sql' => array(
|
||||
'type' => 'sql',
|
||||
'name' => $this->getPatchPath('20131123.drydockblueprintpolicy.sql'),
|
||||
),
|
||||
'20131129.drydockresourceblueprint.sql' => array(
|
||||
'type' => 'sql',
|
||||
'name' => $this->getPatchPath('20131129.drydockresourceblueprint.sql'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,29 @@ final class PHUIFormLayoutView extends AphrontView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function appendInstructions($text) {
|
||||
return $this->appendChild(
|
||||
phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'aphront-form-instructions',
|
||||
),
|
||||
$text));
|
||||
}
|
||||
|
||||
public function appendRemarkupInstructions($remarkup) {
|
||||
if ($this->getUser() === null) {
|
||||
throw new Exception(
|
||||
"Call `setUser` before appending Remarkup to PHUIFormLayoutView.");
|
||||
}
|
||||
|
||||
return $this->appendInstructions(
|
||||
PhabricatorMarkupEngine::renderOneObject(
|
||||
id(new PhabricatorMarkupOneOff())->setContent($remarkup),
|
||||
'default',
|
||||
$this->getUser()));
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$classes = array('phui-form-view');
|
||||
|
||||
|
|
Loading…
Reference in a new issue