From 220ac48801a10ad9b288e86e708e03db481460d9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 26 Feb 2016 12:02:04 -0800 Subject: [PATCH 01/41] Remove buildable handle / container handle logic form Harbormaster buildable queries Summary: Ref T10457. We currently have these weird, out-of-place methods on Harbormaster queries that just load handles. These were written before HandlePool, and HandlePool is now more convenient, simpler, and more efficient. Drop this stuff in favor of using handle pools off `$viewer`. Test Plan: Looked at buildable list, looked at buildable detail, grepped for removed methods. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10457 Differential Revision: https://secure.phabricator.com/D15354 --- .../HarbormasterBuildableViewController.php | 11 +-- .../HarbormasterBuildableInterface.php | 1 - .../phid/HarbormasterBuildablePHIDType.php | 26 +++++-- .../query/HarbormasterBuildableQuery.php | 67 +++---------------- .../HarbormasterBuildableSearchEngine.php | 31 ++++++--- .../storage/HarbormasterBuildable.php | 24 ++----- 6 files changed, 60 insertions(+), 100 deletions(-) diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php index 91fb45176f..42f2572266 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php @@ -9,8 +9,6 @@ final class HarbormasterBuildableViewController $buildable = id(new HarbormasterBuildableQuery()) ->setViewer($viewer) ->withIDs(array($request->getURIData('id'))) - ->needBuildableHandles(true) - ->needContainerHandles(true) ->executeOne(); if (!$buildable) { return new Aphront404Response(); @@ -167,15 +165,18 @@ final class HarbormasterBuildableViewController ->setActionList($actions); $box->addPropertyList($properties); - if ($buildable->getContainerHandle() !== null) { + $container_phid = $buildable->getContainerPHID(); + $buildable_phid = $buildable->getBuildablePHID(); + + if ($container_phid) { $properties->addProperty( pht('Container'), - $buildable->getContainerHandle()->renderLink()); + $viewer->renderHandle($container_phid)); } $properties->addProperty( pht('Buildable'), - $buildable->getBuildableHandle()->renderLink()); + $viewer->renderHandle($buildable_phid)); $properties->addProperty( pht('Origin'), diff --git a/src/applications/harbormaster/interface/HarbormasterBuildableInterface.php b/src/applications/harbormaster/interface/HarbormasterBuildableInterface.php index a5517a6b2b..d2b2d332aa 100644 --- a/src/applications/harbormaster/interface/HarbormasterBuildableInterface.php +++ b/src/applications/harbormaster/interface/HarbormasterBuildableInterface.php @@ -6,7 +6,6 @@ interface HarbormasterBuildableInterface { public function getHarbormasterContainerPHID(); public function getBuildVariables(); - public function getAvailableBuildVariables(); } diff --git a/src/applications/harbormaster/phid/HarbormasterBuildablePHIDType.php b/src/applications/harbormaster/phid/HarbormasterBuildablePHIDType.php index c6ccabf515..fbc08d4ef3 100644 --- a/src/applications/harbormaster/phid/HarbormasterBuildablePHIDType.php +++ b/src/applications/harbormaster/phid/HarbormasterBuildablePHIDType.php @@ -21,8 +21,7 @@ final class HarbormasterBuildablePHIDType extends PhabricatorPHIDType { array $phids) { return id(new HarbormasterBuildableQuery()) - ->withPHIDs($phids) - ->needBuildableHandles(true); + ->withPHIDs($phids); } public function loadHandles( @@ -30,15 +29,30 @@ final class HarbormasterBuildablePHIDType extends PhabricatorPHIDType { array $handles, array $objects) { + $viewer = $this->getViewer(); + + $target_phids = array(); + foreach ($objects as $phid => $object) { + $target_phids[] = $object->getBuildablePHID(); + } + $target_handles = $viewer->loadHandles($target_phids); + foreach ($handles as $phid => $handle) { $buildable = $objects[$phid]; $id = $buildable->getID(); - $target = $buildable->getBuildableHandle()->getFullName(); + $buildable_phid = $buildable->getBuildablePHID(); - $handle->setURI("/B{$id}"); - $handle->setName("B{$id}"); - $handle->setFullName("B{$id}: ".$target); + $target = $target_handles[$buildable_phid]; + $target_name = $target->getFullName(); + + $uri = $buildable->getURI(); + $monogram = $buildable->getMonogram(); + + $handle + ->setURI($uri) + ->setName($monogram) + ->setFullName("{$monogram}: {$target_name}"); } } diff --git a/src/applications/harbormaster/query/HarbormasterBuildableQuery.php b/src/applications/harbormaster/query/HarbormasterBuildableQuery.php index 2986bb45b7..fc069bd031 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildableQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildableQuery.php @@ -10,8 +10,6 @@ final class HarbormasterBuildableQuery private $manualBuildables; private $needContainerObjects; - private $needContainerHandles; - private $needBuildableHandles; private $needBuilds; private $needTargets; @@ -45,16 +43,6 @@ final class HarbormasterBuildableQuery return $this; } - public function needContainerHandles($need) { - $this->needContainerHandles = $need; - return $this; - } - - public function needBuildableHandles($need) { - $this->needBuildableHandles = $need; - return $this; - } - public function needBuilds($need) { $this->needBuilds = $need; return $this; @@ -99,60 +87,23 @@ final class HarbormasterBuildableQuery } protected function didFilterPage(array $page) { - if ($this->needContainerObjects || $this->needContainerHandles) { + if ($this->needContainerObjects) { $container_phids = array_filter(mpull($page, 'getContainerPHID')); - if ($this->needContainerObjects) { - $containers = array(); - - 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->needContainerHandles) { - $handles = array(); - - if ($container_phids) { - $handles = id(new PhabricatorHandleQuery()) - ->setViewer($this->getViewer()) - ->withPHIDs($container_phids) - ->setParentQuery($this) - ->execute(); - } - - foreach ($page as $key => $buildable) { - $container_phid = $buildable->getContainerPHID(); - $buildable->attachContainerHandle(idx($handles, $container_phid)); - } - } - } - - if ($this->needBuildableHandles) { - $handles = array(); - - $handle_phids = array_filter(mpull($page, 'getBuildablePHID')); - if ($handle_phids) { - $handles = id(new PhabricatorHandleQuery()) + if ($container_phids) { + $containers = id(new PhabricatorObjectQuery()) ->setViewer($this->getViewer()) - ->withPHIDs($handle_phids) + ->withPHIDs($container_phids) ->setParentQuery($this) ->execute(); + $containers = mpull($containers, null, 'getPHID'); + } else { + $containers = array(); } foreach ($page as $key => $buildable) { - $handle_phid = $buildable->getBuildablePHID(); - $buildable->attachBuildableHandle(idx($handles, $handle_phid)); + $container_phid = $buildable->getContainerPHID(); + $buildable->attachContainerObject(idx($containers, $container_phid)); } } diff --git a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php index 195a68a695..982db3dd39 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php +++ b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php @@ -58,9 +58,7 @@ final class HarbormasterBuildableSearchEngine } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new HarbormasterBuildableQuery()) - ->needContainerHandles(true) - ->needBuildableHandles(true); + $query = id(new HarbormasterBuildableQuery()); $container_phids = $saved->getParameter('containerPHIDs', array()); if ($container_phids) { @@ -185,23 +183,36 @@ final class HarbormasterBuildableSearchEngine $viewer = $this->requireViewer(); + $phids = array(); + foreach ($buildables as $buildable) { + $phids[] = $buildable->getContainerPHID(); + $phids[] = $buildable->getBuildablePHID(); + } + $handles = $viewer->loadHandles($phids); + + $list = new PHUIObjectItemListView(); foreach ($buildables as $buildable) { $id = $buildable->getID(); + $container_phid = $buildable->getContainerPHID(); + $buildable_phid = $buildable->getBuildablePHID(); + $item = id(new PHUIObjectItemView()) ->setHeader(pht('Buildable %d', $buildable->getID())); - if ($buildable->getContainerHandle() !== null) { - $item->addAttribute($buildable->getContainerHandle()->getName()); - } - if ($buildable->getBuildableHandle() !== null) { - $item->addAttribute($buildable->getBuildableHandle()->getFullName()); + + if ($container_phid) { + $handle = $handles[$container_phid]; + $item->addAttribute($handle->getName()); } - if ($id) { - $item->setHref("/B{$id}"); + if ($buildable_phid) { + $handle = $handles[$buildable_phid]; + $item->addAttribute($handle->getFullName()); } + $item->setHref($buildable->getURI()); + if ($buildable->getIsManualBuildable()) { $item->addIcon('fa-wrench grey', pht('Manual')); } diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php index 6286072b42..7ee6981477 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildable.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -13,8 +13,6 @@ final class HarbormasterBuildable extends HarbormasterDAO private $buildableObject = self::ATTACHABLE; private $containerObject = self::ATTACHABLE; - private $buildableHandle = self::ATTACHABLE; - private $containerHandle = self::ATTACHABLE; private $builds = self::ATTACHABLE; const STATUS_BUILDING = 'building'; @@ -70,6 +68,10 @@ final class HarbormasterBuildable extends HarbormasterDAO return 'B'.$this->getID(); } + public function getURI() { + return '/'.$this->getMonogram(); + } + /** * Returns an existing buildable for the object's PHID or creates a * new buildable implicitly if needed. @@ -237,24 +239,6 @@ final class HarbormasterBuildable extends HarbormasterDAO return $this->assertAttached($this->containerObject); } - public function attachContainerHandle($container_handle) { - $this->containerHandle = $container_handle; - return $this; - } - - public function getContainerHandle() { - return $this->assertAttached($this->containerHandle); - } - - public function attachBuildableHandle($buildable_handle) { - $this->buildableHandle = $buildable_handle; - return $this; - } - - public function getBuildableHandle() { - return $this->assertAttached($this->buildableHandle); - } - public function attachBuilds(array $builds) { assert_instances_of($builds, 'HarbormasterBuild'); $this->builds = $builds; From fdca684814dacf6b9386b4f0750905978540526a Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 26 Feb 2016 12:20:47 -0800 Subject: [PATCH 02/41] Slightly improve Buildable list in Harbormaster Summary: Ref T10457. This makes diffs/revisions show the revision as the buildable title, and commits show the commit as the title. Previously, the title was "Buildable X". Also makes icons/colors/labels more consitent. Test Plan: {F1131885} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10457 Differential Revision: https://secure.phabricator.com/D15355 --- .../differential/storage/DifferentialDiff.php | 9 ++++++ .../storage/DifferentialRevision.php | 4 +++ .../HarbormasterBuildableInterface.php | 13 ++++++++ .../HarbormasterBuildableSearchEngine.php | 30 +++++++++++++------ .../storage/HarbormasterBuildable.php | 4 +++ .../storage/PhabricatorRepositoryCommit.php | 4 +++ 6 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php index d0cdcf1830..889b46d470 100644 --- a/src/applications/differential/storage/DifferentialDiff.php +++ b/src/applications/differential/storage/DifferentialDiff.php @@ -429,6 +429,15 @@ final class DifferentialDiff /* -( HarbormasterBuildableInterface )------------------------------------- */ + public function getHarbormasterBuildableDisplayPHID() { + $container_phid = $this->getHarbormasterContainerPHID(); + if ($container_phid) { + return $container_phid; + } + + return $this->getHarbormasterBuildablePHID(); + } + public function getHarbormasterBuildablePHID() { return $this->getPHID(); } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index ff72b46de7..e67208c53a 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -432,6 +432,10 @@ final class DifferentialRevision extends DifferentialDAO /* -( HarbormasterBuildableInterface )------------------------------------- */ + public function getHarbormasterBuildableDisplayPHID() { + return $this->getHarbormasterContainerPHID(); + } + public function getHarbormasterBuildablePHID() { return $this->loadActiveDiff()->getPHID(); } diff --git a/src/applications/harbormaster/interface/HarbormasterBuildableInterface.php b/src/applications/harbormaster/interface/HarbormasterBuildableInterface.php index d2b2d332aa..0e852b72a3 100644 --- a/src/applications/harbormaster/interface/HarbormasterBuildableInterface.php +++ b/src/applications/harbormaster/interface/HarbormasterBuildableInterface.php @@ -2,6 +2,19 @@ interface HarbormasterBuildableInterface { + /** + * Get the object PHID which best identifies this buildable to humans. + * + * This object is the primary object associated with the buildable in the + * UI. The most human-readable object for a buildable varies: for example, + * for diffs the container (the revision) is more meaningful than the + * buildable (the diff), but for commits the buildable (the commit) is more + * meaningful than the container (the repository). + * + * @return phid Related object PHID most meaningful for human viewers. + */ + public function getHarbormasterBuildableDisplayPHID(); + public function getHarbormasterBuildablePHID(); public function getHarbormasterContainerPHID(); diff --git a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php index 982db3dd39..937816eb19 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php +++ b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php @@ -185,6 +185,9 @@ final class HarbormasterBuildableSearchEngine $phids = array(); foreach ($buildables as $buildable) { + $phids[] = $buildable->getBuildableObject() + ->getHarbormasterBuildableDisplayPHID(); + $phids[] = $buildable->getContainerPHID(); $phids[] = $buildable->getBuildablePHID(); } @@ -195,18 +198,26 @@ final class HarbormasterBuildableSearchEngine foreach ($buildables as $buildable) { $id = $buildable->getID(); + $display_phid = $buildable->getBuildableObject() + ->getHarbormasterBuildableDisplayPHID(); + $container_phid = $buildable->getContainerPHID(); $buildable_phid = $buildable->getBuildablePHID(); $item = id(new PHUIObjectItemView()) - ->setHeader(pht('Buildable %d', $buildable->getID())); + ->setObjectName(pht('Buildable %d', $buildable->getID())); - if ($container_phid) { + if ($display_phid) { + $handle = $handles[$display_phid]; + $item->setHeader($handle->getFullName()); + } + + if ($container_phid && ($container_phid != $display_phid)) { $handle = $handles[$container_phid]; $item->addAttribute($handle->getName()); } - if ($buildable_phid) { + if ($buildable_phid && ($buildable_phid != $display_phid)) { $handle = $handles[$buildable_phid]; $item->addAttribute($handle->getFullName()); } @@ -217,14 +228,15 @@ final class HarbormasterBuildableSearchEngine $item->addIcon('fa-wrench grey', pht('Manual')); } - $item->setStatusIcon('fa-wrench '. - HarbormasterBuildable::getBuildableStatusColor( - $buildable->getBuildableStatus())); - $item->addByline(HarbormasterBuildable::getBuildableStatusName( - $buildable->getBuildableStatus())); + $status = $buildable->getBuildableStatus(); + + $status_icon = HarbormasterBuildable::getBuildableStatusIcon($status); + $status_color = HarbormasterBuildable::getBuildableStatusColor($status); + $status_label = HarbormasterBuildable::getBuildableStatusName($status); + + $item->setStatusIcon("{$status_icon} {$status_color}", $status_label); $list->addItem($item); - } $result = new PhabricatorApplicationSearchResultView(); diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php index 7ee6981477..f02fff270b 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildable.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -302,6 +302,10 @@ final class HarbormasterBuildable extends HarbormasterDAO /* -( HarbormasterBuildableInterface )------------------------------------- */ + public function getHarbormasterBuildableDisplayPHID() { + return $this->getBuildableObject()->getHarbormasterBuildableDisplayPHID(); + } + public function getHarbormasterBuildablePHID() { // NOTE: This is essentially just for convenience, as it allows you create // a copy of a buildable by specifying `B123` without bothering to go diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index 348b94d021..9fd218db9a 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -370,6 +370,10 @@ final class PhabricatorRepositoryCommit /* -( HarbormasterBuildableInterface )------------------------------------- */ + public function getHarbormasterBuildableDisplayPHID() { + return $this->getHarbormasterBuildablePHID(); + } + public function getHarbormasterBuildablePHID() { return $this->getPHID(); } From c29ba039bbdabe963dfa5014232f4f320ab329ab Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 26 Feb 2016 12:44:18 -0800 Subject: [PATCH 03/41] Update Buildable search in Harbormaster Summary: Fixes T10011. - Modernize searching for buildables. - Prepare for `harbormaster.buildable.search`. - Allow users to query by status (see T10011). - Collapse the four weird "commit / diff / revision / repository" fields into two slightly less weird fields with more UI hinting? Test Plan: {F1131918} Reviewers: chad Reviewed By: chad Subscribers: Luke081515.2 Maniphest Tasks: T10011 Differential Revision: https://secure.phabricator.com/D15356 --- .../phid/HarbormasterBuildablePHIDType.php | 2 +- .../query/HarbormasterBuildableQuery.php | 13 ++ .../HarbormasterBuildableSearchEngine.php | 197 ++++++------------ .../storage/HarbormasterBuildable.php | 20 +- .../PhabricatorSearchStringListField.php | 20 +- 5 files changed, 112 insertions(+), 140 deletions(-) diff --git a/src/applications/harbormaster/phid/HarbormasterBuildablePHIDType.php b/src/applications/harbormaster/phid/HarbormasterBuildablePHIDType.php index fbc08d4ef3..674393dfc7 100644 --- a/src/applications/harbormaster/phid/HarbormasterBuildablePHIDType.php +++ b/src/applications/harbormaster/phid/HarbormasterBuildablePHIDType.php @@ -29,7 +29,7 @@ final class HarbormasterBuildablePHIDType extends PhabricatorPHIDType { array $handles, array $objects) { - $viewer = $this->getViewer(); + $viewer = $query->getViewer(); $target_phids = array(); foreach ($objects as $phid => $object) { diff --git a/src/applications/harbormaster/query/HarbormasterBuildableQuery.php b/src/applications/harbormaster/query/HarbormasterBuildableQuery.php index fc069bd031..b1a643cac7 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildableQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildableQuery.php @@ -7,6 +7,7 @@ final class HarbormasterBuildableQuery private $phids; private $buildablePHIDs; private $containerPHIDs; + private $statuses; private $manualBuildables; private $needContainerObjects; @@ -43,6 +44,11 @@ final class HarbormasterBuildableQuery return $this; } + public function withStatuses(array $statuses) { + $this->statuses = $statuses; + return $this; + } + public function needBuilds($need) { $this->needBuilds = $need; return $this; @@ -154,6 +160,13 @@ final class HarbormasterBuildableQuery $this->containerPHIDs); } + if ($this->statuses !== null) { + $where[] = qsprintf( + $conn, + 'buildableStatus in (%Ls)', + $this->statuses); + } + if ($this->manualBuildables !== null) { $where[] = qsprintf( $conn, diff --git a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php index 937816eb19..cfff27b1aa 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php +++ b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php @@ -11,146 +11,87 @@ final class HarbormasterBuildableSearchEngine return 'PhabricatorHarbormasterApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $revisions = $this->readPHIDsFromRequest( - $request, - 'revisions', - array( - DifferentialRevisionPHIDType::TYPECONST, - )); - - $repositories = $this->readPHIDsFromRequest( - $request, - 'repositories', - array( - PhabricatorRepositoryRepositoryPHIDType::TYPECONST, - )); - - $container_phids = array_merge($revisions, $repositories); - $saved->setParameter('containerPHIDs', $container_phids); - - $commits = $this->readPHIDsFromRequest( - $request, - 'commits', - array( - PhabricatorRepositoryCommitPHIDType::TYPECONST, - )); - - $diffs = $this->readListFromRequest($request, 'diffs'); - if ($diffs) { - $diffs = id(new DifferentialDiffQuery()) - ->setViewer($this->requireViewer()) - ->withIDs($diffs) - ->execute(); - $diffs = mpull($diffs, 'getPHID', 'getPHID'); - } - - $buildable_phids = array_merge($commits, $diffs); - $saved->setParameter('buildablePHIDs', $buildable_phids); - - $saved->setParameter( - 'manual', - $this->readBoolFromRequest($request, 'manual')); - - return $saved; + public function newQuery() { + return new HarbormasterBuildableQuery(); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new HarbormasterBuildableQuery()); - - $container_phids = $saved->getParameter('containerPHIDs', array()); - if ($container_phids) { - $query->withContainerPHIDs($container_phids); - } - - $buildable_phids = $saved->getParameter('buildablePHIDs', array()); - - if ($buildable_phids) { - $query->withBuildablePHIDs($buildable_phids); - } - - $manual = $saved->getParameter('manual'); - if ($manual !== null) { - $query->withManualBuildables($manual); - } - - return $query; + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchStringListField()) + ->setKey('objectPHIDs') + ->setAliases(array('objects')) + ->setLabel(pht('Objects')) + ->setPlaceholder(pht('rXabcdef, PHID-DIFF-1234, ...')) + ->setDescription(pht('Search for builds of particular objects.')), + id(new PhabricatorSearchStringListField()) + ->setKey('containerPHIDs') + ->setAliases(array('containers')) + ->setLabel(pht('Containers')) + ->setPlaceholder(pht('rXYZ, R123, D456, ...')) + ->setDescription( + pht('Search for builds by containing revision or repository.')), + id(new PhabricatorSearchCheckboxesField()) + ->setKey('statuses') + ->setLabel(pht('Statuses')) + ->setOptions(HarbormasterBuildable::getBuildStatusMap()) + ->setDescription(pht('Search for builds by buildable status.')), + id(new PhabricatorSearchThreeStateField()) + ->setLabel(pht('Manual')) + ->setKey('manual') + ->setDescription( + pht('Search for only manual or automatic buildables.')) + ->setOptions( + pht('(Show All)'), + pht('Show Only Manual Builds'), + pht('Show Only Automated Builds')), + ); } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved_query) { + private function resolvePHIDs(array $names) { + $viewer = $this->requireViewer(); - $container_phids = $saved_query->getParameter('containerPHIDs', array()); - $buildable_phids = $saved_query->getParameter('buildablePHIDs', array()); + $objects = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withNames($names) + ->execute(); - $all_phids = array_merge($container_phids, $buildable_phids); + // TODO: Instead of using string lists, we should ideally be using some + // kind of smart field with resolver logic that can help users type the + // right stuff. For now, just return a bogus value here so nothing matches + // but the form doesn't explode. + if (!$objects) { + return array('-'); + } - $revision_names = array(); - $diff_names = array(); - $repository_names = array(); - $commit_names = array(); + return mpull($objects, 'getPHID'); + } - if ($all_phids) { - $objects = id(new PhabricatorObjectQuery()) - ->setViewer($this->requireViewer()) - ->withPHIDs($all_phids) - ->execute(); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); - foreach ($all_phids as $phid) { - $object = idx($objects, $phid); - if (!$object) { - continue; - } - - if ($object instanceof DifferentialRevision) { - $revision_names[] = 'D'.$object->getID(); - } else if ($object instanceof DifferentialDiff) { - $diff_names[] = $object->getID(); - } else if ($object instanceof PhabricatorRepository) { - $repository_names[] = $object->getMonogram(); - } else if ($object instanceof PhabricatorRepositoryCommit) { - $repository = $object->getRepository(); - $commit_names[] = $repository->formatCommitName( - $object->getCommitIdentifier()); - } + if ($map['objectPHIDs']) { + $phids = $this->resolvePHIDs($map['objectPHIDs']); + if ($phids) { + $query->withBuildablePHIDs($phids); } } - $form - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Differential Revisions')) - ->setName('revisions') - ->setValue(implode(', ', $revision_names))) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Differential Diffs')) - ->setName('diffs') - ->setValue(implode(', ', $diff_names))) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Repositories')) - ->setName('repositories') - ->setValue(implode(', ', $repository_names))) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Commits')) - ->setName('commits') - ->setValue(implode(', ', $commit_names))) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Origin')) - ->setName('manual') - ->setValue($this->getBoolFromQuery($saved_query, 'manual')) - ->setOptions( - array( - '' => pht('(All Origins)'), - 'true' => pht('Manual Buildables'), - 'false' => pht('Automatic Buildables'), - ))); + if ($map['containerPHIDs']) { + $phids = $this->resolvePHIDs($map['containerPHIDs']); + if ($phids) { + $query->withContainerPHIDs($phids); + } + } + + if ($map['statuses']) { + $query->withStatuses($map['statuses']); + } + + if ($map['manual'] !== null) { + $query->withManualBuildables($map['manual']); + } + + return $query; } protected function getURI($path) { diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php index f02fff270b..7a7b32618c 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildable.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -20,16 +20,16 @@ final class HarbormasterBuildable extends HarbormasterDAO const STATUS_FAILED = 'failed'; public static function getBuildableStatusName($status) { - switch ($status) { - case self::STATUS_BUILDING: - return pht('Building'); - case self::STATUS_PASSED: - return pht('Passed'); - case self::STATUS_FAILED: - return pht('Failed'); - default: - return pht('Unknown'); - } + $map = self::getBuildStatusMap(); + return idx($map, $status, pht('Unknown ("%s")', $status)); + } + + public static function getBuildStatusMap() { + return array( + self::STATUS_BUILDING => pht('Building'), + self::STATUS_PASSED => pht('Passed'), + self::STATUS_FAILED => pht('Failed'), + ); } public static function getBuildableStatusIcon($status) { diff --git a/src/applications/search/field/PhabricatorSearchStringListField.php b/src/applications/search/field/PhabricatorSearchStringListField.php index 415caf7ea2..2dd9517a3e 100644 --- a/src/applications/search/field/PhabricatorSearchStringListField.php +++ b/src/applications/search/field/PhabricatorSearchStringListField.php @@ -3,6 +3,17 @@ final class PhabricatorSearchStringListField extends PhabricatorSearchField { + private $placeholder; + + public function setPlaceholder($placeholder) { + $this->placeholder = $placeholder; + return $this; + } + + public function getPlaceholder() { + return $this->placeholder; + } + protected function getDefaultValue() { return array(); } @@ -12,7 +23,14 @@ final class PhabricatorSearchStringListField } protected function newControl() { - return new AphrontFormTextControl(); + $control = new AphrontFormTextControl(); + + $placeholder = $this->getPlaceholder(); + if ($placeholder !== null) { + $control->setPlaceholder($placeholder); + } + + return $control; } protected function getValueForControl() { From 5295c6ba1e6176fe39cb4f2882776e1f13841cb3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 26 Feb 2016 19:12:55 -0800 Subject: [PATCH 04/41] Use EditEngine for Harbormaster Build Plans, fix some crumbs/mobile stuff Summary: Ref T10457. - Use EditEngine for Build Plans. - Fix some minor issues with crumbs being inconsistent. - Fix some minor issues with mobile menus not being consistent/available. Test Plan: - Created and edited build plans. - Poked around in mobile width, verified mobile menu had the right stuff in it. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10457 Differential Revision: https://secure.phabricator.com/D15357 --- src/__phutil_library_map__.php | 12 +- .../PhabricatorHarbormasterApplication.php | 6 +- .../HarbormasterBuildableListController.php | 37 ++--- .../controller/HarbormasterController.php | 5 + .../controller/HarbormasterPlanController.php | 5 + .../HarbormasterPlanEditController.php | 143 +----------------- .../HarbormasterPlanListController.php | 46 +----- .../HarbormasterPlanRunController.php | 2 +- .../HarbormasterStepAddController.php | 3 +- .../HarbormasterStepDeleteController.php | 3 +- .../HarbormasterStepEditController.php | 3 +- .../HarbormasterStepViewController.php | 3 +- .../HarbormasterBuildPlanEditEngine.php | 89 +++++++++++ .../editor/HarbormasterBuildPlanEditor.php | 2 +- 14 files changed, 140 insertions(+), 219 deletions(-) create mode 100644 src/applications/harbormaster/editor/HarbormasterBuildPlanEditEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 8528aa9a19..0a60ce8330 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1053,6 +1053,7 @@ phutil_register_library_map(array( 'HarbormasterBuildPlanDatasource' => 'applications/harbormaster/typeahead/HarbormasterBuildPlanDatasource.php', 'HarbormasterBuildPlanDefaultEditCapability' => 'applications/harbormaster/capability/HarbormasterBuildPlanDefaultEditCapability.php', 'HarbormasterBuildPlanDefaultViewCapability' => 'applications/harbormaster/capability/HarbormasterBuildPlanDefaultViewCapability.php', + 'HarbormasterBuildPlanEditEngine' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditEngine.php', 'HarbormasterBuildPlanEditor' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditor.php', 'HarbormasterBuildPlanPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildPlanPHIDType.php', 'HarbormasterBuildPlanQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanQuery.php', @@ -5202,6 +5203,7 @@ phutil_register_library_map(array( 'HarbormasterBuildPlanDatasource' => 'PhabricatorTypeaheadDatasource', 'HarbormasterBuildPlanDefaultEditCapability' => 'PhabricatorPolicyCapability', 'HarbormasterBuildPlanDefaultViewCapability' => 'PhabricatorPolicyCapability', + 'HarbormasterBuildPlanEditEngine' => 'PhabricatorEditEngine', 'HarbormasterBuildPlanEditor' => 'PhabricatorApplicationTransactionEditor', 'HarbormasterBuildPlanPHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildPlanQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', @@ -5283,7 +5285,7 @@ phutil_register_library_map(array( 'HarbormasterPlanDisableController' => 'HarbormasterPlanController', 'HarbormasterPlanEditController' => 'HarbormasterPlanController', 'HarbormasterPlanListController' => 'HarbormasterPlanController', - 'HarbormasterPlanRunController' => 'HarbormasterController', + 'HarbormasterPlanRunController' => 'HarbormasterPlanController', 'HarbormasterPlanViewController' => 'HarbormasterPlanController', 'HarbormasterPrototypeBuildStepGroup' => 'HarbormasterBuildStepGroup', 'HarbormasterPublishFragmentBuildStepImplementation' => 'HarbormasterBuildStepImplementation', @@ -5296,10 +5298,10 @@ phutil_register_library_map(array( 'HarbormasterScratchTable' => 'HarbormasterDAO', 'HarbormasterSendMessageConduitAPIMethod' => 'HarbormasterConduitAPIMethod', 'HarbormasterSleepBuildStepImplementation' => 'HarbormasterBuildStepImplementation', - 'HarbormasterStepAddController' => 'HarbormasterController', - 'HarbormasterStepDeleteController' => 'HarbormasterController', - 'HarbormasterStepEditController' => 'HarbormasterController', - 'HarbormasterStepViewController' => 'HarbormasterController', + 'HarbormasterStepAddController' => 'HarbormasterPlanController', + 'HarbormasterStepDeleteController' => 'HarbormasterPlanController', + 'HarbormasterStepEditController' => 'HarbormasterPlanController', + 'HarbormasterStepViewController' => 'HarbormasterPlanController', 'HarbormasterTargetEngine' => 'Phobject', 'HarbormasterTargetWorker' => 'HarbormasterWorker', 'HarbormasterTestBuildStepGroup' => 'HarbormasterBuildStepGroup', diff --git a/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php b/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php index 97fa40dee8..a2956432ff 100644 --- a/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php +++ b/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php @@ -76,9 +76,9 @@ final class PhabricatorHarbormasterApplication extends PhabricatorApplication { => 'HarbormasterBuildActionController', ), 'plan/' => array( - '(?:query/(?P[^/]+)/)?' - => 'HarbormasterPlanListController', - 'edit/(?:(?P\d+)/)?' => 'HarbormasterPlanEditController', + $this->getQueryRoutePattern() => 'HarbormasterPlanListController', + $this->getEditRoutePattern('edit/') + => 'HarbormasterPlanEditController', 'order/(?:(?P\d+)/)?' => 'HarbormasterPlanOrderController', 'disable/(?P\d+)/' => 'HarbormasterPlanDisableController', 'run/(?P\d+)/' => 'HarbormasterPlanRunController', diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableListController.php b/src/applications/harbormaster/controller/HarbormasterBuildableListController.php index b7e6dcd978..99595335be 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildableListController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableListController.php @@ -7,34 +7,21 @@ final class HarbormasterBuildableListController extends HarbormasterController { } public function handleRequest(AphrontRequest $request) { - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($request->getURIData('queryKey')) - ->setSearchEngine(new HarbormasterBuildableSearchEngine()) - ->setNavigation($this->buildSideNavView()); + $items = array(); - return $this->delegateToController($controller); - } + $items[] = id(new PHUIListItemView()) + ->setType(PHUIListItemView::TYPE_LABEL) + ->setName(pht('Build Plans')); - public function buildSideNavView($for_app = false) { - $user = $this->getRequest()->getUser(); + $items[] = id(new PHUIListItemView()) + ->setType(PHUIListItemView::TYPE_LINK) + ->setName(pht('Manage Build Plans')) + ->setHref($this->getApplicationURI('plan/')); - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - id(new HarbormasterBuildableSearchEngine()) - ->setViewer($user) - ->addNavigationItems($nav->getMenu()); - - $nav->addLabel(pht('Build Plans')); - $nav->addFilter('plan/', pht('Manage Build Plans')); - - $nav->selectFilter(null); - - return $nav; - } - - public function buildApplicationMenu() { - return $this->buildSideNavView(true)->getMenu(); + return id(new HarbormasterBuildableSearchEngine()) + ->setController($this) + ->setNavigationItems($items) + ->buildResponse(); } } diff --git a/src/applications/harbormaster/controller/HarbormasterController.php b/src/applications/harbormaster/controller/HarbormasterController.php index dea76b36d3..286b186811 100644 --- a/src/applications/harbormaster/controller/HarbormasterController.php +++ b/src/applications/harbormaster/controller/HarbormasterController.php @@ -2,6 +2,11 @@ abstract class HarbormasterController extends PhabricatorController { + public function buildApplicationMenu() { + return $this->newApplicationMenu() + ->setSearchEngine(new HarbormasterBuildableSearchEngine()); + } + protected function addBuildableCrumb( PHUICrumbsView $crumbs, HarbormasterBuildable $buildable) { diff --git a/src/applications/harbormaster/controller/HarbormasterPlanController.php b/src/applications/harbormaster/controller/HarbormasterPlanController.php index d158c69bed..c70e74099f 100644 --- a/src/applications/harbormaster/controller/HarbormasterPlanController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanController.php @@ -2,6 +2,11 @@ abstract class HarbormasterPlanController extends HarbormasterController { + public function buildApplicationMenu() { + return $this->newApplicationMenu() + ->setSearchEngine(new HarbormasterBuildPlanSearchEngine()); + } + protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); diff --git a/src/applications/harbormaster/controller/HarbormasterPlanEditController.php b/src/applications/harbormaster/controller/HarbormasterPlanEditController.php index a4f177e7dc..82af16e4b0 100644 --- a/src/applications/harbormaster/controller/HarbormasterPlanEditController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanEditController.php @@ -3,146 +3,9 @@ final class HarbormasterPlanEditController extends HarbormasterPlanController { public function handleRequest(AphrontRequest $request) { - $viewer = $this->getViewer(); - - $id = $request->getURIData('id'); - if ($id) { - $plan = id(new HarbormasterBuildPlanQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$plan) { - return new Aphront404Response(); - } - } else { - $this->requireApplicationCapability( - HarbormasterCreatePlansCapability::CAPABILITY); - - $plan = HarbormasterBuildPlan::initializeNewBuildPlan($viewer); - } - - $e_name = true; - $v_name = $plan->getName(); - $v_view = $plan->getViewPolicy(); - $v_edit = $plan->getEditPolicy(); - $validation_exception = null; - if ($request->isFormPost()) { - $xactions = array(); - - $v_name = $request->getStr('name'); - $v_view = $request->getStr('viewPolicy'); - $v_edit = $request->getStr('editPolicy'); - - $e_name = null; - - $type_name = HarbormasterBuildPlanTransaction::TYPE_NAME; - $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; - $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; - - $xactions[] = id(new HarbormasterBuildPlanTransaction()) - ->setTransactionType($type_name) - ->setNewValue($v_name); - - $xactions[] = id(new HarbormasterBuildPlanTransaction()) - ->setTransactionType($type_view) - ->setNewValue($v_view); - - $xactions[] = id(new HarbormasterBuildPlanTransaction()) - ->setTransactionType($type_edit) - ->setNewValue($v_edit); - - $editor = id(new HarbormasterBuildPlanEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect(true) - ->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('plan/'); - $save_button = pht('Create Build Plan'); - } else { - $id = $plan->getID(); - - $title = pht('Edit Build Plan'); - $cancel_uri = $this->getApplicationURI('plan/'.$plan->getID().'/'); - $save_button = pht('Save Build Plan'); - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($plan) - ->execute(); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendControl( - id(new AphrontFormTextControl()) - ->setLabel(pht('Plan Name')) - ->setName('name') - ->setError($e_name) - ->setValue($v_name)) - ->appendControl( - id(new AphrontFormPolicyControl()) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicyObject($plan) - ->setPolicies($policies) - ->setValue($v_view) - ->setName('viewPolicy')) - ->appendControl( - id(new AphrontFormPolicyControl()) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicyObject($plan) - ->setPolicies($policies) - ->setValue($v_edit) - ->setName('editPolicy')) - ->appendControl( - 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->addTextCrumb(pht('New Build Plan')); - } else { - $id = $plan->getID(); - $crumbs->addTextCrumb( - pht('Plan %d', $id), - $this->getApplicationURI("plan/{$id}/")); - $crumbs->addTextCrumb(pht('Edit')); - } - - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, - )); + return id(new HarbormasterBuildPlanEditEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/harbormaster/controller/HarbormasterPlanListController.php b/src/applications/harbormaster/controller/HarbormasterPlanListController.php index a67debd533..357c08c460 100644 --- a/src/applications/harbormaster/controller/HarbormasterPlanListController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanListController.php @@ -7,53 +7,19 @@ final class HarbormasterPlanListController extends HarbormasterPlanController { } public function handleRequest(AphrontRequest $request) { - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($request->getURIData('queryKey')) - ->setSearchEngine(new HarbormasterBuildPlanSearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); - } - - public function buildSideNavView($for_app = false) { - $user = $this->getRequest()->getUser(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - if ($for_app) { - $nav->addFilter('new/', pht('New Build Plan')); - } - - id(new HarbormasterBuildPlanSearchEngine()) - ->setViewer($user) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; - } - - public function buildApplicationMenu() { - return $this->buildSideNavView(true)->getMenu(); + return id(new HarbormasterBuildPlanSearchEngine()) + ->setController($this) + ->buildResponse(); } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $can_create = $this->hasApplicationCapability( - HarbormasterCreatePlansCapability::CAPABILITY); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('New Build Plan')) - ->setHref($this->getApplicationURI('plan/edit/')) - ->setDisabled(!$can_create) - ->setWorkflow(!$can_create) - ->setIcon('fa-plus-square')); + id(new HarbormasterBuildPlanEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } - } diff --git a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php index fb664dc084..85bdbd8b49 100644 --- a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php @@ -1,6 +1,6 @@ getViewer(); diff --git a/src/applications/harbormaster/controller/HarbormasterStepAddController.php b/src/applications/harbormaster/controller/HarbormasterStepAddController.php index a5ec41b2e7..06412e2323 100644 --- a/src/applications/harbormaster/controller/HarbormasterStepAddController.php +++ b/src/applications/harbormaster/controller/HarbormasterStepAddController.php @@ -1,6 +1,7 @@ getViewer(); diff --git a/src/applications/harbormaster/controller/HarbormasterStepDeleteController.php b/src/applications/harbormaster/controller/HarbormasterStepDeleteController.php index 1aa8c24e4a..b4890a671a 100644 --- a/src/applications/harbormaster/controller/HarbormasterStepDeleteController.php +++ b/src/applications/harbormaster/controller/HarbormasterStepDeleteController.php @@ -1,6 +1,7 @@ getViewer(); diff --git a/src/applications/harbormaster/controller/HarbormasterStepEditController.php b/src/applications/harbormaster/controller/HarbormasterStepEditController.php index f15a3235b9..49c4563183 100644 --- a/src/applications/harbormaster/controller/HarbormasterStepEditController.php +++ b/src/applications/harbormaster/controller/HarbormasterStepEditController.php @@ -1,6 +1,7 @@ getViewer(); diff --git a/src/applications/harbormaster/controller/HarbormasterStepViewController.php b/src/applications/harbormaster/controller/HarbormasterStepViewController.php index 8f8abf6ea9..faa4b62c7e 100644 --- a/src/applications/harbormaster/controller/HarbormasterStepViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterStepViewController.php @@ -1,6 +1,7 @@ getViewer(); diff --git a/src/applications/harbormaster/editor/HarbormasterBuildPlanEditEngine.php b/src/applications/harbormaster/editor/HarbormasterBuildPlanEditEngine.php new file mode 100644 index 0000000000..342156c101 --- /dev/null +++ b/src/applications/harbormaster/editor/HarbormasterBuildPlanEditEngine.php @@ -0,0 +1,89 @@ +getViewer(); + return HarbormasterBuildPlan::initializeNewBuildPlan($viewer); + } + + protected function newObjectQuery() { + return new HarbormasterBuildPlanQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Build Plan'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Build Plan'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Build Plan: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Build Plan'); + } + + protected function getObjectCreateShortText() { + return pht('Create Build Plan'); + } + + protected function getEditorURI() { + return '/harbormaster/plan/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/harbormaster/plan/'; + } + + protected function getObjectViewURI($object) { + $id = $object->getID(); + return "/harbormaster/plan/{$id}/"; + } + + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + HarbormasterCreatePlansCapability::CAPABILITY); + } + + protected function buildCustomEditFields($object) { + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setIsRequired(true) + ->setTransactionType(HarbormasterBuildPlanTransaction::TYPE_NAME) + ->setDescription(pht('The build plan name.')) + ->setConduitDescription(pht('Rename the plan.')) + ->setConduitTypeDescription(pht('New plan name.')) + ->setValue($object->getName()), + ); + } + +} diff --git a/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php b/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php index 3b45ff108c..16fdd79f9e 100644 --- a/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php +++ b/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php @@ -90,7 +90,7 @@ final class HarbormasterBuildPlanEditor $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), - pht('Plan name is required.'), + pht('You must choose a name for your build plan.'), last($xactions)); $error->setIsMissingFieldError(true); From 2e19b78b8dd6ef85c0f708a3ffade1eef79d94bd Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 28 Feb 2016 16:28:43 -0800 Subject: [PATCH 05/41] Don't publish anything for "Disable Mail" / "Enable Mail" on projects Summary: Fixes T10466. Currently, clicking "Disable Mail" or "Enable Mail" on a project toggles an edge, but it gets a default "added an edge" story and transaction record. These are confusing, useless and not interesting, so just hide them. Test Plan: - Before patch: clicked enable/disable mail, saw "added an edge" / "removed an edge" stories in feed and project history. - After patch: clicked enable/disable mail, saw nothing in feed or project history. - (Note that this patch is not retroactive for feed, so already-published stories won't unpublish.) Reviewers: chad Reviewed By: chad Maniphest Tasks: T10466 Differential Revision: https://secure.phabricator.com/D15361 --- .../storage/PhabricatorProjectTransaction.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index ef5d39b4e8..2123afae2c 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -69,6 +69,21 @@ final class PhabricatorProjectTransaction return parent::getColor(); } + public function shouldHide() { + switch ($this->getTransactionType()) { + case PhabricatorTransactions::TYPE_EDGE: + $edge_type = $this->getMetadataValue('edge:type'); + switch ($edge_type) { + case PhabricatorProjectSilencedEdgeType::EDGECONST: + return true; + default: + break; + } + } + + return parent::shouldHide(); + } + public function shouldHideForFeed() { switch ($this->getTransactionType()) { case self::TYPE_HASWORKBOARD: From f078fd98d79bb6344a10fbbc91b53e0de9abaae6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 27 Feb 2016 08:59:33 -0800 Subject: [PATCH 06/41] Support searching for Harbormater build plans by name substring Summary: Ref T10457. Allow build plans to be queried by name. Test Plan: - Searched for plans by name. - Renamed a plan, searched for new name. {F1133085} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10457 Differential Revision: https://secure.phabricator.com/D15359 --- .../20160227.harbormaster.1.plann.sql | 7 +++++++ .../20160227.harbormaster.2.plani.php | 11 ++++++++++ src/__phutil_library_map__.php | 3 +++ .../editor/HarbormasterBuildPlanEditor.php | 4 ++++ .../query/HarbormasterBuildPlanQuery.php | 20 ++++++++++++++----- .../HarbormasterBuildPlanSearchEngine.php | 8 ++++++++ .../configuration/HarbormasterBuildPlan.php | 14 ++++++++++++- .../HarbormasterBuildPlanNameNgrams.php | 18 +++++++++++++++++ 8 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 resources/sql/autopatches/20160227.harbormaster.1.plann.sql create mode 100644 resources/sql/autopatches/20160227.harbormaster.2.plani.php create mode 100644 src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanNameNgrams.php diff --git a/resources/sql/autopatches/20160227.harbormaster.1.plann.sql b/resources/sql/autopatches/20160227.harbormaster.1.plann.sql new file mode 100644 index 0000000000..4c0b4f48b3 --- /dev/null +++ b/resources/sql/autopatches/20160227.harbormaster.1.plann.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildplanname_ngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + KEY `key_object` (objectID), + KEY `key_ngram` (ngram, objectID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160227.harbormaster.2.plani.php b/resources/sql/autopatches/20160227.harbormaster.2.plani.php new file mode 100644 index 0000000000..6dea004c06 --- /dev/null +++ b/resources/sql/autopatches/20160227.harbormaster.2.plani.php @@ -0,0 +1,11 @@ +getPHID(), + array( + 'force' => true, + )); +} diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 0a60ce8330..ce5912410f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1055,6 +1055,7 @@ phutil_register_library_map(array( 'HarbormasterBuildPlanDefaultViewCapability' => 'applications/harbormaster/capability/HarbormasterBuildPlanDefaultViewCapability.php', 'HarbormasterBuildPlanEditEngine' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditEngine.php', 'HarbormasterBuildPlanEditor' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditor.php', + 'HarbormasterBuildPlanNameNgrams' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanNameNgrams.php', 'HarbormasterBuildPlanPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildPlanPHIDType.php', 'HarbormasterBuildPlanQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanQuery.php', 'HarbormasterBuildPlanSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php', @@ -5199,12 +5200,14 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'PhabricatorSubscribableInterface', + 'PhabricatorNgramsInterface', ), 'HarbormasterBuildPlanDatasource' => 'PhabricatorTypeaheadDatasource', 'HarbormasterBuildPlanDefaultEditCapability' => 'PhabricatorPolicyCapability', 'HarbormasterBuildPlanDefaultViewCapability' => 'PhabricatorPolicyCapability', 'HarbormasterBuildPlanEditEngine' => 'PhabricatorEditEngine', 'HarbormasterBuildPlanEditor' => 'PhabricatorApplicationTransactionEditor', + 'HarbormasterBuildPlanNameNgrams' => 'PhabricatorSearchNgrams', 'HarbormasterBuildPlanPHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildPlanQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildPlanSearchEngine' => 'PhabricatorApplicationSearchEngine', diff --git a/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php b/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php index 16fdd79f9e..71c9283ade 100644 --- a/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php +++ b/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php @@ -11,6 +11,10 @@ final class HarbormasterBuildPlanEditor return pht('Harbormaster Build Plans'); } + protected function supportsSearch() { + return true; + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); $types[] = HarbormasterBuildPlanTransaction::TYPE_NAME; diff --git a/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php b/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php index efa04b037b..4058325140 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php @@ -35,6 +35,12 @@ final class HarbormasterBuildPlanQuery return $this; } + public function withNameNgrams($ngrams) { + return $this->withNgramsConstraint( + new HarbormasterBuildPlanNameNgrams(), + $ngrams); + } + public function needBuildSteps($need) { $this->needBuildSteps = $need; return $this; @@ -74,41 +80,45 @@ final class HarbormasterBuildPlanQuery if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'plan.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'plan.phid IN (%Ls)', $this->phids); } if ($this->statuses !== null) { $where[] = qsprintf( $conn, - 'planStatus IN (%Ls)', + 'plan.planStatus IN (%Ls)', $this->statuses); } if (strlen($this->datasourceQuery)) { $where[] = qsprintf( $conn, - 'name LIKE %>', + 'plan.name LIKE %>', $this->datasourceQuery); } if ($this->planAutoKeys !== null) { $where[] = qsprintf( $conn, - 'planAutoKey IN (%Ls)', + 'plan.planAutoKey IN (%Ls)', $this->planAutoKeys); } return $where; } + protected function getPrimaryTableAlias() { + return 'plan'; + } + public function getQueryApplicationClass() { return 'PhabricatorHarbormasterApplication'; } diff --git a/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php index 2a05cc21f3..cc345aa1fe 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php +++ b/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php @@ -17,6 +17,10 @@ final class HarbormasterBuildPlanSearchEngine protected function buildCustomSearchFields() { return array( + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Name Contains')) + ->setKey('match') + ->setDescription(pht('Search for namespaces by name substring.')), id(new PhabricatorSearchCheckboxesField()) ->setLabel(pht('Status')) ->setKey('status') @@ -32,6 +36,10 @@ final class HarbormasterBuildPlanSearchEngine protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); + if ($map['match'] !== null) { + $query->withNameNgrams($map['match']); + } + if ($map['status']) { $query->withStatuses($map['status']); } diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php index 5ac9696813..31d84da7aa 100644 --- a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php +++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php @@ -7,7 +7,8 @@ final class HarbormasterBuildPlan extends HarbormasterDAO implements PhabricatorApplicationTransactionInterface, PhabricatorPolicyInterface, - PhabricatorSubscribableInterface { + PhabricatorSubscribableInterface, + PhabricatorNgramsInterface { protected $name; protected $planStatus; @@ -198,4 +199,15 @@ final class HarbormasterBuildPlan extends HarbormasterDAO return $messages; } + +/* -( PhabricatorNgramInterface )------------------------------------------ */ + + + public function newNgrams() { + return array( + id(new HarbormasterBuildPlanNameNgrams()) + ->setValue($this->getName()), + ); + } + } diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanNameNgrams.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanNameNgrams.php new file mode 100644 index 0000000000..e91daad8ce --- /dev/null +++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanNameNgrams.php @@ -0,0 +1,18 @@ + Date: Sat, 27 Feb 2016 09:19:20 -0800 Subject: [PATCH 07/41] Allow Harbormaster build plans to be tagged with projects and searched by tag Summary: Ref T10457. This is mostly just for consitency, but I imagine it will make managing large/complex build processes easier, and if we support Herald rules it would eventually let you write "Build plan's tags include [whatever]" to apply behavior to a group of plans. Test Plan: {F1133107} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10457 Differential Revision: https://secure.phabricator.com/D15360 --- src/__phutil_library_map__.php | 1 + .../HarbormasterBuildPlanSearchEngine.php | 25 ++++++++++++++++++- .../configuration/HarbormasterBuildPlan.php | 3 ++- .../phid/view/PHUIHandleTagListView.php | 7 ++++-- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index ce5912410f..c2e8c628e6 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5201,6 +5201,7 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorSubscribableInterface', 'PhabricatorNgramsInterface', + 'PhabricatorProjectInterface', ), 'HarbormasterBuildPlanDatasource' => 'PhabricatorTypeaheadDatasource', 'HarbormasterBuildPlanDefaultEditCapability' => 'PhabricatorPolicyCapability', diff --git a/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php index cc345aa1fe..36f86b9ec5 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php +++ b/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php @@ -84,12 +84,24 @@ final class HarbormasterBuildPlanSearchEngine $viewer = $this->requireViewer(); + + if ($plans) { + $edge_query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs(mpull($plans, 'getPHID')) + ->withEdgeTypes( + array( + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, + )); + + $edge_query->execute(); + } + $list = new PHUIObjectItemListView(); foreach ($plans as $plan) { $id = $plan->getID(); $item = id(new PHUIObjectItemView()) - ->setObjectName(pht('Plan %d', $plan->getID())) + ->setObjectName(pht('Plan %d', $id)) ->setHeader($plan->getName()); if ($plan->isDisabled()) { @@ -102,6 +114,17 @@ final class HarbormasterBuildPlanSearchEngine $item->setHref($this->getApplicationURI("plan/{$id}/")); + $phid = $plan->getPHID(); + $project_phids = $edge_query->getDestinationPHIDs(array($phid)); + $project_handles = $viewer->loadHandles($project_phids); + + $item->addAttribute( + id(new PHUIHandleTagListView()) + ->setLimit(4) + ->setNoDataString(pht('No Projects')) + ->setSlim(true) + ->setHandles($project_handles)); + $list->addItem($item); } diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php index 31d84da7aa..c5b2142ac5 100644 --- a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php +++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php @@ -8,7 +8,8 @@ final class HarbormasterBuildPlan extends HarbormasterDAO PhabricatorApplicationTransactionInterface, PhabricatorPolicyInterface, PhabricatorSubscribableInterface, - PhabricatorNgramsInterface { + PhabricatorNgramsInterface, + PhabricatorProjectInterface { protected $name; protected $planStatus; diff --git a/src/applications/phid/view/PHUIHandleTagListView.php b/src/applications/phid/view/PHUIHandleTagListView.php index 0bbfc4d249..b789fa1bf1 100644 --- a/src/applications/phid/view/PHUIHandleTagListView.php +++ b/src/applications/phid/view/PHUIHandleTagListView.php @@ -53,7 +53,7 @@ final class PHUIHandleTagListView extends AphrontTagView { $handles = $this->handles; // If the list is empty, we may render a "No Projects" tag. - if (!$handles) { + if (!count($handles)) { if (strlen($this->noDataString)) { $no_data_tag = $this->newPlaceholderTag() ->setName($this->noDataString); @@ -61,7 +61,10 @@ final class PHUIHandleTagListView extends AphrontTagView { } } - if ($this->limit) { + if ($this->limit && ($this->limit > count($handles))) { + if (!is_array($handles)) { + $handles = iterator_to_array($handles); + } $handles = array_slice($handles, 0, $this->limit); } From 3c19b72ca0c8635d853571c9bf69bf075b0e18af Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 29 Feb 2016 06:15:13 -0800 Subject: [PATCH 08/41] Begin making Harbormaster unit test results a little easier to read Summary: Ref T10457. These lack color and iconography and are difficult to parse. Make them easier to read. Test Plan: Before: {F1135396} After: {F1135399} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10457 Differential Revision: https://secure.phabricator.com/D15362 --- src/__phutil_library_map__.php | 2 + .../customfield/DifferentialUnitField.php | 66 ++++---------- .../constants/HarbormasterUnitStatus.php | 89 +++++++++++++++++++ .../HarbormasterBuildableViewController.php | 23 ++++- .../build/HarbormasterBuildUnitMessage.php | 13 +-- .../view/HarbormasterUnitPropertyView.php | 75 +++++++++++----- 6 files changed, 184 insertions(+), 84 deletions(-) create mode 100644 src/applications/harbormaster/constants/HarbormasterUnitStatus.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index c2e8c628e6..9d4b2dbea2 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1147,6 +1147,7 @@ phutil_register_library_map(array( 'HarbormasterURIArtifact' => 'applications/harbormaster/artifact/HarbormasterURIArtifact.php', 'HarbormasterUnitMessagesController' => 'applications/harbormaster/controller/HarbormasterUnitMessagesController.php', 'HarbormasterUnitPropertyView' => 'applications/harbormaster/view/HarbormasterUnitPropertyView.php', + 'HarbormasterUnitStatus' => 'applications/harbormaster/constants/HarbormasterUnitStatus.php', 'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php', 'HarbormasterWaitForPreviousBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php', 'HarbormasterWorker' => 'applications/harbormaster/worker/HarbormasterWorker.php', @@ -5314,6 +5315,7 @@ phutil_register_library_map(array( 'HarbormasterURIArtifact' => 'HarbormasterArtifact', 'HarbormasterUnitMessagesController' => 'HarbormasterController', 'HarbormasterUnitPropertyView' => 'AphrontView', + 'HarbormasterUnitStatus' => 'Phobject', 'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterWaitForPreviousBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterWorker' => 'PhabricatorWorker', diff --git a/src/applications/differential/customfield/DifferentialUnitField.php b/src/applications/differential/customfield/DifferentialUnitField.php index 17973be7a6..4b14b3d364 100644 --- a/src/applications/differential/customfield/DifferentialUnitField.php +++ b/src/applications/differential/customfield/DifferentialUnitField.php @@ -54,7 +54,9 @@ final class DifferentialUnitField $this->getModernUnitMessageDictionary($message)); } - protected function newHarbormasterMessageView(array $messages) { + protected function newHarbormasterMessageView(array $all_messages) { + $messages = $all_messages; + foreach ($messages as $key => $message) { switch ($message->getResult()) { case ArcanistUnitTestResult::RESULT_PASS: @@ -71,9 +73,18 @@ final class DifferentialUnitField return null; } - return id(new HarbormasterUnitPropertyView()) - ->setLimit(10) - ->setUnitMessages($messages); + $table = id(new HarbormasterUnitPropertyView()) + ->setLimit(5) + ->setUnitMessages($all_messages); + + $diff = $this->getObject()->getActiveDiff(); + $buildable = $diff->getBuildable(); + if ($buildable) { + $full_results = '/harbormaster/unit/'.$buildable->getID().'/'; + $table->setFullResultsURI($full_results); + } + + return $table; } public function getWarningsForDetailView() { @@ -112,53 +123,6 @@ final class DifferentialUnitField $note = array(); - $groups = mgroup($messages, 'getResult'); - - $groups = array_select_keys( - $groups, - array( - ArcanistUnitTestResult::RESULT_FAIL, - ArcanistUnitTestResult::RESULT_BROKEN, - ArcanistUnitTestResult::RESULT_UNSOUND, - ArcanistUnitTestResult::RESULT_SKIP, - ArcanistUnitTestResult::RESULT_PASS, - )) + $groups; - - foreach ($groups as $result => $group) { - $count = phutil_count($group); - switch ($result) { - case ArcanistUnitTestResult::RESULT_PASS: - $note[] = pht('%s Passed Test(s)', $count); - break; - case ArcanistUnitTestResult::RESULT_FAIL: - $note[] = pht('%s Failed Test(s)', $count); - break; - case ArcanistUnitTestResult::RESULT_SKIP: - $note[] = pht('%s Skipped Test(s)', $count); - break; - case ArcanistUnitTestResult::RESULT_BROKEN: - $note[] = pht('%s Broken Test(s)', $count); - break; - case ArcanistUnitTestResult::RESULT_UNSOUND: - $note[] = pht('%s Unsound Test(s)', $count); - break; - default: - $note[] = pht('%s Other Test(s)', $count); - break; - } - } - - $buildable = $diff->getBuildable(); - if ($buildable) { - $full_results = '/harbormaster/unit/'.$buildable->getID().'/'; - $note[] = phutil_tag( - 'a', - array( - 'href' => $full_results, - ), - pht('View Full Results')); - } - $excuse = $diff->getProperty('arc:unit-excuse'); if (strlen($excuse)) { $excuse = array( diff --git a/src/applications/harbormaster/constants/HarbormasterUnitStatus.php b/src/applications/harbormaster/constants/HarbormasterUnitStatus.php new file mode 100644 index 0000000000..ea79c7843d --- /dev/null +++ b/src/applications/harbormaster/constants/HarbormasterUnitStatus.php @@ -0,0 +1,89 @@ + array( + 'label' => pht('Failed'), + 'icon' => 'fa-times', + 'color' => 'red', + 'sort' => 'A', + ), + ArcanistUnitTestResult::RESULT_BROKEN => array( + 'label' => pht('Broken'), + 'icon' => 'fa-bomb', + 'color' => 'indigo', + 'sort' => 'B', + ), + ArcanistUnitTestResult::RESULT_UNSOUND => array( + 'label' => pht('Unsound'), + 'icon' => 'fa-exclamation-triangle', + 'color' => 'yellow', + 'sort' => 'C', + ), + ArcanistUnitTestResult::RESULT_SKIP => array( + 'label' => pht('Skipped'), + 'icon' => 'fa-fast-forward', + 'color' => 'blue', + ), + ArcanistUnitTestResult::RESULT_PASS => array( + 'label' => pht('Passed'), + 'icon' => 'fa-check', + 'color' => 'green', + 'sort' => 'Z', + ), + ); + } + +} diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php index 42f2572266..f6487ff661 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php @@ -336,15 +336,32 @@ final class HarbormasterBuildableViewController } if ($unit_data) { + $unit_href = $this->getApplicationURI('unit/'.$buildable->getID().'/'); + $unit_table = id(new HarbormasterUnitPropertyView()) ->setUser($viewer) - ->setLimit(25) - ->setUnitMessages($unit_data); + ->setLimit(5) + ->setUnitMessages($unit_data) + ->setFullResultsURI($unit_href); - $unit_href = $this->getApplicationURI('unit/'.$buildable->getID().'/'); + $unit_data = msort($unit_data, 'getSortKey'); + $head_unit = head($unit_data); + if ($head_unit) { + $status = $head_unit->getResult(); + + $tag_text = HarbormasterUnitStatus::getUnitStatusLabel($status); + $tag_color = HarbormasterUnitStatus::getUnitStatusColor($status); + $tag_icon = HarbormasterUnitStatus::getUnitStatusIcon($status); + + } else { + $tag_text = pht('No Unit Tests'); + $tag_color = 'grey'; + $tag_icon = 'fa-ban'; + } $unit_header = id(new PHUIHeaderView()) ->setHeader(pht('Unit Tests')) + ->setStatus($tag_icon, $tag_color, $tag_text) ->addActionLink( id(new PHUIButtonView()) ->setTag('a') diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php b/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php index 1062b64a3a..5fa0576f3b 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php @@ -136,18 +136,11 @@ final class HarbormasterBuildUnitMessage } public function getSortKey() { - // TODO: Maybe use more numeric values after T6861. - $map = array( - ArcanistUnitTestResult::RESULT_FAIL => 'A', - ArcanistUnitTestResult::RESULT_BROKEN => 'B', - ArcanistUnitTestResult::RESULT_UNSOUND => 'C', - ArcanistUnitTestResult::RESULT_PASS => 'Z', - ); - - $result = idx($map, $this->getResult(), 'N'); + $status = $this->getResult(); + $sort = HarbormasterUnitStatus::getUnitStatusSort($status); $parts = array( - $result, + $sort, $this->getEngine(), $this->getNamespace(), $this->getName(), diff --git a/src/applications/harbormaster/view/HarbormasterUnitPropertyView.php b/src/applications/harbormaster/view/HarbormasterUnitPropertyView.php index 2614c25743..bf1869dc59 100644 --- a/src/applications/harbormaster/view/HarbormasterUnitPropertyView.php +++ b/src/applications/harbormaster/view/HarbormasterUnitPropertyView.php @@ -5,6 +5,7 @@ final class HarbormasterUnitPropertyView extends AphrontView { private $pathURIMap = array(); private $unitMessages = array(); private $limit; + private $fullResultsURI; public function setPathURIMap(array $map) { $this->pathURIMap = $map; @@ -22,18 +23,39 @@ final class HarbormasterUnitPropertyView extends AphrontView { return $this; } + public function setFullResultsURI($full_results_uri) { + $this->fullResultsURI = $full_results_uri; + return $this; + } + public function render() { $messages = $this->unitMessages; $messages = msort($messages, 'getSortKey'); + $limit = $this->limit; + if ($this->limit) { - $messages = array_slice($messages, 0, $this->limit); + $display_messages = array_slice($messages, 0, $limit); + } else { + $display_messages = $messages; } $rows = array(); $any_duration = false; - foreach ($messages as $message) { - $result = $this->renderResult($message->getResult()); + foreach ($display_messages as $message) { + $status = $message->getResult(); + + $icon_icon = HarbormasterUnitStatus::getUnitStatusIcon($status); + $icon_color = HarbormasterUnitStatus::getUnitStatusColor($status); + $icon_label = HarbormasterUnitStatus::getUnitStatusLabel($status); + + $result_icon = id(new PHUIIconView()) + ->setIcon("{$icon_icon} {$icon_color}") + ->addSigil('has-tooltip') + ->setMetadata( + array( + 'tip' => $icon_label, + )); $duration = $message->getDuration(); if ($duration !== null) { @@ -54,16 +76,44 @@ final class HarbormasterUnitPropertyView extends AphrontView { } $rows[] = array( - $result, + $result_icon, $duration, $name, ); } + $full_uri = $this->fullResultsURI; + if ($full_uri && (count($messages) > $limit)) { + $counts = array(); + + $groups = mgroup($messages, 'getResult'); + foreach ($groups as $status => $group) { + $counts[] = HarbormasterUnitStatus::getUnitStatusCountLabel( + $status, + count($group)); + } + + $link_text = pht( + 'View Full Test Results (%s)', + implode(" \xC2\xB7 ", $counts)); + + $full_link = phutil_tag( + 'a', + array( + 'href' => $full_uri, + ), + $link_text); + + $link_icon = id(new PHUIIconView()) + ->setIcon('fa-ellipsis-h lightgreytext'); + + $rows[] = array($link_icon, null, $full_link); + } + $table = id(new AphrontTableView($rows)) ->setHeaders( array( - pht('Result'), + null, pht('Time'), pht('Test'), )) @@ -82,19 +132,4 @@ final class HarbormasterUnitPropertyView extends AphrontView { return $table; } - private function renderResult($result) { - $names = array( - ArcanistUnitTestResult::RESULT_BROKEN => pht('Broken'), - ArcanistUnitTestResult::RESULT_FAIL => pht('Failed'), - ArcanistUnitTestResult::RESULT_UNSOUND => pht('Unsound'), - ArcanistUnitTestResult::RESULT_SKIP => pht('Skipped'), - ArcanistUnitTestResult::RESULT_PASS => pht('Passed'), - ); - $result = idx($names, $result, $result); - - // TODO: Add some color. - - return $result; - } - } From d436fecdab19210c06db2a0a35e9b5fe39da8b52 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 29 Feb 2016 06:26:01 -0800 Subject: [PATCH 09/41] Show additional details for failed builds in Harbormaster Summary: Ref T10457. When tests fail, it currently takes several clicks to figure out //why// they failed. In this project, map rebuilds and `liberate` are fairly common failure conditions, but verifying that they were the root issue requires jumping into a build, then scrolling through a log. Instead, display details if they're available. Test Plan: Before: {F1135453} After: {F1135454} Reviewers: chad Reviewed By: chad Maniphest Tasks: T9951, T10457 Differential Revision: https://secure.phabricator.com/D15363 --- resources/celerity/map.php | 10 +- src/__phutil_library_map__.php | 6 +- .../PhabricatorHarbormasterApplication.php | 3 +- ...HarbormasterUnitMessageListController.php} | 2 +- .../HarbormasterUnitMessageViewController.php | 122 ++++++++++++++++++ .../storage/build/HarbormasterBuild.php | 5 + .../build/HarbormasterBuildUnitMessage.php | 34 +++++ .../view/HarbormasterUnitPropertyView.php | 56 ++++++-- src/view/control/AphrontTableView.php | 35 ++++- webroot/rsrc/css/aphront/table-view.css | 4 + .../application/harbormaster/harbormaster.css | 8 ++ 11 files changed, 263 insertions(+), 22 deletions(-) rename src/applications/harbormaster/controller/{HarbormasterUnitMessagesController.php => HarbormasterUnitMessageListController.php} (96%) create mode 100644 src/applications/harbormaster/controller/HarbormasterUnitMessageViewController.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 44383032b7..49bde5d58e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'ecdca229', + 'core.pkg.css' => 'f7a91f6a', 'core.pkg.js' => '7d8faf57', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', @@ -25,7 +25,7 @@ return array( 'rsrc/css/aphront/notification.css' => '7f684b62', 'rsrc/css/aphront/panel-view.css' => '8427b78d', 'rsrc/css/aphront/phabricator-nav-view.css' => 'ac79a758', - 'rsrc/css/aphront/table-view.css' => '6d01d468', + 'rsrc/css/aphront/table-view.css' => 'ec078a76', 'rsrc/css/aphront/tokenizer.css' => '056da01b', 'rsrc/css/aphront/tooltip.css' => '1a07aea8', 'rsrc/css/aphront/typeahead-browse.css' => 'd8581d2c', @@ -70,7 +70,7 @@ return array( 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 'rsrc/css/application/files/global-drag-and-drop.css' => '5c1b47c2', 'rsrc/css/application/flag/flag.css' => '5337623f', - 'rsrc/css/application/harbormaster/harbormaster.css' => 'b0758ca5', + 'rsrc/css/application/harbormaster/harbormaster.css' => '834879db', 'rsrc/css/application/herald/herald-test.css' => 'a52e323e', 'rsrc/css/application/herald/herald.css' => '826075fa', 'rsrc/css/application/maniphest/batch-editor.css' => 'b0f0b6d5', @@ -523,7 +523,7 @@ return array( 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => 'fd18389d', 'aphront-panel-view-css' => '8427b78d', - 'aphront-table-view-css' => '6d01d468', + 'aphront-table-view-css' => 'ec078a76', 'aphront-tokenizer-control-css' => '056da01b', 'aphront-tooltip-css' => '1a07aea8', 'aphront-typeahead-control-css' => 'd4f16145', @@ -558,7 +558,7 @@ return array( 'font-fontawesome' => 'c43323c5', 'font-lato' => 'c7ccd872', 'global-drag-and-drop-css' => '5c1b47c2', - 'harbormaster-css' => 'b0758ca5', + 'harbormaster-css' => '834879db', 'herald-css' => '826075fa', 'herald-rule-editor' => '746ca158', 'herald-test-css' => 'a52e323e', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9d4b2dbea2..32603c9bf5 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1145,7 +1145,8 @@ phutil_register_library_map(array( 'HarbormasterThrowExceptionBuildStep' => 'applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php', 'HarbormasterUIEventListener' => 'applications/harbormaster/event/HarbormasterUIEventListener.php', 'HarbormasterURIArtifact' => 'applications/harbormaster/artifact/HarbormasterURIArtifact.php', - 'HarbormasterUnitMessagesController' => 'applications/harbormaster/controller/HarbormasterUnitMessagesController.php', + 'HarbormasterUnitMessageListController' => 'applications/harbormaster/controller/HarbormasterUnitMessageListController.php', + 'HarbormasterUnitMessageViewController' => 'applications/harbormaster/controller/HarbormasterUnitMessageViewController.php', 'HarbormasterUnitPropertyView' => 'applications/harbormaster/view/HarbormasterUnitPropertyView.php', 'HarbormasterUnitStatus' => 'applications/harbormaster/constants/HarbormasterUnitStatus.php', 'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php', @@ -5313,7 +5314,8 @@ phutil_register_library_map(array( 'HarbormasterThrowExceptionBuildStep' => 'HarbormasterBuildStepImplementation', 'HarbormasterUIEventListener' => 'PhabricatorEventListener', 'HarbormasterURIArtifact' => 'HarbormasterArtifact', - 'HarbormasterUnitMessagesController' => 'HarbormasterController', + 'HarbormasterUnitMessageListController' => 'HarbormasterController', + 'HarbormasterUnitMessageViewController' => 'HarbormasterController', 'HarbormasterUnitPropertyView' => 'AphrontView', 'HarbormasterUnitStatus' => 'Phobject', 'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation', diff --git a/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php b/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php index a2956432ff..d111f266a6 100644 --- a/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php +++ b/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php @@ -85,7 +85,8 @@ final class PhabricatorHarbormasterApplication extends PhabricatorApplication { '(?P\d+)/' => 'HarbormasterPlanViewController', ), 'unit/' => array( - '(?P\d+)/' => 'HarbormasterUnitMessagesController', + '(?P\d+)/' => 'HarbormasterUnitMessageListController', + 'view/(?P\d+)/' => 'HarbormasterUnitMessageViewController', ), 'lint/' => array( '(?P\d+)/' => 'HarbormasterLintMessagesController', diff --git a/src/applications/harbormaster/controller/HarbormasterUnitMessagesController.php b/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php similarity index 96% rename from src/applications/harbormaster/controller/HarbormasterUnitMessagesController.php rename to src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php index ada83f9a5a..032777674f 100644 --- a/src/applications/harbormaster/controller/HarbormasterUnitMessagesController.php +++ b/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php @@ -1,6 +1,6 @@ getViewer(); + + $message_id = $request->getURIData('id'); + + $message = id(new HarbormasterBuildUnitMessage())->load($message_id); + if (!$message) { + return new Aphront404Response(); + } + + $build_target = id(new HarbormasterBuildTargetQuery()) + ->setViewer($viewer) + ->withPHIDs(array($message->getBuildTargetPHID())) + ->executeOne(); + if (!$build_target) { + return new Aphront404Response(); + } + + $build = $build_target->getBuild(); + $buildable = $build->getBuildable(); + $buildable_id = $buildable->getID(); + + $id = $message->getID(); + $display_name = $message->getUnitMessageDisplayName(); + + $status = $message->getResult(); + $status_icon = HarbormasterUnitStatus::getUnitStatusIcon($status); + $status_color = HarbormasterUnitStatus::getUnitStatusColor($status); + $status_label = HarbormasterUnitStatus::getUnitStatusLabel($status); + + $header = id(new PHUIHeaderView()) + ->setHeader($display_name) + ->setStatus($status_icon, $status_color, $status_label); + + $properties = $this->buildPropertyListView($message); + $actions = $this->buildActionView($message, $build); + + $properties->setActionList($actions); + + $unit = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->addPropertyList($properties); + + $crumbs = $this->buildApplicationCrumbs(); + $this->addBuildableCrumb($crumbs, $buildable); + + $crumbs->addTextCrumb( + pht('Unit Tests'), + "/harbormaster/unit/{$buildable_id}/"); + + $crumbs->addTextCrumb(pht('Unit %d', $id)); + + $title = array( + $display_name, + $buildable->getMonogram(), + ); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($unit); + } + + private function buildPropertyListView( + HarbormasterBuildUnitMessage $message) { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $view->addProperty( + pht('Run At'), + phabricator_datetime($message->getDateCreated(), $viewer)); + + $details = $message->getUnitMessageDetails(); + if (strlen($details)) { + // TODO: Use the log view here, once it gets cleaned up. + $details = phutil_tag( + 'div', + array( + 'class' => 'PhabricatorMonospaced', + 'style' => + 'white-space: pre-wrap; '. + 'color: #666666; '. + 'overflow-x: auto;', + ), + $details); + } else { + $details = phutil_tag('em', array(), pht('No details provided.')); + } + + $view->addSectionHeader( + pht('Details'), + PHUIPropertyListView::ICON_TESTPLAN); + $view->addTextContent($details); + + return $view; + } + + private function buildActionView( + HarbormasterBuildUnitMessage $message, + HarbormasterBuild $build) { + $viewer = $this->getViewer(); + + $view = id(new PhabricatorActionListView()) + ->setUser($viewer); + + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('View Build')) + ->setHref($build->getURI()) + ->setIcon('fa-wrench')); + + return $view; + } +} diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php index 1bac8d3ecd..5134d0efa0 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -323,6 +323,11 @@ final class HarbormasterBuild extends HarbormasterDAO return ($this->getBuildStatus() == self::STATUS_PAUSED); } + public function getURI() { + $id = $this->getID(); + return "/harbormaster/build/{$id}/"; + } + /* -( Build Commands )----------------------------------------------------- */ diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php b/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php index 5fa0576f3b..956850bd9f 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php @@ -61,6 +61,11 @@ final class HarbormasterBuildUnitMessage 'description' => pht( 'Coverage information for this test.'), ), + 'details' => array( + 'type' => 'optional string', + 'description' => pht( + 'Additional human-readable information about the failure.'), + ), ); } @@ -94,6 +99,11 @@ final class HarbormasterBuildUnitMessage $obj->setProperty('coverage', $coverage); } + $details = idx($dict, 'details'); + if ($details) { + $obj->setProperty('details', $details); + } + return $obj; } @@ -135,6 +145,30 @@ final class HarbormasterBuildUnitMessage return $this; } + public function getUnitMessageDetails() { + return $this->getProperty('details', ''); + } + + public function getUnitMessageDisplayName() { + $name = $this->getName(); + + $namespace = $this->getNamespace(); + if (strlen($namespace)) { + $name = $namespace.'::'.$name; + } + + $engine = $this->getEngine(); + if (strlen($engine)) { + $name = $engine.' > '.$name; + } + + if (!strlen($name)) { + return pht('Nameless Test (%d)', $this->getID()); + } + + return $name; + } + public function getSortKey() { $status = $this->getResult(); $sort = HarbormasterUnitStatus::getUnitStatusSort($status); diff --git a/src/applications/harbormaster/view/HarbormasterUnitPropertyView.php b/src/applications/harbormaster/view/HarbormasterUnitPropertyView.php index bf1869dc59..0d58140a35 100644 --- a/src/applications/harbormaster/view/HarbormasterUnitPropertyView.php +++ b/src/applications/harbormaster/view/HarbormasterUnitPropertyView.php @@ -29,6 +29,8 @@ final class HarbormasterUnitPropertyView extends AphrontView { } public function render() { + require_celerity_resource('harbormaster-css'); + $messages = $this->unitMessages; $messages = msort($messages, 'getSortKey'); @@ -63,16 +65,22 @@ final class HarbormasterUnitPropertyView extends AphrontView { $duration = pht('%s ms', new PhutilNumber((int)(1000 * $duration))); } - $name = $message->getName(); + $name = $message->getUnitMessageDisplayName(); + $id = $message->getID(); - $namespace = $message->getNamespace(); - if (strlen($namespace)) { - $name = $namespace.'::'.$name; - } + $name = phutil_tag( + 'a', + array( + 'href' => "/harbormaster/unit/view/{$id}/", + ), + $name); - $engine = $message->getEngine(); - if (strlen($engine)) { - $name = $engine.' > '.$name; + $details = $message->getUnitMessageDetails(); + if (strlen($details)) { + $name = array( + $name, + $this->renderUnitTestDetails($details), + ); } $rows[] = array( @@ -119,9 +127,14 @@ final class HarbormasterUnitPropertyView extends AphrontView { )) ->setColumnClasses( array( - null, - null, - 'pri wide', + 'top', + 'top', + 'top wide', + )) + ->setColumnWidths( + array( + '32px', + '64px', )) ->setColumnVisibility( array( @@ -132,4 +145,25 @@ final class HarbormasterUnitPropertyView extends AphrontView { return $table; } + private function renderUnitTestDetails($full_details) { + $details = id(new PhutilUTF8StringTruncator()) + ->setMaximumBytes(2048) + ->truncateString($full_details); + $details = phutil_split_lines($details); + + $limit = 3; + if (count($details) > $limit) { + $details = array_slice($details, 0, $limit); + } + + $details = implode('', $details); + + return phutil_tag( + 'div', + array( + 'class' => 'PhabricatorMonospaced harbormaster-unit-details', + ), + $details); + } + } diff --git a/src/view/control/AphrontTableView.php b/src/view/control/AphrontTableView.php index b74ccd3124..97e88f4f33 100644 --- a/src/view/control/AphrontTableView.php +++ b/src/view/control/AphrontTableView.php @@ -15,6 +15,8 @@ final class AphrontTableView extends AphrontView { protected $columnVisibility = array(); private $deviceVisibility = array(); + private $columnWidths = array(); + protected $sortURI; protected $sortParam; protected $sortSelected; @@ -46,6 +48,11 @@ final class AphrontTableView extends AphrontView { return $this; } + public function setColumnWidths(array $widths) { + $this->columnWidths = $widths; + return $this; + } + public function setNoDataString($no_data_string) { $this->noDataString = $no_data_string; return $this; @@ -131,6 +138,8 @@ final class AphrontTableView extends AphrontView { $visibility = array_values($this->columnVisibility); $device_visibility = array_values($this->deviceVisibility); + $column_widths = $this->columnWidths; + $headers = $this->headers; $short_headers = $this->shortHeaders; $sort_values = $this->sortValues; @@ -236,7 +245,18 @@ final class AphrontTableView extends AphrontView { $header = hsprintf('%s %s', $header_nodevice, $header_device); } - $tr[] = phutil_tag('th', array('class' => $class), $header); + $style = null; + if (isset($column_widths[$col_num])) { + $style = 'width: '.$column_widths[$col_num].';'; + } + + $tr[] = phutil_tag( + 'th', + array( + 'class' => $class, + 'style' => $style, + ), + $header); } $table[] = phutil_tag('tr', array(), $tr); } @@ -283,7 +303,13 @@ final class AphrontTableView extends AphrontView { if (!empty($this->cellClasses[$row_num][$col_num])) { $class = trim($class.' '.$this->cellClasses[$row_num][$col_num]); } - $tr[] = phutil_tag('td', array('class' => $class), $value); + + $tr[] = phutil_tag( + 'td', + array( + 'class' => $class, + ), + $value); ++$col_num; } @@ -315,10 +341,15 @@ final class AphrontTableView extends AphrontView { if ($this->className !== null) { $classes[] = $this->className; } + if ($this->deviceReadyTable) { $classes[] = 'aphront-table-view-device-ready'; } + if ($this->columnWidths) { + $classes[] = 'aphront-table-view-fixed'; + } + $html = phutil_tag( 'table', array( diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css index b235fd9c47..8a842e9538 100644 --- a/webroot/rsrc/css/aphront/table-view.css +++ b/webroot/rsrc/css/aphront/table-view.css @@ -15,6 +15,10 @@ border-bottom: 1px solid {$blueborder}; } +.aphront-table-view-fixed { + table-layout: fixed; +} + .aphront-table-view td.aphront-table-notice { padding: 12px 16px; font-size: {$normalfontsize}; diff --git a/webroot/rsrc/css/application/harbormaster/harbormaster.css b/webroot/rsrc/css/application/harbormaster/harbormaster.css index 6158582ddd..a80c8b7418 100644 --- a/webroot/rsrc/css/application/harbormaster/harbormaster.css +++ b/webroot/rsrc/css/application/harbormaster/harbormaster.css @@ -20,3 +20,11 @@ padding: 12px; color: {$darkgreytext}; } + +.harbormaster-unit-details { + margin: 8px 0 4px; + overflow: hidden; + white-space: pre; + text-overflow: ellipsis; + color: {$lightgreytext}; +} From 181e030535f6c8a24cee617eb679f12ff7490e06 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 29 Feb 2016 09:53:46 -0800 Subject: [PATCH 10/41] Give unit test results their own table in Differential Summary: Ref T10457. This gives unit test results a more first-class treatment in the Differential UI, and consolidates some rendering code. Test Plan: Before: {F1135536} After: {F1135537} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10457 Differential Revision: https://secure.phabricator.com/D15365 --- resources/celerity/map.php | 6 +- src/__phutil_library_map__.php | 4 +- .../controller/DifferentialController.php | 126 ++++++++++++++++++ .../DifferentialRevisionViewController.php | 76 +++++++---- .../customfield/DifferentialUnitField.php | 91 +------------ .../differential/storage/DifferentialDiff.php | 27 ++++ .../HarbormasterBuildableViewController.php | 39 +----- .../HarbormasterUnitMessageListController.php | 8 +- .../view/HarbormasterUnitPropertyView.php | 29 ++-- .../view/HarbormasterUnitSummaryView.php | 104 +++++++++++++++ src/view/control/AphrontTableView.php | 33 ++--- webroot/rsrc/css/aphront/table-view.css | 6 +- 12 files changed, 365 insertions(+), 184 deletions(-) create mode 100644 src/applications/harbormaster/view/HarbormasterUnitSummaryView.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 49bde5d58e..1a4c791ddb 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'f7a91f6a', + 'core.pkg.css' => '76a3afdf', 'core.pkg.js' => '7d8faf57', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', @@ -25,7 +25,7 @@ return array( 'rsrc/css/aphront/notification.css' => '7f684b62', 'rsrc/css/aphront/panel-view.css' => '8427b78d', 'rsrc/css/aphront/phabricator-nav-view.css' => 'ac79a758', - 'rsrc/css/aphront/table-view.css' => 'ec078a76', + 'rsrc/css/aphront/table-view.css' => 'aba95954', 'rsrc/css/aphront/tokenizer.css' => '056da01b', 'rsrc/css/aphront/tooltip.css' => '1a07aea8', 'rsrc/css/aphront/typeahead-browse.css' => 'd8581d2c', @@ -523,7 +523,7 @@ return array( 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => 'fd18389d', 'aphront-panel-view-css' => '8427b78d', - 'aphront-table-view-css' => 'ec078a76', + 'aphront-table-view-css' => 'aba95954', 'aphront-tokenizer-control-css' => '056da01b', 'aphront-tooltip-css' => '1a07aea8', 'aphront-typeahead-control-css' => 'd4f16145', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 32603c9bf5..a3f54fbf3e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1149,6 +1149,7 @@ phutil_register_library_map(array( 'HarbormasterUnitMessageViewController' => 'applications/harbormaster/controller/HarbormasterUnitMessageViewController.php', 'HarbormasterUnitPropertyView' => 'applications/harbormaster/view/HarbormasterUnitPropertyView.php', 'HarbormasterUnitStatus' => 'applications/harbormaster/constants/HarbormasterUnitStatus.php', + 'HarbormasterUnitSummaryView' => 'applications/harbormaster/view/HarbormasterUnitSummaryView.php', 'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php', 'HarbormasterWaitForPreviousBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php', 'HarbormasterWorker' => 'applications/harbormaster/worker/HarbormasterWorker.php', @@ -4624,7 +4625,7 @@ phutil_register_library_map(array( 'DifferentialTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'DifferentialTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'DifferentialTransactionView' => 'PhabricatorApplicationTransactionView', - 'DifferentialUnitField' => 'DifferentialHarbormasterField', + 'DifferentialUnitField' => 'DifferentialCustomField', 'DifferentialUnitStatus' => 'Phobject', 'DifferentialUnitTestResult' => 'Phobject', 'DifferentialUpdateRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod', @@ -5318,6 +5319,7 @@ phutil_register_library_map(array( 'HarbormasterUnitMessageViewController' => 'HarbormasterController', 'HarbormasterUnitPropertyView' => 'AphrontView', 'HarbormasterUnitStatus' => 'Phobject', + 'HarbormasterUnitSummaryView' => 'AphrontView', 'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterWaitForPreviousBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterWorker' => 'PhabricatorWorker', diff --git a/src/applications/differential/controller/DifferentialController.php b/src/applications/differential/controller/DifferentialController.php index 4f38015ece..46ac290c09 100644 --- a/src/applications/differential/controller/DifferentialController.php +++ b/src/applications/differential/controller/DifferentialController.php @@ -92,4 +92,130 @@ abstract class DifferentialController extends PhabricatorController { return $toc_view; } + protected function loadDiffProperties(array $diffs) { + $diffs = mpull($diffs, null, 'getID'); + + $properties = id(new DifferentialDiffProperty())->loadAllWhere( + 'diffID IN (%Ld)', + array_keys($diffs)); + $properties = mgroup($properties, 'getDiffID'); + + foreach ($diffs as $id => $diff) { + $values = idx($properties, $id, array()); + $values = mpull($values, 'getData', 'getName'); + $diff->attachDiffProperties($values); + } + } + + + protected function loadHarbormasterData(array $diffs) { + $viewer = $this->getViewer(); + + $diffs = mpull($diffs, null, 'getPHID'); + + $buildables = id(new HarbormasterBuildableQuery()) + ->setViewer($viewer) + ->withBuildablePHIDs(array_keys($diffs)) + ->withManualBuildables(false) + ->needBuilds(true) + ->needTargets(true) + ->execute(); + + $buildables = mpull($buildables, null, 'getBuildablePHID'); + foreach ($diffs as $phid => $diff) { + $diff->attachBuildable(idx($buildables, $phid)); + } + + $target_map = array(); + foreach ($diffs as $phid => $diff) { + $target_map[$phid] = $diff->getBuildTargetPHIDs(); + } + $all_target_phids = array_mergev($target_map); + + if ($all_target_phids) { + $unit_messages = id(new HarbormasterBuildUnitMessage())->loadAllWhere( + 'buildTargetPHID IN (%Ls)', + $all_target_phids); + $unit_messages = mgroup($unit_messages, 'getBuildTargetPHID'); + } else { + $unit_messages = array(); + } + + foreach ($diffs as $phid => $diff) { + $target_phids = idx($target_map, $phid, array()); + $messages = array_select_keys($unit_messages, $target_phids); + $messages = array_mergev($messages); + $diff->attachUnitMessages($messages); + } + + // For diffs with no messages, look for legacy unit messages stored on the + // diff itself. + foreach ($diffs as $phid => $diff) { + if ($diff->getUnitMessages()) { + continue; + } + + if (!$diff->hasDiffProperty('arc:unit')) { + continue; + } + + $legacy_messages = $diff->getProperty('arc:unit'); + if (!$legacy_messages) { + continue; + } + + // Show the top 100 legacy lint messages. Previously, we showed some + // by default and let the user toggle the rest. With modern messages, + // we can send the user to the Harbormaster detail page. Just show + // "a lot" of messages in legacy cases to try to strike a balance + // between implementation simplicitly and compatibility. + $legacy_messages = array_slice($legacy_messages, 0, 100); + + $messages = array(); + foreach ($legacy_messages as $message) { + $messages[] = HarbormasterBuildUnitMessage::newFromDictionary( + new HarbormasterBuildTarget(), + $this->getModernUnitMessageDictionary($message)); + } + + $diff->attachUnitMessages($messages); + } + } + + private function getModernUnitMessageDictionary(array $map) { + // Strip out `null` values to satisfy stricter typechecks. + foreach ($map as $key => $value) { + if ($value === null) { + unset($map[$key]); + } + } + + return $map; + } + + protected function getDiffTabLabels(array $diffs) { + // Make sure we're only going to render unique diffs. + $diffs = mpull($diffs, null, 'getID'); + $labels = array(pht('Left'), pht('Right')); + + $results = array(); + + foreach ($diffs as $diff) { + if (count($diffs) == 2) { + $label = array_shift($labels); + $label = pht('%s (Diff %d)', $label, $diff->getID()); + } else { + $label = pht('Diff %d', $diff->getID()); + } + + $results[] = array( + $label, + $diff, + ); + } + + return $results; + } + + } diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 71f4932fed..ccd24220b5 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -102,10 +102,8 @@ final class DifferentialRevisionViewController extends DifferentialController { } } - $props = id(new DifferentialDiffProperty())->loadAllWhere( - 'diffID = %d', - $target_manual->getID()); - $props = mpull($props, 'getData', 'getName'); + $this->loadDiffProperties($diffs); + $props = $target_manual->getDiffProperties(); $object_phids = array_merge( $revision->getReviewers(), @@ -256,23 +254,17 @@ final class DifferentialRevisionViewController extends DifferentialController { array($diff_vs, $target->getID())); $detail_diffs = mpull($detail_diffs, null, 'getPHID'); - $buildables = id(new HarbormasterBuildableQuery()) - ->setViewer($viewer) - ->withBuildablePHIDs(array_keys($detail_diffs)) - ->withManualBuildables(false) - ->needBuilds(true) - ->needTargets(true) - ->execute(); - $buildables = mpull($buildables, null, 'getBuildablePHID'); - foreach ($detail_diffs as $diff_phid => $detail_diff) { - $detail_diff->attachBuildable(idx($buildables, $diff_phid)); - } + $this->loadHarbormasterData($detail_diffs); $diff_detail_box = $this->buildDiffDetailView( $detail_diffs, $revision, $field_list); + $unit_box = $this->buildUnitMessagesView( + $target, + $revision); + $comment_view = $this->buildTransactions( $revision, $diff_vs ? $diffs[$diff_vs] : $target, @@ -469,6 +461,7 @@ final class DifferentialRevisionViewController extends DifferentialController { $operations_box, $revision_detail_box, $diff_detail_box, + $unit_box, $page_pane, ); @@ -972,18 +965,9 @@ final class DifferentialRevisionViewController extends DifferentialController { return null; } - // Make sure we're only going to render unique diffs. - $diffs = mpull($diffs, null, 'getID'); - $labels = array(pht('Left'), pht('Right')); - $property_lists = array(); - foreach ($diffs as $diff) { - if (count($diffs) == 2) { - $label = array_shift($labels); - $label = pht('%s (Diff %d)', $label, $diff->getID()); - } else { - $label = pht('Diff %d', $diff->getID()); - } + foreach ($this->getDiffTabLabels($diffs) as $tab) { + list($label, $diff) = $tab; $property_lists[] = array( $label, @@ -1083,4 +1067,44 @@ final class DifferentialRevisionViewController extends DifferentialController { ->setOperation($operation); } + private function buildUnitMessagesView( + $diff, + DifferentialRevision $revision) { + $viewer = $this->getViewer(); + + if (!$diff->getUnitMessages()) { + return null; + } + + $interesting_messages = array(); + foreach ($diff->getUnitMessages() as $message) { + switch ($message->getResult()) { + case ArcanistUnitTestResult::RESULT_PASS: + case ArcanistUnitTestResult::RESULT_SKIP: + break; + default: + $interesting_messages[] = $message; + break; + } + } + + if (!$interesting_messages) { + return null; + } + + $excuse = null; + if ($diff->hasDiffProperty('arc:unit-excuse')) { + $excuse = $diff->getProperty('arc:unit-excuse'); + } + + return id(new HarbormasterUnitSummaryView()) + ->setUser($viewer) + ->setExcuse($excuse) + ->setBuildable($diff->getBuildable()) + ->setUnitMessages($diff->getUnitMessages()) + ->setLimit(5) + ->setShowViewAll(true); + } + + } diff --git a/src/applications/differential/customfield/DifferentialUnitField.php b/src/applications/differential/customfield/DifferentialUnitField.php index 4b14b3d364..fa2016b106 100644 --- a/src/applications/differential/customfield/DifferentialUnitField.php +++ b/src/applications/differential/customfield/DifferentialUnitField.php @@ -1,7 +1,7 @@ getFieldName(); } - protected function getLegacyProperty() { - return 'arc:unit'; - } - - protected function getDiffPropertyKeys() { - return array( - 'arc:unit', - 'arc:unit-excuse', - ); - } - - protected function loadHarbormasterTargetMessages(array $target_phids) { - return id(new HarbormasterBuildUnitMessage())->loadAllWhere( - 'buildTargetPHID IN (%Ls)', - $target_phids); - } - - protected function newModernMessage(array $message) { - return HarbormasterBuildUnitMessage::newFromDictionary( - new HarbormasterBuildTarget(), - $this->getModernUnitMessageDictionary($message)); - } - - protected function newHarbormasterMessageView(array $all_messages) { - $messages = $all_messages; - - foreach ($messages as $key => $message) { - switch ($message->getResult()) { - case ArcanistUnitTestResult::RESULT_PASS: - case ArcanistUnitTestResult::RESULT_SKIP: - // Don't show "Pass" or "Skip" in the UI since they aren't very - // interesting. The user can click through to the full results if - // they want details. - unset($messages[$key]); - break; - } - } - - if (!$messages) { - return null; - } - - $table = id(new HarbormasterUnitPropertyView()) - ->setLimit(5) - ->setUnitMessages($all_messages); - - $diff = $this->getObject()->getActiveDiff(); - $buildable = $diff->getBuildable(); - if ($buildable) { - $full_results = '/harbormaster/unit/'.$buildable->getID().'/'; - $table->setFullResultsURI($full_results); - } - - return $table; - } - public function getWarningsForDetailView() { $status = $this->getObject()->getActiveDiff()->getUnitStatus(); @@ -105,9 +49,7 @@ final class DifferentialUnitField return $warnings; } - protected function renderHarbormasterStatus( - DifferentialDiff $diff, - array $messages) { + public function renderDiffPropertyViewValue(DifferentialDiff $diff) { $colors = array( DifferentialUnitStatus::UNIT_NONE => 'grey', @@ -121,42 +63,15 @@ final class DifferentialUnitField $message = DifferentialRevisionUpdateHistoryView::getDiffUnitMessage($diff); - $note = array(); - - $excuse = $diff->getProperty('arc:unit-excuse'); - if (strlen($excuse)) { - $excuse = array( - phutil_tag('strong', array(), pht('Excuse:')), - ' ', - phutil_escape_html_newlines($excuse), - ); - $note[] = $excuse; - } - - $note = phutil_implode_html(" \xC2\xB7 ", $note); - $status = id(new PHUIStatusListView()) ->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_STAR, $icon_color) - ->setTarget($message) - ->setNote($note)); + ->setTarget($message)); return $status; } - private function getModernUnitMessageDictionary(array $map) { - // Strip out `null` values to satisfy stricter typechecks. - foreach ($map as $key => $value) { - if ($value === null) { - unset($map[$key]); - } - } - - // TODO: Remap more stuff here? - - return $map; - } } diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php index 889b46d470..913abad3ab 100644 --- a/src/applications/differential/storage/DifferentialDiff.php +++ b/src/applications/differential/storage/DifferentialDiff.php @@ -40,6 +40,8 @@ final class DifferentialDiff private $properties = array(); private $buildable = self::ATTACHABLE; + private $unitMessages = self::ATTACHABLE; + protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, @@ -334,6 +336,20 @@ final class DifferentialDiff return $this->assertAttachedKey($this->properties, $key); } + public function hasDiffProperty($key) { + $properties = $this->getDiffProperties(); + return array_key_exists($key, $properties); + } + + public function attachDiffProperties(array $properties) { + $this->properties = $properties; + return $this; + } + + public function getDiffProperties() { + return $this->assertAttached($this->properties); + } + public function attachBuildable(HarbormasterBuildable $buildable = null) { $this->buildable = $buildable; return $this; @@ -391,6 +407,17 @@ final class DifferentialDiff } + public function attachUnitMessages(array $unit_messages) { + $this->unitMessages = $unit_messages; + return $this; + } + + + public function getUnitMessages() { + return $this->assertAttached($this->unitMessages); + } + + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php index f6487ff661..22d598431f 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php @@ -336,42 +336,11 @@ final class HarbormasterBuildableViewController } if ($unit_data) { - $unit_href = $this->getApplicationURI('unit/'.$buildable->getID().'/'); - - $unit_table = id(new HarbormasterUnitPropertyView()) - ->setUser($viewer) - ->setLimit(5) + $unit = id(new HarbormasterUnitSummaryView()) + ->setBuildable($buildable) ->setUnitMessages($unit_data) - ->setFullResultsURI($unit_href); - - $unit_data = msort($unit_data, 'getSortKey'); - $head_unit = head($unit_data); - if ($head_unit) { - $status = $head_unit->getResult(); - - $tag_text = HarbormasterUnitStatus::getUnitStatusLabel($status); - $tag_color = HarbormasterUnitStatus::getUnitStatusColor($status); - $tag_icon = HarbormasterUnitStatus::getUnitStatusIcon($status); - - } else { - $tag_text = pht('No Unit Tests'); - $tag_color = 'grey'; - $tag_icon = 'fa-ban'; - } - - $unit_header = id(new PHUIHeaderView()) - ->setHeader(pht('Unit Tests')) - ->setStatus($tag_icon, $tag_color, $tag_text) - ->addActionLink( - id(new PHUIButtonView()) - ->setTag('a') - ->setHref($unit_href) - ->setIcon('fa-list-ul') - ->setText('View All')); - - $unit = id(new PHUIObjectBoxView()) - ->setHeader($unit_header) - ->setTable($unit_table); + ->setShowViewAll(true) + ->setLimit(5); } else { $unit = null; } diff --git a/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php b/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php index 032777674f..2e7f20a71e 100644 --- a/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php +++ b/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php @@ -34,14 +34,10 @@ final class HarbormasterUnitMessageListController $unit_data = array(); } - $unit_table = id(new HarbormasterUnitPropertyView()) - ->setUser($viewer) + $unit = id(new HarbormasterUnitSummaryView()) + ->setBuildable($buildable) ->setUnitMessages($unit_data); - $unit = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Unit Tests')) - ->setTable($unit_table); - $crumbs = $this->buildApplicationCrumbs(); $this->addBuildableCrumb($crumbs, $buildable); $crumbs->addTextCrumb(pht('Unit Tests')); diff --git a/src/applications/harbormaster/view/HarbormasterUnitPropertyView.php b/src/applications/harbormaster/view/HarbormasterUnitPropertyView.php index 0d58140a35..0ce1688027 100644 --- a/src/applications/harbormaster/view/HarbormasterUnitPropertyView.php +++ b/src/applications/harbormaster/view/HarbormasterUnitPropertyView.php @@ -6,6 +6,7 @@ final class HarbormasterUnitPropertyView extends AphrontView { private $unitMessages = array(); private $limit; private $fullResultsURI; + private $notice; public function setPathURIMap(array $map) { $this->pathURIMap = $map; @@ -28,6 +29,12 @@ final class HarbormasterUnitPropertyView extends AphrontView { return $this; } + public function setNotice($notice) { + $this->notice = $notice; + return $this; + } + + public function render() { require_celerity_resource('harbormaster-css'); @@ -68,12 +75,14 @@ final class HarbormasterUnitPropertyView extends AphrontView { $name = $message->getUnitMessageDisplayName(); $id = $message->getID(); - $name = phutil_tag( - 'a', - array( - 'href' => "/harbormaster/unit/view/{$id}/", - ), - $name); + if ($id) { + $name = phutil_tag( + 'a', + array( + 'href' => "/harbormaster/unit/view/{$id}/", + ), + $name); + } $details = $message->getUnitMessageDetails(); if (strlen($details)) { @@ -127,8 +136,8 @@ final class HarbormasterUnitPropertyView extends AphrontView { )) ->setColumnClasses( array( - 'top', - 'top', + 'top center', + 'top right', 'top wide', )) ->setColumnWidths( @@ -142,6 +151,10 @@ final class HarbormasterUnitPropertyView extends AphrontView { $any_duration, )); + if ($this->notice) { + $table->setNotice($this->notice); + } + return $table; } diff --git a/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php new file mode 100644 index 0000000000..75de4b20df --- /dev/null +++ b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php @@ -0,0 +1,104 @@ +buildable = $buildable; + return $this; + } + + public function setUnitMessages(array $messages) { + $this->messages = $messages; + return $this; + } + + public function setLimit($limit) { + $this->limit = $limit; + return $this; + } + + public function setExcuse($excuse) { + $this->excuse = $excuse; + return $this; + } + + public function setShowViewAll($show_view_all) { + $this->showViewAll = $show_view_all; + return $this; + } + + public function render() { + $messages = $this->messages; + $buildable = $this->buildable; + + $id = $buildable->getID(); + $full_uri = "/harbormaster/unit/{$id}/"; + + $messages = msort($messages, 'getSortKey'); + $head_unit = head($messages); + if ($head_unit) { + $status = $head_unit->getResult(); + + $tag_text = HarbormasterUnitStatus::getUnitStatusLabel($status); + $tag_color = HarbormasterUnitStatus::getUnitStatusColor($status); + $tag_icon = HarbormasterUnitStatus::getUnitStatusIcon($status); + } else { + $tag_text = pht('No Unit Tests'); + $tag_color = 'grey'; + $tag_icon = 'fa-ban'; + } + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Unit Tests')) + ->setStatus($tag_icon, $tag_color, $tag_text); + + if ($this->showViewAll) { + $view_all = id(new PHUIButtonView()) + ->setTag('a') + ->setHref($full_uri) + ->setIcon('fa-list-ul') + ->setText('View All'); + $header->addActionLink($view_all); + } + + $box = id(new PHUIObjectBoxView()) + ->setHeader($header); + + $table = id(new HarbormasterUnitPropertyView()) + ->setUnitMessages($messages); + + if ($this->showViewAll) { + $table->setFullResultsURI($full_uri); + } + + if ($this->limit) { + $table->setLimit($this->limit); + } + + $excuse = $this->excuse; + if (strlen($excuse)) { + $excuse_icon = id(new PHUIIconView()) + ->setIcon('fa-commenting-o red'); + + $table->setNotice( + array( + $excuse_icon, + ' ', + phutil_tag('strong', array(), pht('Excuse:')), + ' ', + $excuse, + )); + } + + $box->setTable($table); + + return $box; + } + +} diff --git a/src/view/control/AphrontTableView.php b/src/view/control/AphrontTableView.php index 97e88f4f33..176d246718 100644 --- a/src/view/control/AphrontTableView.php +++ b/src/view/control/AphrontTableView.php @@ -157,21 +157,6 @@ final class AphrontTableView extends AphrontView { $sort_values[] = null; } - if ($this->notice) { - $colspan = max(count(array_filter($visibility)), 1); - $table[] = phutil_tag( - 'tr', - array(), - phutil_tag( - 'td', - array( - 'colspan' => $colspan, - 'class' => 'aphront-table-notice', - ), - $this->notice)); - - } - $tr = array(); foreach ($headers as $col_num => $header) { if (!$visibility[$col_num]) { @@ -350,13 +335,29 @@ final class AphrontTableView extends AphrontView { $classes[] = 'aphront-table-view-fixed'; } + $notice = null; + if ($this->notice) { + $notice = phutil_tag( + 'div', + array( + 'class' => 'aphront-table-notice', + ), + $this->notice); + } + $html = phutil_tag( 'table', array( 'class' => implode(' ', $classes), ), $table); - return phutil_tag_div('aphront-table-wrap', $html); + + return phutil_tag_div( + 'aphront-table-wrap', + array( + $notice, + $html, + )); } public static function renderSingleDisplayLine($line) { diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css index 8a842e9538..40dc1be987 100644 --- a/webroot/rsrc/css/aphront/table-view.css +++ b/webroot/rsrc/css/aphront/table-view.css @@ -19,7 +19,11 @@ table-layout: fixed; } -.aphront-table-view td.aphront-table-notice { +.aphront-table-view-fixed th { + box-sizing: border-box; +} + +.aphront-table-notice { padding: 12px 16px; font-size: {$normalfontsize}; color: {$darkbluetext}; From 383b0bdc04d0c170641ca8be206d7991af3986ca Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 29 Feb 2016 14:21:34 -0800 Subject: [PATCH 11/41] Fix missing cursor data for paging Projects by creation date Summary: Fixes T10478. Test Plan: Paged projects by "Created" without errors. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10478 Differential Revision: https://secure.phabricator.com/D15367 --- src/applications/project/query/PhabricatorProjectQuery.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php index 9845628b4b..ab41ecf0ac 100644 --- a/src/applications/project/query/PhabricatorProjectQuery.php +++ b/src/applications/project/query/PhabricatorProjectQuery.php @@ -187,6 +187,7 @@ final class PhabricatorProjectQuery protected function getPagingValueMap($cursor, array $keys) { $project = $this->loadCursorObject($cursor); return array( + 'id' => $project->getID(), 'name' => $project->getName(), ); } From 58cea0714bff163c1c1bae156a9b48de9c36aa78 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 1 Mar 2016 05:46:50 -0800 Subject: [PATCH 12/41] Fix doubling of unbroken build steps in Build Plan view Summary: Fixes T10479. Test Plan: Viewed a valid build plan. Reviewers: hach-que, tycho.tatitscheff, chad Reviewed By: chad Maniphest Tasks: T10479 Differential Revision: https://secure.phabricator.com/D15368 --- .../harbormaster/controller/HarbormasterPlanViewController.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php index f4212c38b3..847eaa6328 100644 --- a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php @@ -187,8 +187,6 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController { if ($is_deadlocking) { $item->setStatusIcon('fa-warning red'); } - - $step_list->addItem($item); } $step_list->setFlush(true); From 8240e0f727069a2d11eb3f4d7913ca71516c5117 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 1 Mar 2016 05:58:28 -0800 Subject: [PATCH 13/41] Treat "skipped" unit tests as less interesting than "passed" Summary: Ref T10457. Skipped tests are almost always well-behaved (e.g., `testWindows()`, but the test is running on Linux) and not interesting, and we do not expect well-written, solid systems to necessarily have 0 skips. Although skips //could// indicate that you have missing dependencies on a build server, and thus be a bit interesting, I think they almost always indicate that a particular test is not expected to run in the current environment. If we wanted to tackle this problem in granular detail, we could eventually add a "Missing" status or similar which would serve as "a skip you //could// reasonably fix in this environment", but I don't think that's too interesting. Test Plan: Here's an example of a build result with skips: B10875 {F1136511} I think this is clearer as "Passed", as this is the expected production state of the build. Locally, looked at some builds. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10457 Differential Revision: https://secure.phabricator.com/D15369 --- .../constants/HarbormasterUnitStatus.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/applications/harbormaster/constants/HarbormasterUnitStatus.php b/src/applications/harbormaster/constants/HarbormasterUnitStatus.php index ea79c7843d..de7c4dd42f 100644 --- a/src/applications/harbormaster/constants/HarbormasterUnitStatus.php +++ b/src/applications/harbormaster/constants/HarbormasterUnitStatus.php @@ -43,10 +43,10 @@ final class HarbormasterUnitStatus return pht('%s Broken Test(s)', $count); case ArcanistUnitTestResult::RESULT_UNSOUND: return pht('%s Unsound Test(s)', $count); - case ArcanistUnitTestResult::RESULT_SKIP: - return pht('%s Skipped Test(s)', $count); case ArcanistUnitTestResult::RESULT_PASS: return pht('%s Passed Test(s)', $count); + case ArcanistUnitTestResult::RESULT_SKIP: + return pht('%s Skipped Test(s)', $count); } return pht('%s Other Test(s)', $count); @@ -72,16 +72,17 @@ final class HarbormasterUnitStatus 'color' => 'yellow', 'sort' => 'C', ), - ArcanistUnitTestResult::RESULT_SKIP => array( - 'label' => pht('Skipped'), - 'icon' => 'fa-fast-forward', - 'color' => 'blue', - ), ArcanistUnitTestResult::RESULT_PASS => array( 'label' => pht('Passed'), 'icon' => 'fa-check', 'color' => 'green', - 'sort' => 'Z', + 'sort' => 'D', + ), + ArcanistUnitTestResult::RESULT_SKIP => array( + 'label' => pht('Skipped'), + 'icon' => 'fa-fast-forward', + 'color' => 'blue', + 'sort' => 'E', ), ); } From fe7e288cf558051c5405679d8569ce26847482ef Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 26 Feb 2016 14:34:51 -0800 Subject: [PATCH 14/41] Solidify PHUITwoColumnView as a page layout Summary: Rolls out a new "Object Page" design with PHUITwoColumnView. This is reasonably polished, but wanted to post it up for you now for feedback before chasing down minor bugs. This implements TwoColumn in the following applications: - Ponder - Paste - Slowvote - Countdown - Projects - Profile - Passphrase This helped track down display issues and inconsistencies and make sure the layout was flexible for different pages. Test Plan: Test each of the applications on mobile, tablet, and desktop breakpoints. {F1135705} {F1135706} {F1135707} {F1135708} {F1135709} {F1135710} {F1135711} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15366 --- resources/celerity/map.php | 48 ++--- src/__phutil_library_map__.php | 4 +- .../CelerityDefaultPostprocessor.php | 5 +- .../PhabricatorCountdownViewController.php | 80 ++++---- .../view/PhabricatorCountdownView.php | 58 +++--- .../PassphraseCredentialViewController.php | 82 ++++++-- .../PhabricatorPasteViewController.php | 77 ++++--- ...PhabricatorPeopleProfileViewController.php | 8 +- .../PonderQuestionViewController.php | 134 +++++-------- .../ponder/view/PonderAddAnswerView.php | 3 +- .../PhabricatorProjectProfileController.php | 9 +- .../PhabricatorSlowvotePollController.php | 65 +++--- .../slowvote/view/SlowvoteEmbedView.php | 46 ++--- src/view/phui/PHUIHeadThingView.php | 65 ++++++ src/view/phui/PHUIObjectBoxView.php | 1 + src/view/phui/PHUITwoColumnView.php | 80 ++++++-- .../rsrc/css/application/countdown/timer.css | 29 +-- webroot/rsrc/css/application/paste/paste.css | 8 - .../css/application/ponder/ponder-view.css | 97 +-------- .../css/application/project/project-view.css | 54 +++-- .../css/application/slowvote/slowvote.css | 20 +- webroot/rsrc/css/phui/phui-box.css | 32 ++- webroot/rsrc/css/phui/phui-button.css | 7 +- webroot/rsrc/css/phui/phui-head-thing.css | 33 +++ webroot/rsrc/css/phui/phui-header-view.css | 2 +- webroot/rsrc/css/phui/phui-object-box.css | 4 + .../rsrc/css/phui/phui-two-column-view.css | 188 +++++++++++++++--- 27 files changed, 741 insertions(+), 498 deletions(-) create mode 100644 src/view/phui/PHUIHeadThingView.php create mode 100644 webroot/rsrc/css/phui/phui-head-thing.css diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 1a4c791ddb..4df1247742 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '76a3afdf', + 'core.pkg.css' => 'e50c063a', 'core.pkg.js' => '7d8faf57', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', @@ -52,7 +52,7 @@ return array( 'rsrc/css/application/conpherence/update.css' => 'faf6be09', 'rsrc/css/application/conpherence/widget-pane.css' => '775eaaba', 'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4', - 'rsrc/css/application/countdown/timer.css' => 'e7544472', + 'rsrc/css/application/countdown/timer.css' => '96696f21', 'rsrc/css/application/daemon/bulk-job.css' => 'df9c1d4a', 'rsrc/css/application/dashboard/dashboard.css' => 'eb458607', 'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a', @@ -79,7 +79,7 @@ return array( 'rsrc/css/application/maniphest/task-summary.css' => '11cc5344', 'rsrc/css/application/objectselector/object-selector.css' => '85ee8ce6', 'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b', - 'rsrc/css/application/paste/paste.css' => 'a5157c48', + 'rsrc/css/application/paste/paste.css' => '1898e534', 'rsrc/css/application/people/people-profile.css' => '2473d929', 'rsrc/css/application/phame/phame.css' => '737792d6', 'rsrc/css/application/pholio/pholio-edit.css' => '3ad9d1ee', @@ -92,18 +92,18 @@ return array( 'rsrc/css/application/policy/policy-edit.css' => '815c66f7', 'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43', 'rsrc/css/application/policy/policy.css' => '957ea14c', - 'rsrc/css/application/ponder/ponder-view.css' => '212495e0', + 'rsrc/css/application/ponder/ponder-view.css' => '4e321d68', 'rsrc/css/application/project/project-card-view.css' => '9418c97d', - 'rsrc/css/application/project/project-view.css' => '298b7c5b', + 'rsrc/css/application/project/project-view.css' => '9ce99f21', 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', 'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae', 'rsrc/css/application/search/search-results.css' => '7dea472c', - 'rsrc/css/application/slowvote/slowvote.css' => 'da0afb1b', + 'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230', 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 'rsrc/css/application/uiexample/example.css' => '528b19de', - 'rsrc/css/core/core.css' => '5b3563c8', + 'rsrc/css/core/core.css' => 'd0801452', 'rsrc/css/core/remarkup.css' => 'fc228f08', 'rsrc/css/core/syntax.css' => '9fd11da8', 'rsrc/css/core/z-index.css' => '5b6fcf3f', @@ -123,8 +123,8 @@ return array( 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', 'rsrc/css/phui/phui-badge.css' => 'f25c3476', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', - 'rsrc/css/phui/phui-box.css' => 'dd1294d3', - 'rsrc/css/phui/phui-button.css' => 'edf464e9', + 'rsrc/css/phui/phui-box.css' => '348bd872', + 'rsrc/css/phui/phui-button.css' => 'c096e4e9', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-crumbs-view.css' => '79d536e5', 'rsrc/css/phui/phui-document-pro.css' => '92d5b648', @@ -134,7 +134,8 @@ return array( 'rsrc/css/phui/phui-fontkit.css' => '9cda225e', 'rsrc/css/phui/phui-form-view.css' => '4a1a0f5e', 'rsrc/css/phui/phui-form.css' => 'aac1d51d', - 'rsrc/css/phui/phui-header-view.css' => 'a6d7b20d', + 'rsrc/css/phui/phui-head-thing.css' => '11731da0', + 'rsrc/css/phui/phui-header-view.css' => 'b541cc78', 'rsrc/css/phui/phui-hovercard.css' => 'de1a2119', 'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad', 'rsrc/css/phui/phui-icon.css' => '3f33ab57', @@ -142,7 +143,7 @@ return array( 'rsrc/css/phui/phui-info-panel.css' => '27ea50a1', 'rsrc/css/phui/phui-info-view.css' => '6d7c3509', 'rsrc/css/phui/phui-list.css' => '9da2aa00', - 'rsrc/css/phui/phui-object-box.css' => '407eaf5a', + 'rsrc/css/phui/phui-object-box.css' => '91628842', 'rsrc/css/phui/phui-object-item-list-view.css' => '18b2ce8e', 'rsrc/css/phui/phui-pager.css' => 'bea33d23', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', @@ -154,7 +155,7 @@ return array( 'rsrc/css/phui/phui-status.css' => '888cedb8', 'rsrc/css/phui/phui-tag-view.css' => '9d5d4400', 'rsrc/css/phui/phui-timeline-view.css' => '2efceff8', - 'rsrc/css/phui/phui-two-column-view.css' => 'a317616a', + 'rsrc/css/phui/phui-two-column-view.css' => '7c5d0741', 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7', 'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647', 'rsrc/css/phui/workboards/phui-workcard.css' => '3646fb96', @@ -740,7 +741,7 @@ return array( 'multirow-row-manager' => 'b5d57730', 'owners-path-editor' => 'aa1733d0', 'owners-path-editor-css' => '2f00933b', - 'paste-css' => 'a5157c48', + 'paste-css' => '1898e534', 'path-typeahead' => 'f7fc67ec', 'people-profile-css' => '2473d929', 'phabricator-action-list-view-css' => 'c5eba19d', @@ -748,8 +749,8 @@ return array( 'phabricator-busy' => '59a7976a', 'phabricator-chatlog-css' => 'd295b020', 'phabricator-content-source-view-css' => '4b8b05d4', - 'phabricator-core-css' => '5b3563c8', - 'phabricator-countdown-css' => 'e7544472', + 'phabricator-core-css' => 'd0801452', + 'phabricator-countdown-css' => '96696f21', 'phabricator-dashboard-css' => 'eb458607', 'phabricator-drag-and-drop-file-upload' => '81f182b5', 'phabricator-draggable-list' => '5a13c79f', @@ -772,7 +773,7 @@ return array( 'phabricator-search-results-css' => '7dea472c', 'phabricator-shaped-request' => '7cbe244b', 'phabricator-side-menu-view-css' => '3a3d9f41', - 'phabricator-slowvote-css' => 'da0afb1b', + 'phabricator-slowvote-css' => 'a94b7230', 'phabricator-source-code-view-css' => 'cbeef983', 'phabricator-standard-page-view' => 'e709f6d0', 'phabricator-textareautils' => '5813016a', @@ -802,8 +803,8 @@ return array( 'phui-action-panel-css' => '91c7b835', 'phui-badge-view-css' => 'f25c3476', 'phui-big-info-view-css' => 'bd903741', - 'phui-box-css' => 'dd1294d3', - 'phui-button-css' => 'edf464e9', + 'phui-box-css' => '348bd872', + 'phui-button-css' => 'c096e4e9', 'phui-calendar-css' => 'ccabe893', 'phui-calendar-day-css' => 'd1cf6f93', 'phui-calendar-list-css' => 'c1c7f338', @@ -818,7 +819,8 @@ return array( 'phui-fontkit-css' => '9cda225e', 'phui-form-css' => 'aac1d51d', 'phui-form-view-css' => '4a1a0f5e', - 'phui-header-view-css' => 'a6d7b20d', + 'phui-head-thing-view-css' => '11731da0', + 'phui-header-view-css' => 'b541cc78', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'de1a2119', 'phui-icon-set-selector-css' => '1ab67aad', @@ -828,7 +830,7 @@ return array( 'phui-info-view-css' => '6d7c3509', 'phui-inline-comment-view-css' => '0fdb3667', 'phui-list-view-css' => '9da2aa00', - 'phui-object-box-css' => '407eaf5a', + 'phui-object-box-css' => '91628842', 'phui-object-item-list-view-css' => '18b2ce8e', 'phui-pager-css' => 'bea33d23', 'phui-pinboard-view-css' => '2495140e', @@ -841,7 +843,7 @@ return array( 'phui-tag-view-css' => '9d5d4400', 'phui-theme-css' => '027ba77e', 'phui-timeline-view-css' => '2efceff8', - 'phui-two-column-view-css' => 'a317616a', + 'phui-two-column-view-css' => '7c5d0741', 'phui-workboard-color-css' => 'ac6fe6a7', 'phui-workboard-view-css' => 'e6d89647', 'phui-workcard-view-css' => '3646fb96', @@ -855,9 +857,9 @@ return array( 'policy-css' => '957ea14c', 'policy-edit-css' => '815c66f7', 'policy-transaction-detail-css' => '82100a43', - 'ponder-view-css' => '212495e0', + 'ponder-view-css' => '4e321d68', 'project-card-view-css' => '9418c97d', - 'project-view-css' => '298b7c5b', + 'project-view-css' => '9ce99f21', 'releeph-core' => '9b3c5733', 'releeph-preview-branch' => 'b7a6f4a5', 'releeph-request-differential-create-dialog' => '8d8b92cd', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a3f54fbf3e..740021f3c5 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1524,6 +1524,7 @@ phutil_register_library_map(array( 'PHUIHandleListView' => 'applications/phid/view/PHUIHandleListView.php', 'PHUIHandleTagListView' => 'applications/phid/view/PHUIHandleTagListView.php', 'PHUIHandleView' => 'applications/phid/view/PHUIHandleView.php', + 'PHUIHeadThingView' => 'view/phui/PHUIHeadThingView.php', 'PHUIHeaderView' => 'view/phui/PHUIHeaderView.php', 'PHUIHovercardUIExample' => 'applications/uiexample/examples/PHUIHovercardUIExample.php', 'PHUIHovercardView' => 'view/phui/PHUIHovercardView.php', @@ -5758,6 +5759,7 @@ phutil_register_library_map(array( 'PHUIHandleListView' => 'AphrontTagView', 'PHUIHandleTagListView' => 'AphrontTagView', 'PHUIHandleView' => 'AphrontView', + 'PHUIHeadThingView' => 'AphrontTagView', 'PHUIHeaderView' => 'AphrontTagView', 'PHUIHovercardUIExample' => 'PhabricatorUIExample', 'PHUIHovercardView' => 'AphrontTagView', @@ -6380,7 +6382,7 @@ phutil_register_library_map(array( 'PhabricatorCountdownTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorCountdownTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorCountdownTransactionQuery' => 'PhabricatorApplicationTransactionQuery', - 'PhabricatorCountdownView' => 'AphrontTagView', + 'PhabricatorCountdownView' => 'AphrontView', 'PhabricatorCountdownViewController' => 'PhabricatorCountdownController', 'PhabricatorCredentialsUsedByObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorCursorPagedPolicyAwareQuery' => 'PhabricatorPolicyAwareQuery', diff --git a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php index 4fdeaeccb1..13cef002cf 100644 --- a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php +++ b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php @@ -192,10 +192,7 @@ final class CelerityDefaultPostprocessor 'sh-disabledbackground' => '#f3f3f3', // Background color for "most" themes. - 'page.background' => '#f1f1f4', - - // Background color for "dark" themes. - 'page.background.dark' => '#ebecee', + 'page.background' => '#f8f8fb', 'menu.profile.text' => 'rgba(255,255,255,.8)', 'menu.profile.text.selected' => 'rgba(255,255,255,1)', diff --git a/src/applications/countdown/controller/PhabricatorCountdownViewController.php b/src/applications/countdown/controller/PhabricatorCountdownViewController.php index 401e159c5f..423bfe5207 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownViewController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownViewController.php @@ -21,15 +21,15 @@ final class PhabricatorCountdownViewController $countdown_view = id(new PhabricatorCountdownView()) ->setUser($viewer) - ->setCountdown($countdown) - ->setHeadless(true); + ->setCountdown($countdown); $id = $countdown->getID(); $title = $countdown->getTitle(); $crumbs = $this ->buildApplicationCrumbs() - ->addTextCrumb("C{$id}"); + ->addTextCrumb("C{$id}") + ->setBorder(true); $epoch = $countdown->getEpoch(); if ($epoch >= PhabricatorTime::getNow()) { @@ -49,19 +49,26 @@ final class PhabricatorCountdownViewController ->setStatus($icon, $color, $status); $actions = $this->buildActionListView($countdown); - $properties = $this->buildPropertyListView($countdown, $actions); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $properties = $this->buildPropertyListView($countdown); + $subheader = $this->buildSubheaderView($countdown); $timeline = $this->buildTransactionTimeline( $countdown, new PhabricatorCountdownTransactionQuery()); - $add_comment = $this->buildCommentForm($countdown); + $content = array( + $countdown_view, + $timeline, + $add_comment, + ); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setSubheader($subheader) + ->setMainColumn($content) + ->setPropertyList($properties) + ->setActionList($actions); return $this->newPage() ->setTitle($title) @@ -72,10 +79,7 @@ final class PhabricatorCountdownViewController )) ->appendChild( array( - $object_box, - $countdown_view, - $timeline, - $add_comment, + $view, )); } @@ -114,34 +118,40 @@ final class PhabricatorCountdownViewController } private function buildPropertyListView( - PhabricatorCountdown $countdown, - PhabricatorActionListView $actions) { - + PhabricatorCountdown $countdown) { $viewer = $this->getViewer(); - $view = id(new PHUIPropertyListView()) ->setUser($viewer) - ->setObject($countdown) - ->setActionList($actions); - - $view->addProperty( - pht('Author'), - $viewer->renderHandle($countdown->getAuthorPHID())); - - $view->invokeWillRenderEvent(); - - $description = $countdown->getDescription(); - if (strlen($description)) { - $description = new PHUIRemarkupView($viewer, $description); - $view->addSectionHeader( - pht('Description'), - PHUIPropertyListView::ICON_SUMMARY); - $view->addTextContent($description); - } - + ->setObject($countdown); + $view->invokeWillRenderEvent(); return $view; } + private function buildSubheaderView( + PhabricatorCountdown $countdown) { + $viewer = $this->getViewer(); + + $author = $viewer->renderHandle($countdown->getAuthorPHID())->render(); + $date = phabricator_datetime($countdown->getDateCreated(), $viewer); + $author = phutil_tag('strong', array(), $author); + + $person = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($countdown->getAuthorPHID())) + ->needProfileImage(true) + ->executeOne(); + + $image_uri = $person->getProfileImageURI(); + $image_href = '/p/'.$person->getUsername(); + + $content = pht('Authored by %s on %s.', $author, $date); + + return id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); + } + private function buildCommentForm(PhabricatorCountdown $countdown) { $viewer = $this->getViewer(); diff --git a/src/applications/countdown/view/PhabricatorCountdownView.php b/src/applications/countdown/view/PhabricatorCountdownView.php index 2e8f1dedb3..4e975c3626 100644 --- a/src/applications/countdown/view/PhabricatorCountdownView.php +++ b/src/applications/countdown/view/PhabricatorCountdownView.php @@ -1,46 +1,31 @@ headless = $headless; - return $this; - } public function setCountdown(PhabricatorCountdown $countdown) { $this->countdown = $countdown; return $this; } - - protected function getTagContent() { + public function render() { $countdown = $this->countdown; - require_celerity_resource('phabricator-countdown-css'); - $header = null; - if (!$this->headless) { - $header = phutil_tag( - 'div', + $header_text = array( + 'C'.$countdown->getID(), + ' ', + phutil_tag( + 'a', array( - 'class' => 'phabricator-timer-header', + 'href' => '/countdown/'.$countdown->getID(), ), - array( - 'C'.$countdown->getID(), - ' ', - phutil_tag( - 'a', - array( - 'href' => '/countdown/'.$countdown->getID(), - ), - $countdown->getTitle()), - )); - } + $countdown->getTitle()), + ); + $header = id(new PHUIHeaderView()) + ->setHeader($header_text); $ths = array( phutil_tag('th', array(), pht('Days')), @@ -66,12 +51,23 @@ final class PhabricatorCountdownView extends AphrontTagView { ), $launch_date); + $description = $countdown->getDescription(); + if (strlen($description)) { + $description = new PHUIRemarkupView($this->getUser(), $description); + $description = phutil_tag( + 'div', + array( + 'class' => 'countdown-description phabricator-remarkup', + ), + $description); + } + $container = celerity_generate_unique_node_id(); $content = phutil_tag( 'div', array('class' => 'phabricator-timer', 'id' => $container), array( - $header, + $description, phutil_tag('table', array('class' => 'phabricator-timer-table'), array( phutil_tag('tr', array(), $ths), phutil_tag('tr', array(), $dashes), @@ -84,7 +80,11 @@ final class PhabricatorCountdownView extends AphrontTagView { 'container' => $container, )); - return $content; + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->addClass('phabricator-timer-view') + ->appendChild($content); } } diff --git a/src/applications/passphrase/controller/PassphraseCredentialViewController.php b/src/applications/passphrase/controller/PassphraseCredentialViewController.php index c156be81ce..b4ccf0ad9e 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialViewController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialViewController.php @@ -28,23 +28,27 @@ final class PassphraseCredentialViewController extends PassphraseController { $title = pht('%s %s', 'K'.$credential->getID(), $credential->getName()); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb('K'.$credential->getID()); + $crumbs->setBorder(true); $header = $this->buildHeaderView($credential); $actions = $this->buildActionView($credential, $type); - $properties = $this->buildPropertyView($credential, $type, $actions); + $properties = $this->buildPropertyView($credential, $type); + $subheader = $this->buildSubheaderView($credential); + $content = $this->buildDetailsView($credential, $type); - $box = id(new PHUIObjectBoxView()) + $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->addPropertyList($properties); + ->setSubheader($subheader) + ->setMainColumn(array($content, $timeline)) + ->setPropertyList($properties) + ->setActionList($actions); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - $timeline, - ), - array( - 'title' => $title, + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, )); } @@ -63,6 +67,35 @@ final class PassphraseCredentialViewController extends PassphraseController { return $header; } + private function buildSubheaderView( + PassphraseCredential $credential) { + $viewer = $this->getViewer(); + + $author = $viewer->renderHandle($credential->getAuthorPHID())->render(); + $date = phabricator_datetime($credential->getDateCreated(), $viewer); + $author = phutil_tag('strong', array(), $author); + + $person = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($credential->getAuthorPHID())) + ->needProfileImage(true) + ->executeOne(); + + if (!$person) { + return null; + } + + $image_uri = $person->getProfileImageURI(); + $image_href = '/p/'.$credential->getUsername(); + + $content = pht('Created by %s on %s.', $author, $date); + + return id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); + } + private function buildActionView( PassphraseCredential $credential, PassphraseCredentialType $type) { @@ -153,16 +186,13 @@ final class PassphraseCredentialViewController extends PassphraseController { return $actions; } - private function buildPropertyView( + private function buildDetailsView( PassphraseCredential $credential, - PassphraseCredentialType $type, - PhabricatorActionListView $actions) { + PassphraseCredentialType $type) { $viewer = $this->getRequest()->getUser(); $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($credential) - ->setActionList($actions); + ->setUser($viewer); $properties->addProperty( pht('Credential Type'), @@ -192,8 +222,6 @@ final class PassphraseCredentialViewController extends PassphraseController { $viewer->renderHandleList($used_by_phids)); } - $properties->invokeWillRenderEvent(); - $description = $credential->getDescription(); if (strlen($description)) { $properties->addSectionHeader( @@ -203,6 +231,22 @@ final class PassphraseCredentialViewController extends PassphraseController { new PHUIRemarkupView($viewer, $description)); } + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('PROPERTIES')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($properties); + } + + private function buildPropertyView( + PassphraseCredential $credential, + PassphraseCredentialType $type) { + $viewer = $this->getRequest()->getUser(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setObject($credential); + + $properties->invokeWillRenderEvent(); return $properties; } diff --git a/src/applications/paste/controller/PhabricatorPasteViewController.php b/src/applications/paste/controller/PhabricatorPasteViewController.php index 530b2245c5..f259cdc6af 100644 --- a/src/applications/paste/controller/PhabricatorPasteViewController.php +++ b/src/applications/paste/controller/PhabricatorPasteViewController.php @@ -48,25 +48,16 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { $header = $this->buildHeaderView($paste); $actions = $this->buildActionView($viewer, $paste); - $properties = $this->buildPropertyView($paste, $fork_phids, $actions); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); - + $properties = $this->buildPropertyView($paste, $fork_phids); + $subheader = $this->buildSubheaderView($paste); $source_code = $this->buildSourceCodeView($paste, $this->highlightMap); require_celerity_resource('paste-css'); - $source_code = phutil_tag( - 'div', - array( - 'class' => 'container-of-paste', - ), - $source_code); $monogram = $paste->getMonogram(); $crumbs = $this->buildApplicationCrumbs() - ->addTextCrumb($monogram, '/'.$monogram); + ->addTextCrumb($monogram) + ->setBorder(true); $timeline = $this->buildTransactionTimeline( $paste, @@ -79,6 +70,18 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { $timeline->setQuoteRef($monogram); $comment_view->setTransactionTimeline($timeline); + $paste_view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setSubheader($subheader) + ->setMainColumn(array( + $source_code, + $timeline, + $comment_view, + )) + ->setPropertyList($properties) + ->setActionList($actions) + ->addClass('ponder-question-view'); + return $this->newPage() ->setTitle($paste->getFullName()) ->setCrumbs($crumbs) @@ -86,13 +89,7 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { array( $paste->getPHID(), )) - ->appendChild( - array( - $object_box, - $source_code, - $timeline, - $comment_view, - )); + ->appendChild($paste_view); } private function buildHeaderView(PhabricatorPaste $paste) { @@ -167,24 +164,40 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { return $action_list; } + + private function buildSubheaderView( + PhabricatorPaste $paste) { + $viewer = $this->getViewer(); + + $author = $viewer->renderHandle($paste->getAuthorPHID())->render(); + $date = phabricator_datetime($paste->getDateCreated(), $viewer); + $author = phutil_tag('strong', array(), $author); + + $author_info = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($paste->getAuthorPHID())) + ->needProfileImage(true) + ->executeOne(); + + $image_uri = $author_info->getProfileImageURI(); + $image_href = '/p/'.$author_info->getUsername(); + + $content = pht('Authored by %s on %s.', $author, $date); + + return id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); + } + private function buildPropertyView( PhabricatorPaste $paste, - array $child_phids, - PhabricatorActionListView $actions) { + array $child_phids) { $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) - ->setObject($paste) - ->setActionList($actions); - - $properties->addProperty( - pht('Author'), - $viewer->renderHandle($paste->getAuthorPHID())); - - $properties->addProperty( - pht('Created'), - phabricator_datetime($paste->getDateCreated(), $viewer)); + ->setObject($paste); if ($paste->getParentPHID()) { $properties->addProperty( diff --git a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php index 698ff983f1..60b02b01f4 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php @@ -52,7 +52,10 @@ final class PhabricatorPeopleProfileViewController $name = $user->getUsername(); $feed = $this->buildPeopleFeed($user, $viewer); - $feed = phutil_tag_div('project-view-feed', $feed); + $feed = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Recent Activity')) + ->addClass('project-view-feed') + ->appendChild($feed); $projects = $this->buildProjectsView($user); $badges = $this->buildBadgesView($user); @@ -106,8 +109,7 @@ final class PhabricatorPeopleProfileViewController return null; } - $view = id(new PHUIBoxView()) - ->setBorder(true) + $view = id(new PHUIObjectBoxView()) ->appendChild($view) ->addClass('project-view-properties'); diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index 1b5b21e464..75a458fdc8 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -31,7 +31,6 @@ final class PonderQuestionViewController extends PonderController { $header->setHeader($question->getTitle()); $header->setUser($viewer); $header->setPolicyObject($question); - $header->setProfileHeader(true); if ($question->getStatus() == PonderQuestionStatus::STATUS_OPEN) { $header->setStatus('fa-square-o', 'bluegrey', pht('Open')); @@ -43,8 +42,8 @@ final class PonderQuestionViewController extends PonderController { $header->setStatus($icon, 'dark', $text); } - $actions = $this->buildActionListView($question); $properties = $this->buildPropertyListView($question); + $actions = $this->buildActionListView($question); $details = $this->buildDetailsPropertyView($question); $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -52,25 +51,6 @@ final class PonderQuestionViewController extends PonderController { $question, PhabricatorPolicyCapability::CAN_EDIT); - $edit_uri = '/question/edit/'.$question->getID().'/'; - $edit_button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('Edit')) - ->setHref($this->getApplicationURI($edit_uri)) - ->setIcon('fa-pencil') - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit); - - $action_button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('Actions')) - ->setHref('#') - ->setIcon('fa-bars') - ->setDropdownMenu($actions); - - $header->addActionLink($action_button); - $header->addActionLink($edit_button); - $content_id = celerity_generate_unique_node_id(); $timeline = $this->buildTransactionTimeline( $question, @@ -81,7 +61,6 @@ final class PonderQuestionViewController extends PonderController { $add_comment = id(new PhabricatorApplicationTransactionCommentView()) ->setUser($viewer) ->setObjectPHID($question->getPHID()) - ->setFullWidth(true) ->setShowPreview(false) ->setAction($this->getApplicationURI("/question/comment/{$id}/")) ->setSubmitButtonName(pht('Comment')); @@ -108,19 +87,14 @@ final class PonderQuestionViewController extends PonderController { $crumbs->addTextCrumb('Q'.$id, '/Q'.$id); $crumbs->setBorder(true); + $subheader = $this->buildSubheaderView($question); + $answer_wiki = null; if ($question->getAnswerWiki()) { $wiki = new PHUIRemarkupView($viewer, $question->getAnswerWiki()); - $wiki_header = phutil_tag( - 'div', - array( - 'class' => 'ponder-answer-wiki-header', - ), - pht('Answer Summary')); - $answer_wiki = id(new PHUIObjectBoxView()) - ->setBackground(PHUIObjectBoxView::BLUE) - ->appendChild($wiki_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setHeaderText(pht('ANSWER SUMMARY')) ->appendChild($wiki) ->addClass('ponder-answer-wiki'); } @@ -143,8 +117,10 @@ final class PonderQuestionViewController extends PonderController { $ponder_view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setSubheader($subheader) ->setMainColumn($ponder_content) - ->setSideColumn($properties) + ->setPropertyList($properties) + ->setActionList($actions) ->addClass('ponder-question-view'); $page_objects = array_merge( @@ -183,6 +159,14 @@ final class PonderQuestionViewController extends PonderController { $icon = 'fa-square-o'; } + $view->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setName(pht('Edit Question')) + ->setHref($this->getApplicationURI("/question/edit/{$id}/")) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + $view->addAction( id(new PhabricatorActionView()) ->setName($name) @@ -206,23 +190,38 @@ final class PonderQuestionViewController extends PonderController { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) - ->setObject($question) - ->setStacked(true); + ->setObject($question); $view->invokeWillRenderEvent(); - if (!$view->hasAnyProperties()) { - return null; - } - - $view = id(new PHUIObjectBoxView()) - ->appendChild($view) - ->setBackground(PHUIObjectBoxView::GREY) - ->addClass('ponder-view-properties'); - return $view; } + private function buildSubheaderView( + PonderQuestion $question) { + $viewer = $this->getViewer(); + + $asker = $viewer->renderHandle($question->getAuthorPHID())->render(); + $date = phabricator_datetime($question->getDateCreated(), $viewer); + $asker = phutil_tag('strong', array(), $asker); + + $author = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($question->getAuthorPHID())) + ->needProfileImage(true) + ->executeOne(); + + $image_uri = $author->getProfileImageURI(); + $image_href = '/p/'.$author->getUsername(); + + $content = pht('Asked by %s on %s.', $asker, $date); + + return id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); + } + private function buildDetailsPropertyView( PonderQuestion $question) { $viewer = $this->getViewer(); @@ -239,49 +238,14 @@ final class PonderQuestionViewController extends PonderController { pht('No further details for this question.')); } - $asker = $viewer->renderHandle($question->getAuthorPHID())->render(); - $date = phabricator_datetime($question->getDateCreated(), $viewer); - $asker = phutil_tag('strong', array(), $asker); + $question_details = phutil_tag_div( + 'phabricator-remarkup ml', $question_details); - $author = id(new PhabricatorPeopleQuery()) - ->setViewer($viewer) - ->withPHIDs(array($question->getAuthorPHID())) - ->needProfileImage(true) - ->executeOne(); - - $image_uri = $author->getProfileImageURI(); - $image_href = '/p/'.$author->getUsername(); - - $image = phutil_tag( - 'a', - array( - 'class' => 'ponder-details-author-image', - 'style' => 'background-image: url('.$image_uri.');', - 'href' => $image_href, - )); - - $details_header = phutil_tag( - 'div', - array( - 'class' => 'ponder-details-subtitle', - ), - array( - $image, - pht('Asked by %s on %s.', $asker, $date), - )); - - $details = phutil_tag( - 'div', - array( - 'class' => 'ponder-detail-view', - ), - array( - $details_header, - phutil_tag_div('phabricator-remarkup', $question_details), - )); - - - return $details; + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('DETAILS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setFlush(true) + ->appendChild($question_details); } /** diff --git a/src/applications/ponder/view/PonderAddAnswerView.php b/src/applications/ponder/view/PonderAddAnswerView.php index ad17f68af6..fb52bd17ed 100644 --- a/src/applications/ponder/view/PonderAddAnswerView.php +++ b/src/applications/ponder/view/PonderAddAnswerView.php @@ -51,7 +51,6 @@ final class PonderAddAnswerView extends AphrontView { ->setUser($this->user) ->setAction($this->actionURI) ->setWorkflow(true) - ->setFullWidth(true) ->addHiddenInput('question_id', $question->getID()) ->appendChild( id(new PhabricatorRemarkupControl()) @@ -78,7 +77,7 @@ final class PonderAddAnswerView extends AphrontView { $box = id(new PHUIObjectBoxView()) ->appendChild($form) - ->setBackground(PHUIObjectBoxView::GREY) + ->setHeaderText('Answer') ->addClass('ponder-add-answer-view'); if ($info_panel) { diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index ddbd0ffb5b..f61ebd28d2 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -82,7 +82,11 @@ final class PhabricatorProjectProfileController ->execute(); $feed = $this->renderStories($stories); - $feed = phutil_tag_div('project-view-feed', $feed); + $feed = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Recent Activity')) + ->addClass('project-view-feed') + ->appendChild($feed); + require_celerity_resource('project-view-css'); $home = id(new PHUITwoColumnView()) @@ -134,8 +138,7 @@ final class PhabricatorProjectProfileController return null; } - $view = id(new PHUIBoxView()) - ->setBorder(true) + $view = id(new PHUIObjectBoxView()) ->appendChild($view) ->addClass('project-view-properties'); diff --git a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php index fd05afb057..8603185c73 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php @@ -23,7 +23,6 @@ final class PhabricatorSlowvotePollController } $poll_view = id(new SlowvoteEmbedView()) - ->setHeadless(true) ->setUser($viewer) ->setPoll($poll); @@ -47,19 +46,30 @@ final class PhabricatorSlowvotePollController ->setPolicyObject($poll); $actions = $this->buildActionView($poll); - $properties = $this->buildPropertyView($poll, $actions); + $properties = $this->buildPropertyView($poll); + $subheader = $this->buildSubheaderView($poll); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb('V'.$poll->getID()); + $crumbs->setBorder(true); $timeline = $this->buildTransactionTimeline( $poll, new PhabricatorSlowvoteTransactionQuery()); $add_comment = $this->buildCommentForm($poll); - $object_box = id(new PHUIObjectBoxView()) + $poll_content = array( + $poll_view, + $timeline, + $add_comment, + ); + + $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->addPropertyList($properties); + ->setSubheader($subheader) + ->setMainColumn($poll_content) + ->setPropertyList($properties) + ->setActionList($actions); return $this->newPage() ->setTitle('V'.$poll->getID().' '.$poll->getQuestion()) @@ -67,10 +77,7 @@ final class PhabricatorSlowvotePollController ->setPageObjectPHIDs(array($poll->getPHID())) ->appendChild( array( - $object_box, - $poll_view, - $timeline, - $add_comment, + $view, )); } @@ -110,30 +117,42 @@ final class PhabricatorSlowvotePollController } private function buildPropertyView( - PhabricatorSlowvotePoll $poll, - PhabricatorActionListView $actions) { + PhabricatorSlowvotePoll $poll) { $viewer = $this->getRequest()->getUser(); - $view = id(new PHUIPropertyListView()) ->setUser($viewer) - ->setObject($poll) - ->setActionList($actions); - + ->setObject($poll); $view->invokeWillRenderEvent(); - $description = $poll->getDescription(); - if (strlen($description)) { - $description = new PHUIRemarkupView($viewer, $description); - $view->addSectionHeader( - pht('Description'), - PHUIPropertyListView::ICON_SUMMARY); - $view->addTextContent($description); - } - return $view; } + private function buildSubheaderView( + PhabricatorSlowvotePoll $poll) { + $viewer = $this->getViewer(); + + $author = $viewer->renderHandle($poll->getAuthorPHID())->render(); + $date = phabricator_datetime($poll->getDateCreated(), $viewer); + $author = phutil_tag('strong', array(), $author); + + $person = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($poll->getAuthorPHID())) + ->needProfileImage(true) + ->executeOne(); + + $image_uri = $person->getProfileImageURI(); + $image_href = '/p/'.$person->getUsername(); + + $content = pht('Asked by %s on %s.', $author, $date); + + return id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); + } + private function buildCommentForm(PhabricatorSlowvotePoll $poll) { $viewer = $this->getRequest()->getUser(); diff --git a/src/applications/slowvote/view/SlowvoteEmbedView.php b/src/applications/slowvote/view/SlowvoteEmbedView.php index 3582ea45b3..452cfbe9eb 100644 --- a/src/applications/slowvote/view/SlowvoteEmbedView.php +++ b/src/applications/slowvote/view/SlowvoteEmbedView.php @@ -4,12 +4,6 @@ final class SlowvoteEmbedView extends AphrontView { private $poll; private $handles; - private $headless; - - public function setHeadless($headless) { - $this->headless = $headless; - return $this; - } public function setPoll(PhabricatorSlowvotePoll $poll) { $this->poll = $poll; @@ -68,29 +62,25 @@ final class SlowvoteEmbedView extends AphrontView { ), $poll->getQuestion()); - if ($this->headless) { - $header = null; - } else { - $header = id(new PHUIHeaderView()) - ->setHeader($link_to_slowvote); + $header = id(new PHUIHeaderView()) + ->setHeader($link_to_slowvote); - $description = $poll->getDescription(); - if (strlen($description)) { - $description = new PHUIRemarkupView($this->getUser(), $description); - $description = phutil_tag( - 'div', - array( - 'class' => 'slowvote-description', - ), - $description); - } - - $header = array( - $header, - $description, - ); + $description = $poll->getDescription(); + if (strlen($description)) { + $description = new PHUIRemarkupView($this->getUser(), $description); + $description = phutil_tag( + 'div', + array( + 'class' => 'slowvote-description', + ), + $description); } + $header = array( + $header, + $description, + ); + $vis = $poll->getResponseVisibility(); if ($this->areResultsVisible()) { if ($vis == PhabricatorSlowvotePoll::RESPONSES_OWNER) { @@ -163,8 +153,10 @@ final class SlowvoteEmbedView extends AphrontView { array($body)); return id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setHeader($header) - ->appendChild($embed); + ->appendChild($embed) + ->addClass('slowvote-poll-view'); } private function renderLabel(PhabricatorSlowvoteOption $option, $selected) { diff --git a/src/view/phui/PHUIHeadThingView.php b/src/view/phui/PHUIHeadThingView.php new file mode 100644 index 0000000000..4ff0290578 --- /dev/null +++ b/src/view/phui/PHUIHeadThingView.php @@ -0,0 +1,65 @@ +imageHref = $href; + return $this; + } + + public function setImage($image) { + $this->image = $image; + return $this; + } + + public function setContent($content) { + $this->content = $content; + return $this; + } + + public function setSize($size) { + $this->size = $size; + return $this; + } + + protected function getTagAttributes() { + require_celerity_resource('phui-head-thing-view-css'); + + $classes = array(); + $classes[] = 'phui-head-thing-view'; + + if ($this->size) { + $classes[] = $this->size; + } else { + $classes[] = self::SMALL; + } + + return array( + 'class' => $classes, + ); + } + + protected function getTagContent() { + + $image = phutil_tag( + 'a', + array( + 'class' => 'phui-head-thing-image', + 'style' => 'background-image: url('.$this->image.');', + 'href' => $this->imageHref, + )); + + return array($image, $this->content); + + + } + +} diff --git a/src/view/phui/PHUIObjectBoxView.php b/src/view/phui/PHUIObjectBoxView.php index 5cbb3a58b6..9fa280a843 100644 --- a/src/view/phui/PHUIObjectBoxView.php +++ b/src/view/phui/PHUIObjectBoxView.php @@ -36,6 +36,7 @@ final class PHUIObjectBoxView extends AphrontTagView { const COLOR_YELLOW = 'yellow'; const BLUE = 'phui-box-blue'; + const BLUE_PROPERTY = 'phui-box-blue-property'; const GREY = 'phui-box-grey'; public function addPropertyList( diff --git a/src/view/phui/PHUITwoColumnView.php b/src/view/phui/PHUITwoColumnView.php index 5a47dc925a..62db9c16e7 100644 --- a/src/view/phui/PHUITwoColumnView.php +++ b/src/view/phui/PHUITwoColumnView.php @@ -3,10 +3,13 @@ final class PHUITwoColumnView extends AphrontTagView { private $mainColumn; - private $sideColumn; + private $sideColumn = null; private $display; private $fluid; private $header; + private $subheader; + private $actionList; + private $propertyList; const DISPLAY_LEFT = 'phui-side-column-left'; const DISPLAY_RIGHT = 'phui-side-column-right'; @@ -26,6 +29,21 @@ final class PHUITwoColumnView extends AphrontTagView { return $this; } + public function setSubheader($subheader) { + $this->subheader = $subheader; + return $this; + } + + public function setActionList(PhabricatorActionListView $list) { + $this->actionList = $list; + return $this; + } + + public function setPropertyList(PHUIPropertyListView $list) { + $this->propertyList = $list; + return $this; + } + public function setFluid($fluid) { $this->fluid = $fluid; return $this; @@ -53,6 +71,10 @@ final class PHUITwoColumnView extends AphrontTagView { $classes[] = 'phui-two-column-fluid'; } + if ($this->subheader) { + $classes[] = 'with-subheader'; + } + return array( 'class' => implode(' ', $classes), ); @@ -68,25 +90,25 @@ final class PHUITwoColumnView extends AphrontTagView { ), $this->mainColumn); - $side = phutil_tag( - 'div', - array( - 'class' => 'phui-side-column', - ), - $this->sideColumn); + $side = $this->buildSideColumn(); + $order = array($side, $main); - if ($this->getDisplay() == self::DISPLAY_LEFT) { - $order = array($side, $main); - } else { - $order = array($main, $side); - } - - $inner = phutil_tag_div('phui-two-column-row', $order); + $inner = phutil_tag_div('phui-two-column-row grouped', $order); $table = phutil_tag_div('phui-two-column-content', $inner); $header = null; if ($this->header) { - $header = phutil_tag_div('phui-two-column-header', $this->header); + if ($this->actionList) { + $this->header->setActionList($this->actionList); + } + $header = phutil_tag_div( + 'phui-two-column-header', $this->header); + } + + $subheader = null; + if ($this->subheader) { + $subheader = phutil_tag_div( + 'phui-two-column-subheader', $this->subheader); } return phutil_tag( @@ -96,7 +118,35 @@ final class PHUITwoColumnView extends AphrontTagView { ), array( $header, + $subheader, $table, )); } + + private function buildSideColumn() { + $property_list = $this->propertyList; + $action_list = $this->actionList; + + $properties = null; + if ($property_list || $action_list) { + if ($property_list) { + $property_list->setStacked(true); + } + + $properties = id(new PHUIObjectBoxView()) + ->appendChild($action_list) + ->appendChild($property_list) + ->addClass('phui-two-column-properties'); + } + + return phutil_tag( + 'div', + array( + 'class' => 'phui-side-column', + ), + array( + $properties, + $this->sideColumn, + )); + } } diff --git a/webroot/rsrc/css/application/countdown/timer.css b/webroot/rsrc/css/application/countdown/timer.css index 8b87aa6ed2..bbb2fdd684 100644 --- a/webroot/rsrc/css/application/countdown/timer.css +++ b/webroot/rsrc/css/application/countdown/timer.css @@ -4,9 +4,6 @@ .phabricator-timer { margin: 16px 16px 0 16px; - border: 1px solid {$lightblueborder}; - border-radius: 3px; - background: #ffffff; } .device .phabricator-timer { @@ -14,27 +11,20 @@ margin-right: 8px; } -.phabricator-remarkup .phabricator-timer { - max-width: 360px; +.phabricator-remarkup .phabricator-timer-view { + max-width: 460px; margin: 0 0 12px 0; } +.phabricator-timer .countdown-description { + border-bottom: 1px solid {$thinblueborder}; + padding-bottom: 16px; +} + .device-phone .phabricator-remarkup .phabricator-timer { width: auto; } -.phabricator-timer-header { - font-weight: bold; - padding: 8px; - background: {$bluebackground}; - border-radius: 3px 3px 0 0; -} - -.phabricator-timer-header a { - color: {$darkbluetext}; - font-weight: normal; -} - .phabricator-timer-table { width: 100%; margin: 8px 0 0 0; @@ -60,12 +50,11 @@ } .phabricator-timer-table td.phabricator-timer-foot { - font-size: {$normalfontsize}; + font-size: {$biggerfontsize}; line-height: 16px; border-top: 1px solid {$thinblueborder}; color: {$bluetext}; - font-weight: normal; - padding: 8px; + padding: 12px 0; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; text-align: center; diff --git a/webroot/rsrc/css/application/paste/paste.css b/webroot/rsrc/css/application/paste/paste.css index 78bfec2587..b9b2fe2dd2 100644 --- a/webroot/rsrc/css/application/paste/paste.css +++ b/webroot/rsrc/css/application/paste/paste.css @@ -2,14 +2,6 @@ * @provides paste-css */ -.container-of-paste { - margin: 16px 16px 0 16px; -} - -.device .container-of-paste { - margin: 8px 8px 0 8px; -} - .paste-embed { background: {$sh-yellowbackground}; border: 1px solid {$sh-lightyellowborder}; diff --git a/webroot/rsrc/css/application/ponder/ponder-view.css b/webroot/rsrc/css/application/ponder/ponder-view.css index a5bd01a6b1..8d0556ef87 100644 --- a/webroot/rsrc/css/application/ponder/ponder-view.css +++ b/webroot/rsrc/css/application/ponder/ponder-view.css @@ -2,40 +2,10 @@ * @provides ponder-view-css */ -.ponder-question-view { - background: #fff; - padding-bottom: 64px; -} - -.device-desktop .ponder-question-view.phui-two-column-view .phui-side-column { - width: 300px; -} - -.ponder-question-view .phui-object-box, -.ponder-question-view .phui-info-view { - margin-left: 0; - margin-right: 0; -} - -.device-phone .ponder-question-view .phui-profile-header.phui-header-shell - .phui-header-header { - font-size: 20px; -} - .ponder-question-container { border-top: 1px solid {$thinblueborder}; } -.ponder-question-content { - margin: 0 24px; - padding: 24px 0; - border-top: 1px solid rgba({$alphagrey}, .15); -} - -.device-phone .ponder-question-content { - margin: 0 16px; -} - .device .ponder-view-properties { border-left: none; border-right: none; @@ -57,59 +27,19 @@ border: none; } -.ponder-view-properties .phui-property-list-stacked - .phui-property-list-properties .phui-property-list-key { - padding: 0; +.phui-two-column-view .phui-main-column .ponder-question-content + .phui-box.ponder-answer-wiki { + margin: 24px 0; } -.ponder-view-properties .phui-property-list-stacked - .phui-property-list-properties .phui-property-list-value { - margin-bottom: 16px; - padding: 0; -} - -.phui-box.ponder-answer-wiki { +.ponder-answer-wiki .phabricator-remarkup { padding: 16px; - margin: 24px 0; -} - -.ponder-details-subtitle { - height: 24px; - line-height: 24px; - margin-bottom: 12px; - color: {$greytext}; - position: relative; - padding-left: 32px; -} - -.ponder-details-subtitle a { - color: {$darkgreytext}; -} - -.ponder-details-author-image { - height: 24px; - width: 24px; - background-size: 100%; - margin-right: 8px; - border-radius: 3px; - display: inline-block; - position: absolute; - top: 0; - left: 0; -} - -.ponder-detail-view .phabricator-remarkup { - margin-left: 32px; } .ponder-question-content .phui-timeline-view { padding-right: 0; } -.ponder-question-content .phui-timeline-view .phui-timeline-core-content { - background-color: {$lightbluebackground}; -} - .ponder-answer-view { margin-top: 16px; } @@ -123,7 +53,7 @@ margin-left: 12px; } -.ponder-question-view .ponder-answer-view .phui-header-shell { +.ponder-question-view .ponder-answer .phui-header-shell { padding: 4px 8px 3px 8px; } @@ -192,6 +122,7 @@ body .phui-main-column .ponder-question-content .ponder-answer-view .ponder-add-answer-header { margin-top: 64px; + margin-bottom: 20px; } .ponder-add-answer-view { @@ -200,9 +131,8 @@ body .phui-main-column .ponder-question-content .ponder-answer-view .ponder-question-content div.ponder-question-add-comment-view div.phui-box.phui-object-box { - background: {$lightbluebackground}; margin-right: 0; - margin-left: 32px; + margin-left: 62px; } .device .ponder-question-content div.ponder-question-add-comment-view @@ -210,19 +140,6 @@ body .phui-main-column .ponder-question-content .ponder-answer-view margin: 0; } -.ponder-add-answer-view .phui-form-full-width.phui-form-view - label.aphront-form-label, -.ponder-question-add-comment-view .phui-form-full-width.phui-form-view - label.aphront-form-label{ - display: none; -} - -.ponder-add-answer-view.phui-box-grey .phui-header-shell { - border: none; - padding-bottom: 8px; -} - -.ponder-add-answer-view .remarkup-assist-textarea, .ponder-question-add-comment-view .remarkup-assist-textarea { height: 8em; } diff --git a/webroot/rsrc/css/application/project/project-view.css b/webroot/rsrc/css/application/project/project-view.css index be3e8b67f4..2c0fd7d6d8 100644 --- a/webroot/rsrc/css/application/project/project-view.css +++ b/webroot/rsrc/css/application/project/project-view.css @@ -2,11 +2,6 @@ * @provides project-view-css */ -.project-view-home { - padding-bottom: 64px; - background: #fff; -} - .project-view-header-tag { margin-left: 8px; font-size: {$normalfontsize}; @@ -24,25 +19,35 @@ color: {$bluetext}; } -.project-view-home .phui-box.project-view-properties { - margin: 0 16px 16px 16px; - padding: 4px 12px; - border: 2px solid rgba({$alphagrey},.1); - background-color: #F7F7F9; +.device .project-view-home .phui-two-column-row { + display: flex; + flex-direction: column-reverse; } +.project-view-home .phui-box.project-view-properties { + margin: 0 0 16px 0; + padding: 0; + border: 1px solid rgba({$alphagrey}, .2); + background-color: #fff; +} + +.device-desktop .phui-two-column-view .project-view-properties + .phui-property-list-container { + padding: 12px 0; + } + .device-phone .phui-box.project-view-properties { margin: 0 12px 0 12px; } .project-view-properties .phui-property-list-container + .phui-property-list-text-content { - border-color: rgba({$alphagrey},.15); + border-color: rgba({$alphagrey},.2); } .project-view-properties .phui-property-list-key { width: auto; - margin-left: 4px2 + margin-left: 4px; } .project-view-properties .phui-property-list-section-header { @@ -50,23 +55,8 @@ padding: 12px 4px 0; } -.project-view-feed .phui-object-box.phui-box-border { - padding: 0 4px 8px 4px; - border: none; -} - -.project-view-feed .phui-object-box .phui-header-shell { - padding: 8px 4px; -} - -.project-view-feed .phui-header-header { - font-size: {$biggerfontsize}; - margin-left: 8px; -} - -.device-desktop .project-view-feed .phui-feed-story, -.device-tablet .project-view-feed .phui-feed-story { - padding-left: 22px; +.project-view-feed.phui-object-box.phui-box-border { + border: 1px solid rgba({$alphagrey}, .2); } .project-view-home .phui-box-grey { @@ -81,13 +71,17 @@ font-size: {$biggerfontsize}; } +.project-view-home .phui-box-grey .phui-header-action-link { + margin-top: 0; + margin-bottom: 0; +} + .project-view-home .phui-box-grey .phui-object-item-list-view { padding: 4px 8px 0 8px; } .project-view-badges .phui-badge-flex-view { background-color: #fff; - width: 340px; } .project-view-home .phui-box-grey .phui-object-item-attribute .phui-icon-view { diff --git a/webroot/rsrc/css/application/slowvote/slowvote.css b/webroot/rsrc/css/application/slowvote/slowvote.css index afa45a8032..907ff0c242 100644 --- a/webroot/rsrc/css/application/slowvote/slowvote.css +++ b/webroot/rsrc/css/application/slowvote/slowvote.css @@ -2,22 +2,8 @@ * @provides phabricator-slowvote-css */ -.slowvote-embed { - background: #fff; - border-color: {$lightblueborder}; - border-radius: 3px; -} - -.slowvote-header { - font-weight: bold; - line-height: 16px; - border-bottom: 1px solid #bbbbbb; -} - .slowvote-description { - color: {$greytext}; - font-size: {$biggerfontsize}; - padding: 12px 4px 4px; + padding: 12px 16px 4px; } .slowvote-header-content { @@ -25,7 +11,7 @@ } .slowvote-body-content { - padding: 8px 4px 0; + padding: 8px 16px; } .slowvote-option-label { @@ -121,7 +107,7 @@ } .slowvote-footer-content { - padding: 8px; + padding: 8px 16px; overflow: hidden; } diff --git a/webroot/rsrc/css/phui/phui-box.css b/webroot/rsrc/css/phui/phui-box.css index 1516285cf8..38ca1d7610 100644 --- a/webroot/rsrc/css/phui/phui-box.css +++ b/webroot/rsrc/css/phui/phui-box.css @@ -15,7 +15,7 @@ .phui-box-grey { background-color: #F7F7F9; border-radius: 3px; - border-color: rgba({$alphagrey},.1); + border-color: rgba({$alphagrey},.2); } .phui-box-blue { @@ -55,3 +55,33 @@ border: none; color: {$greytext}; } + + +/* Property Boxes */ + +.phui-box.phui-box-blue-property { + border-radius: 3px; + border-color: {$lightblueborder}; + margin: 0; + padding: 0; +} + +.device .phui-box.phui-box-blue-property { + padding: 0; +} + +.phui-box.phui-object-box.phui-box-blue-property .phui-header-shell { + background-color: #eff3fc; + border-top-right-radius: 3px; + border-top-left-radius: 3px; + padding: 6px 16px; +} + +.device .phui-box.phui-box-blue-property .phui-header-shell { + padding: 6px 12px; +} + +.phui-box.phui-box-blue-property .phui-header-header { + font-size: 13px; + color: {$bluetext}; +} diff --git a/webroot/rsrc/css/phui/phui-button.css b/webroot/rsrc/css/phui/phui-button.css index 0ba3007e5f..837c093872 100644 --- a/webroot/rsrc/css/phui/phui-button.css +++ b/webroot/rsrc/css/phui/phui-button.css @@ -60,7 +60,8 @@ input[type="submit"].grey, a.grey, a.grey:visited { background-color: #F7F7F9; - border: 1px solid rgba({$alphagrey},.1); + background-image: linear-gradient(to bottom, #ffffff, #f1f0f1); + border: 1px solid rgba({$alphablue},.2); color: {$darkgreytext}; } @@ -106,8 +107,8 @@ button:hover { a.button.grey:hover, button.grey:hover { - background-color: rgba({$alphablue}, 0.1); - border-color: rgba({$alphablue}, 0.15); + background-image: linear-gradient(to bottom, #ffffff, #eeebec); + border-color: rgba({$alphablue}, 0.3); transition: 0.1s; } diff --git a/webroot/rsrc/css/phui/phui-head-thing.css b/webroot/rsrc/css/phui/phui-head-thing.css new file mode 100644 index 0000000000..bce67ef387 --- /dev/null +++ b/webroot/rsrc/css/phui/phui-head-thing.css @@ -0,0 +1,33 @@ +/** + * @provides phui-head-thing-view-css + */ + +.phui-head-thing-view { + height: 24px; + line-height: 22px; + color: {$greytext}; + position: relative; + padding-left: 32px; +} + +.device-phone .phui-head-thing-view { + min-height: 24px; + height: auto; + line-height: inherit; +} + +.phui-head-thing-view a { + color: {$darkgreytext}; +} + +.phui-head-thing-image { + height: 24px; + width: 24px; + background-size: 100%; + margin-right: 8px; + border-radius: 3px; + display: inline-block; + position: absolute; + top: 0; + left: 0; +} diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index c364059fc5..b743e1de7b 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -185,7 +185,7 @@ body .phui-header-shell.phui-bleed-header .phui-header-subheader .phui-header-status { padding: 3px 9px; border-radius: 3px; - background: rgba({$alphablue}, 0.06); + background: rgba({$alphablue}, 0.08); margin-right: 8px; } diff --git a/webroot/rsrc/css/phui/phui-object-box.css b/webroot/rsrc/css/phui/phui-object-box.css index d7452c2126..f9f06e2d18 100644 --- a/webroot/rsrc/css/phui/phui-object-box.css +++ b/webroot/rsrc/css/phui/phui-object-box.css @@ -120,6 +120,10 @@ div.phui-object-box.phui-object-box-flush { margin: 0; } +.phui-object-box .phui-box-border.phui-box-blue-property { + border-width: 1px; +} + .phui-object-box .phui-object-box .phui-header-shell .phui-header-header { font-size: {$normalfontsize}; margin: 0; diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index 6e0880a4d9..91e236ec6b 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -2,57 +2,191 @@ * @provides phui-two-column-view-css */ -.phui-two-column-view { +.phui-two-column-view .phui-two-column-header { background-color: #fff; + border-bottom: 1px solid rgba({$alphagrey}, .12); + margin-bottom: 24px; } -.phui-two-column-container { - max-width: 1024px; - margin: 0 auto; +.phui-two-column-view.with-subheader .phui-two-column-header { + margin-bottom: 0; +} + +.phui-two-column-header .phui-header-header { + font-size: 20px; + font-family: 'Aleo', {$fontfamily}; + color: #000; +} + +.device-phone .phui-two-column-header .phui-header-header { + font-size: 18px; } .phui-two-column-view .phui-two-column-header .phui-header-shell { - padding-bottom: 32px; + padding: 24px 32px 28px; + border: none; } -.device-phone .phui-two-column-view .phui-two-column-header .phui-header-shell { - padding-bottom: 20px; +.phui-two-column-view .phui-two-column-header + .phui-profile-header.phui-header-shell { + padding-bottom: 20px; } -.phui-two-column-fluid .phui-two-column-container { - max-width: 100%; +.device .phui-two-column-view .phui-two-column-header .phui-header-shell { + padding: 12px 12px 16px; } -.phui-two-column-content { - display: table; - width: 100%; +.phui-two-column-header .phui-header-subheader { + margin-top: 12px; } -.phui-two-column-row { - display: table-row; +.phui-two-column-subheader { + padding: 12px 32px; +} + +.device .phui-two-column-subheader { + padding: 12px 16px; +} + +.device-desktop .phui-two-column-content { + padding: 0 32px; +} + +.device .phui-two-column-content { + padding: 0 12px; } .device-desktop .phui-two-column-view .phui-main-column { - display: table-cell; - vertical-align: top; + float: left; + width: calc(100% - 320px); } .device-desktop .phui-two-column-view .phui-side-column { - width: 320px; - display: table-cell; - vertical-align: top; + float: right; + width: 300px; } -.device-desktop .phui-two-column-view - .phui-main-column .phui-object-box:first-child { - margin: 0 16px 0 16px; +.device .phui-side-column { + margin-bottom: 20px; } -.device-desktop .phui-two-column-view - .phui-side-column .phui-object-box { - margin: 0 16px 16px 0; +.phui-two-column-view .phui-two-column-content + .phui-object-box { + margin: 0 0 20px 0; } -.phui-two-column-view pre { - white-space: pre-wrap; +/* Timeline */ + +.phui-two-column-view .phui-timeline-view { + padding: 0; + background-position: 78px; +} + +.phui-two-column-view .phui-main-column .phui-object-box + .phui-timeline-view { + margin-top: -20px; +} + +.device .phui-two-column-view .phui-timeline-view { + background-position: 16px; + padding: 0; +} + +.device-phone .phui-two-column-view .phui-timeline-event-view { + margin: 0; +} + +/* Main Column Properties */ + +.device-desktop .phui-main-column .phui-property-list-key { + margin-left: 0; + width: 140px; +} + +.device-desktop .phui-main-column .phui-property-list-value { + margin-left: 8px; + width: auto; +} + + +/* Property / Action List */ + +.phui-two-column-properties .phabricator-action-list-view { + padding-top: 4px; +} + +.device-desktop .phui-two-column-view .phui-property-list-container { + padding: 12px 16px; +} + +.device .phui-two-column-view .phui-property-list-container { + padding: 12px 8px; +} + +.phui-two-column-properties.phui-object-box { + border: 1px solid rgba({$alphablue}, .2); +} + +.phui-two-column-properties .phui-property-list-stacked + .phui-property-list-properties .phui-property-list-key { + margin: 20px 0 8px 0; + padding: 20px 4px 0; + border-top: 1px solid rgba({$alphablue}, .2); +} + +.phui-two-column-properties .phui-property-list-stacked + .phui-property-list-properties .phui-property-list-value { + margin: 0; + padding: 0 4px; +} + +.device-desktop .phui-two-column-properties .phui-property-list-container { + padding: 0 0 20px 0; +} + +.device .phui-two-column-properties .phui-property-list-stacked + .phui-property-list-properties .phui-property-list-key { + margin: 12px 0 4px 0; + padding: 0; + border: none; +} + +.device .phui-two-column-properties .phui-property-list-container { + padding: 0 0 12px 0; +} + +.device .phui-two-column-content .phui-two-column-properties.phui-object-box { + padding: 0 12px; +} + +.phui-two-column-properties .phabricator-action-view-icon { + top: 8px; + left: 8px; +} + +.phabricator-action-view button.phabricator-action-view-item, +.phabricator-action-view-item { + padding: 5px 4px 5px 28px; +} + +.device-desktop .phui-two-column-properties .phabricator-action-view:hover + .phabricator-action-view-item { + text-decoration: none; + background-color: rgba({$alphablue}, .08); + color: {$sky}; + border-radius: 3px; +} + +.device-desktop .phui-two-column-properties .phabricator-action-view:hover + .phabricator-action-view-icon { + color: {$sky}; +} + +.phui-two-column-view .phui-property-list-section-header, +.phui-two-column-view .phui-property-list-text-content { + margin: 0 16px; +} + +.device .phui-two-column-view .phui-property-list-section-header, +.device .phui-two-column-view .phui-property-list-text-content { + margin: 0 8px; } From 21f8323612cba2b652151f932018f3384b36069e Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 1 Mar 2016 11:43:58 -0800 Subject: [PATCH 15/41] Fix spaces UI in PHUIHeaderView Summary: This should consistenly apply the styling regardless of font or size of the Header. Fixes T10485 Test Plan: Visit a Task and a Countdown in a different Space. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T10485 Differential Revision: https://secure.phabricator.com/D15374 --- resources/celerity/map.php | 6 +++--- src/view/phui/PHUIHeaderView.php | 8 ++++++-- webroot/rsrc/css/phui/phui-header-view.css | 8 +++++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4df1247742..cd067f35e3 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'e50c063a', + 'core.pkg.css' => '3b2e293c', 'core.pkg.js' => '7d8faf57', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', @@ -135,7 +135,7 @@ return array( 'rsrc/css/phui/phui-form-view.css' => '4a1a0f5e', 'rsrc/css/phui/phui-form.css' => 'aac1d51d', 'rsrc/css/phui/phui-head-thing.css' => '11731da0', - 'rsrc/css/phui/phui-header-view.css' => 'b541cc78', + 'rsrc/css/phui/phui-header-view.css' => '6152c91b', 'rsrc/css/phui/phui-hovercard.css' => 'de1a2119', 'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad', 'rsrc/css/phui/phui-icon.css' => '3f33ab57', @@ -820,7 +820,7 @@ return array( 'phui-form-css' => 'aac1d51d', 'phui-form-view-css' => '4a1a0f5e', 'phui-head-thing-view-css' => '11731da0', - 'phui-header-view-css' => 'b541cc78', + 'phui-header-view-css' => '6152c91b', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'de1a2119', 'phui-icon-set-selector-css' => '1ab67aad', diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php index 233e5a7c72..7227bed0a7 100644 --- a/src/view/phui/PHUIHeaderView.php +++ b/src/view/phui/PHUIHeaderView.php @@ -253,8 +253,9 @@ final class PHUIHeaderView extends AphrontTagView { $left = array(); $right = array(); + $space_header = null; if ($viewer) { - $left[] = id(new PHUISpacesNamespaceContextView()) + $space_header = id(new PHUISpacesNamespaceContextView()) ->setUser($viewer) ->setObject($this->policyObject); } @@ -335,7 +336,10 @@ final class PHUIHeaderView extends AphrontTagView { array( 'class' => 'phui-header-header', ), - $header_content); + array( + $space_header, + $header_content, + )); if ($this->subheader || $this->badges) { $badges = null; diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index b743e1de7b..1ce8efd9e2 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -272,10 +272,16 @@ body .phui-header-shell.phui-bleed-header font-size: 18px; } -.spaces-name .phui-handle { +.spaces-name .phui-handle, +.spaces-name a.phui-handle { color: {$sh-redtext}; } +.device-desktop .spaces-name a.phui-handle:hover { + color: {$sh-redtext}; + text-decoration: underline; +} + .phui-header-subheader .phui-badge-flex-view { display: inline; margin-right: 4px; From db4fdf8c2d0760201cf85557fe36c22ea5690abf Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 1 Mar 2016 20:11:51 +0000 Subject: [PATCH 16/41] Update Herald for PHUITwoColumnView Summary: Updates Herald to new two column layout Test Plan: View herald rule tablet, desktop, mobile Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15373 --- .../controller/HeraldRuleViewController.php | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/src/applications/herald/controller/HeraldRuleViewController.php b/src/applications/herald/controller/HeraldRuleViewController.php index 3b209dd960..da9f35c6e4 100644 --- a/src/applications/herald/controller/HeraldRuleViewController.php +++ b/src/applications/herald/controller/HeraldRuleViewController.php @@ -33,12 +33,14 @@ final class HeraldRuleViewController extends HeraldController { } $actions = $this->buildActionView($rule); - $properties = $this->buildPropertyView($rule, $actions); + $properties = $this->buildPropertyView($rule); + $details = $this->buildDetailsView($rule); $id = $rule->getID(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb("H{$id}"); + $crumbs->setBorder(true); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) @@ -51,13 +53,21 @@ final class HeraldRuleViewController extends HeraldController { $title = $rule->getName(); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn(array( + $details, + $timeline, + )) + ->setPropertyList($properties) + ->setActionList($actions); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $object_box, - $timeline, + $view, )); } @@ -105,15 +115,24 @@ final class HeraldRuleViewController extends HeraldController { } private function buildPropertyView( - HeraldRule $rule, - PhabricatorActionListView $actions) { + HeraldRule $rule) { $viewer = $this->getRequest()->getUser(); - $view = id(new PHUIPropertyListView()) ->setUser($viewer) - ->setObject($rule) - ->setActionList($actions); + ->setObject($rule); + + $view->invokeWillRenderEvent(); + + return $view; + } + + private function buildDetailsView( + HeraldRule $rule) { + + $viewer = $this->getRequest()->getUser(); + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); $view->addProperty( pht('Rule Type'), @@ -139,8 +158,6 @@ final class HeraldRuleViewController extends HeraldController { $viewer->renderHandle($rule->getTriggerObjectPHID())); } - $view->invokeWillRenderEvent(); - $view->addSectionHeader( pht('Rule Description'), PHUIPropertyListView::ICON_SUMMARY); @@ -150,7 +167,10 @@ final class HeraldRuleViewController extends HeraldController { $view->addTextContent($rule_text); } - return $view; + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('DETAILS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($view); } } From 58aac5de0ec2cb6f46df1f12dea3506e07a8df87 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 1 Mar 2016 21:14:19 +0000 Subject: [PATCH 17/41] Pop the buttons Summary: Keeping in step with the gradients on the grey buttons, this adds slight gradients and hover states to blue and green. I feel like they are much more obviously buttons now in the UI (on headers for example). Test Plan: Review UI Buttons, check differential, actions, other random buttons. {F1137208} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15379 --- resources/celerity/map.php | 6 +++--- webroot/rsrc/css/phui/phui-button.css | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index cd067f35e3..b9cde6ad51 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '3b2e293c', + 'core.pkg.css' => '80c32191', 'core.pkg.js' => '7d8faf57', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', @@ -124,7 +124,7 @@ return array( 'rsrc/css/phui/phui-badge.css' => 'f25c3476', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', 'rsrc/css/phui/phui-box.css' => '348bd872', - 'rsrc/css/phui/phui-button.css' => 'c096e4e9', + 'rsrc/css/phui/phui-button.css' => 'a64a8de6', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-crumbs-view.css' => '79d536e5', 'rsrc/css/phui/phui-document-pro.css' => '92d5b648', @@ -804,7 +804,7 @@ return array( 'phui-badge-view-css' => 'f25c3476', 'phui-big-info-view-css' => 'bd903741', 'phui-box-css' => '348bd872', - 'phui-button-css' => 'c096e4e9', + 'phui-button-css' => 'a64a8de6', 'phui-calendar-css' => 'ccabe893', 'phui-calendar-day-css' => 'd1cf6f93', 'phui-calendar-list-css' => 'c1c7f338', diff --git a/webroot/rsrc/css/phui/phui-button.css b/webroot/rsrc/css/phui/phui-button.css index 837c093872..0e7cfd3ecc 100644 --- a/webroot/rsrc/css/phui/phui-button.css +++ b/webroot/rsrc/css/phui/phui-button.css @@ -26,8 +26,9 @@ button, a.button, a.button:visited, input[type="submit"] { - background-color: {$blue}; - border: 1px solid {$blue}; + background-color: #2980b9; + border: 1px solid #2980b9; + background-image: linear-gradient(to bottom, #3498db, #2980b9); color: white; cursor: pointer; font-weight: bold; @@ -53,6 +54,7 @@ a.green, a.green:visited { background-color: {$green}; border-color: {$green}; + background-image: linear-gradient(to bottom, #23BB5B, #139543); } button.grey, @@ -101,7 +103,9 @@ a.phuix-dropdown-open { a.button:hover, button:hover { text-decoration: none; - background-color: {$sky}; + background-color: #2980b9; + background-image: linear-gradient(to bottom, #3498db, #1b6ba0); + border-color: #115988; transition: 0.1s; } @@ -114,13 +118,16 @@ button.grey:hover { a.button.green:hover, button.green:hover { + border-color: #127336; background-color: #0DAD48; + background-image: linear-gradient(to bottom, #23BB5B, #178841); transition: 0.1s; } a.button.simple:hover, button.simple:hover { background-color: {$blue}; + background-image: linear-gradient(to bottom, {$blue}, {$blue}); color: #fff; transition: 0.1s; } @@ -165,6 +172,7 @@ button.link { display: inline; border: none; background: transparent; + background-image: none; font-weight: normal; padding: 0; margin: 0; @@ -310,6 +318,7 @@ a.policy-control .caret { .phui-button-bar-borderless .button { border: 0; background-color: transparent; + background-image: none; padding-left: 10px; padding-right: 10px; } @@ -321,6 +330,7 @@ a.policy-control .caret { .phui-button-bar-borderless .button:hover { background-color: transparent; + background-image: none; border-radius: 3px; } @@ -357,10 +367,12 @@ a.policy-control .caret { .phui-button-bar .button.simple:hover { border-color: {$lightblueborder}; background-color: #fff; + background-image: none; color: {$sky}; } .phui-button-bar .button.simple:hover .phui-icon-view { border-color: {$lightblueborder}; color: {$sky}; + background-image: none; } From cf0957451ede30ccbf3899b3b8f5940c437fb897 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 1 Mar 2016 06:39:26 -0800 Subject: [PATCH 18/41] Slightly simplify the Harbormaster build log API Summary: Ref T5822. This prepares for inline compression and garbage collection of build logs. This reduces the API surface area and removes a log from the "wait" step that would just log a message every 15 seconds. (If this is actually useful, I think we find a better way to communicate it.) Test Plan: Ran a build, saw a log: {F1136691} Reviewers: chad Reviewed By: chad Maniphest Tasks: T5822 Differential Revision: https://secure.phabricator.com/D15371 --- ...WaitForPreviousBuildStepImplementation.php | 17 ----- .../storage/build/HarbormasterBuild.php | 17 ----- .../storage/build/HarbormasterBuildLog.php | 73 ++++++++++--------- .../storage/build/HarbormasterBuildTarget.php | 5 +- .../worker/HarbormasterTargetWorker.php | 9 +-- 5 files changed, 42 insertions(+), 79 deletions(-) diff --git a/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php index 7f63eda072..eadf4c94f5 100644 --- a/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php @@ -31,24 +31,7 @@ final class HarbormasterWaitForPreviousBuildStepImplementation // Block until all previous builds of the same build plan have // finished. $plan = $build->getBuildPlan(); - - $existing_logs = id(new HarbormasterBuildLogQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withBuildTargetPHIDs(array($build_target->getPHID())) - ->execute(); - - if ($existing_logs) { - $log = head($existing_logs); - } else { - $log = $build->createLog($build_target, 'waiting', 'blockers'); - } - $blockers = $this->getBlockers($object, $plan, $build); - if ($blockers) { - $log->start(); - $log->append(pht("Blocked by: %s\n", implode(',', $blockers))); - $log->finalize(); - } if ($blockers) { throw new PhabricatorWorkerYieldException(15); diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php index 5134d0efa0..4d9278c7bf 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -234,23 +234,6 @@ final class HarbormasterBuild extends HarbormasterDAO return ($this->getPlanAutoKey() !== null); } - public function createLog( - HarbormasterBuildTarget $build_target, - $log_source, - $log_type) { - - $log_source = id(new PhutilUTF8StringTruncator()) - ->setMaximumBytes(250) - ->truncateString($log_source); - - $log = HarbormasterBuildLog::initializeNewBuildLog($build_target) - ->setLogSource($log_source) - ->setLogType($log_type) - ->save(); - - return $log; - } - public function retrieveVariablesFromBuild() { $results = array( 'buildable.diff' => null, diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php index 85e7ae2411..8b7c5c85bb 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php @@ -1,6 +1,7 @@ start) { - $this->finalize($this->start); + if ($this->getLive()) { + $this->closeBuildLog(); } } @@ -34,6 +34,35 @@ final class HarbormasterBuildLog extends HarbormasterDAO ->setLive(0); } + public function openBuildLog() { + if ($this->getLive()) { + throw new Exception(pht('This build log is already open!')); + } + + return $this + ->setLive(1) + ->save(); + } + + public function closeBuildLog() { + if (!$this->getLive()) { + throw new Exception(pht('This build log is not open!')); + } + + // TODO: Encode the log contents in a gzipped format. + + $this->reload(); + + $start = $this->getDateCreated(); + $now = PhabricatorTime::getNow(); + + return $this + ->setDuration($now - $start) + ->setLive(0) + ->save(); + } + + protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, @@ -73,26 +102,13 @@ final class HarbormasterBuildLog extends HarbormasterDAO return pht('Build Log'); } - public function start() { - if ($this->getLive()) { - throw new Exception( - pht('Live logging has already started for this log.')); - } - - $this->setLive(1); - $this->save(); - - $this->start = PhabricatorTime::getNow(); - - return time(); - } - public function append($content) { if (!$this->getLive()) { - throw new Exception( - pht('Start logging before appending data to the log.')); + throw new PhutilInvalidStateException('openBuildLog'); } - if (strlen($content) === 0) { + + $content = (string)$content; + if (!strlen($content)) { return; } @@ -152,21 +168,6 @@ final class HarbormasterBuildLog extends HarbormasterDAO } } - public function finalize($start = 0) { - if (!$this->getLive()) { - // TODO: Clean up this API. - return; - } - - // TODO: Encode the log contents in a gzipped format. - $this->reload(); - if ($start > 0) { - $this->setDuration(time() - $start); - } - $this->setLive(0); - $this->save(); - } - public function getLogText() { // TODO: This won't cope very well if we're pulling like a 700MB // log file out of the DB. We should probably implement some sort diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php index 27655189e6..8b47bdfc21 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php @@ -256,9 +256,8 @@ final class HarbormasterBuildTarget extends HarbormasterDAO $log = HarbormasterBuildLog::initializeNewBuildLog($this) ->setLogSource($log_source) - ->setLogType($log_type); - - $log->start(); + ->setLogType($log_type) + ->openBuildLog(); return $log; } diff --git a/src/applications/harbormaster/worker/HarbormasterTargetWorker.php b/src/applications/harbormaster/worker/HarbormasterTargetWorker.php index ac3014dc29..fa3a01272b 100644 --- a/src/applications/harbormaster/worker/HarbormasterTargetWorker.php +++ b/src/applications/harbormaster/worker/HarbormasterTargetWorker.php @@ -90,13 +90,10 @@ final class HarbormasterTargetWorker extends HarbormasterWorker { $target->setDateCompleted(PhabricatorTime::getNow()); $target->save(); } catch (Exception $ex) { - phlog($ex); - try { - $log = $build->createLog($target, 'core', 'exception'); - $start = $log->start(); - $log->append((string)$ex); - $log->finalize($start); + $log = $target->newLog('core', 'exception') + ->append($ex) + ->closeBuildLog(); } catch (Exception $log_ex) { phlog($log_ex); } From 0daa9ad98758187ab1386ff133f26333e48adfb8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 1 Mar 2016 07:15:09 -0800 Subject: [PATCH 19/41] Use PhutilRope as a buffer in Harbormaster BuildLogs Summary: Ref T10457. Currently, every `append()` call necessarily generates queries, and these queries are slightly inefficient if a large block of data is appended to a partial log (they do about twice as much work as they technically need to). Use `PhutilRope` to buffer `append()` input so the logic is a little cleaner and we could add a rule like "flush logs no more than once every 500ms" later. Test Plan: - Ran a build, saw logs. - Set chunk size very small, ran build, saw logs, verified small logs in database. {F1137115} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10457 Differential Revision: https://secure.phabricator.com/D15375 --- .../storage/HarbormasterSchemaSpec.php | 2 +- .../storage/build/HarbormasterBuildLog.php | 130 ++++++++++-------- 2 files changed, 75 insertions(+), 57 deletions(-) diff --git a/src/applications/harbormaster/storage/HarbormasterSchemaSpec.php b/src/applications/harbormaster/storage/HarbormasterSchemaSpec.php index 60d942596e..b1ec953af9 100644 --- a/src/applications/harbormaster/storage/HarbormasterSchemaSpec.php +++ b/src/applications/harbormaster/storage/HarbormasterSchemaSpec.php @@ -24,7 +24,7 @@ final class HarbormasterSchemaSpec extends PhabricatorConfigSchemaSpec { $this->buildRawSchema( id(new HarbormasterBuildable())->getApplicationName(), - 'harbormaster_buildlogchunk', + HarbormasterBuildLog::CHUNK_TABLE, array( 'id' => 'auto', 'logID' => 'id', diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php index 8b7c5c85bb..50b1545c06 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php @@ -11,16 +11,23 @@ final class HarbormasterBuildLog protected $live; private $buildTarget = self::ATTACHABLE; + private $rope; + private $isOpen; const CHUNK_BYTE_LIMIT = 102400; + const CHUNK_TABLE = 'harbormaster_buildlogchunk'; /** * The log is encoded as plain text. */ const ENCODING_TEXT = 'text'; + public function __construct() { + $this->rope = new PhutilRope(); + } + public function __destruct() { - if ($this->getLive()) { + if ($this->isOpen) { $this->closeBuildLog(); } } @@ -35,17 +42,19 @@ final class HarbormasterBuildLog } public function openBuildLog() { - if ($this->getLive()) { + if ($this->isOpen) { throw new Exception(pht('This build log is already open!')); } + $this->isOpen = true; + return $this ->setLive(1) ->save(); } public function closeBuildLog() { - if (!$this->getLive()) { + if (!$this->isOpen) { throw new Exception(pht('This build log is not open!')); } @@ -108,63 +117,72 @@ final class HarbormasterBuildLog } $content = (string)$content; - if (!strlen($content)) { - return; - } - // If the length of the content is greater than the chunk size limit, - // then we can never fit the content in a single record. We need to - // split our content out and call append on it for as many parts as there - // are to the content. - if (strlen($content) > self::CHUNK_BYTE_LIMIT) { - $current = $content; - while (strlen($current) > self::CHUNK_BYTE_LIMIT) { - $part = substr($current, 0, self::CHUNK_BYTE_LIMIT); - $current = substr($current, self::CHUNK_BYTE_LIMIT); - $this->append($part); + $this->rope->append($content); + $this->flush(); + } + + private function flush() { + + // TODO: Maybe don't flush more than a couple of times per second. If a + // caller writes a single character over and over again, we'll currently + // spend a lot of time flushing that. + + $chunk_table = self::CHUNK_TABLE; + $chunk_limit = self::CHUNK_BYTE_LIMIT; + $rope = $this->rope; + + while (true) { + $length = $rope->getByteLength(); + if (!$length) { + break; } - $this->append($current); - return; - } - // Retrieve the size of last chunk from the DB for this log. If the - // chunk is over 500K, then we need to create a new log entry. - $conn = $this->establishConnection('w'); - $result = queryfx_all( - $conn, - 'SELECT id, size, encoding '. - 'FROM harbormaster_buildlogchunk '. - 'WHERE logID = %d '. - 'ORDER BY id DESC '. - 'LIMIT 1', - $this->getID()); - if (count($result) === 0 || - $result[0]['size'] + strlen($content) > self::CHUNK_BYTE_LIMIT || - $result[0]['encoding'] !== self::ENCODING_TEXT) { + $conn_w = $this->establishConnection('w'); + $tail = queryfx_one( + $conn_w, + 'SELECT id, size, encoding FROM %T WHERE logID = %d + ORDER BY id DESC LIMIT 1', + $chunk_table, + $this->getID()); - // We must insert a new chunk because the data we are appending - // won't fit into the existing one, or we don't have any existing - // chunk data. - queryfx( - $conn, - 'INSERT INTO harbormaster_buildlogchunk '. - '(logID, encoding, size, chunk) '. - 'VALUES '. - '(%d, %s, %d, %B)', - $this->getID(), - self::ENCODING_TEXT, - strlen($content), - $content); - } else { - // We have a resulting record that we can append our content onto. - queryfx( - $conn, - 'UPDATE harbormaster_buildlogchunk '. - 'SET chunk = CONCAT(chunk, %B), size = LENGTH(CONCAT(chunk, %B))'. - 'WHERE id = %d', - $content, - $content, - $result[0]['id']); + $can_append = + ($tail) && + ($tail['encoding'] == self::ENCODING_TEXT) && + ($tail['size'] < $chunk_limit); + if ($can_append) { + $append_id = $tail['id']; + $prefix_size = $tail['size']; + } else { + $append_id = null; + $prefix_size = 0; + } + + $data_limit = ($chunk_limit - $prefix_size); + $append_data = $rope->getPrefixBytes($data_limit); + $data_size = strlen($append_data); + + if ($append_id) { + queryfx( + $conn_w, + 'UPDATE %T SET chunk = CONCAT(chunk, %B), size = %d WHERE id = %d', + $chunk_table, + $append_data, + $prefix_size + $data_size, + $append_id); + } else { + queryfx( + $conn_w, + 'INSERT INTO %T (logID, encoding, size, chunk) + VALUES (%d, %s, %d, %B)', + $chunk_table, + $this->getID(), + self::ENCODING_TEXT, + $data_size, + $append_data); + } + + $rope->removeBytesFromHead(strlen($append_data)); } } From e174cac1b47616904fa4f8b58893aedae83eb21a Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 1 Mar 2016 12:10:27 -0800 Subject: [PATCH 20/41] Give HarbormasterBuildLogChunk a real table Summary: Ref T10457. Currently, this table is an ad-hoc table, but can easily be turned into a normal table. This will make iterating over log chunks to compress and archive them easier. Test Plan: Viewed logs, ran `bin/storage adjust` with no issues. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10457 Differential Revision: https://secure.phabricator.com/D15376 --- src/__phutil_library_map__.php | 2 ++ .../storage/HarbormasterSchemaSpec.php | 25 --------------- .../storage/build/HarbormasterBuildLog.php | 6 ++-- .../build/HarbormasterBuildLogChunk.php | 32 +++++++++++++++++++ 4 files changed, 37 insertions(+), 28 deletions(-) create mode 100644 src/applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 740021f3c5..b16ece7018 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1044,6 +1044,7 @@ phutil_register_library_map(array( 'HarbormasterBuildGraph' => 'applications/harbormaster/engine/HarbormasterBuildGraph.php', 'HarbormasterBuildLintMessage' => 'applications/harbormaster/storage/build/HarbormasterBuildLintMessage.php', 'HarbormasterBuildLog' => 'applications/harbormaster/storage/build/HarbormasterBuildLog.php', + 'HarbormasterBuildLogChunk' => 'applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php', 'HarbormasterBuildLogPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildLogPHIDType.php', 'HarbormasterBuildLogQuery' => 'applications/harbormaster/query/HarbormasterBuildLogQuery.php', 'HarbormasterBuildMessage' => 'applications/harbormaster/storage/HarbormasterBuildMessage.php', @@ -5191,6 +5192,7 @@ phutil_register_library_map(array( 'HarbormasterDAO', 'PhabricatorPolicyInterface', ), + 'HarbormasterBuildLogChunk' => 'HarbormasterDAO', 'HarbormasterBuildLogPHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildMessage' => array( diff --git a/src/applications/harbormaster/storage/HarbormasterSchemaSpec.php b/src/applications/harbormaster/storage/HarbormasterSchemaSpec.php index b1ec953af9..b310db14de 100644 --- a/src/applications/harbormaster/storage/HarbormasterSchemaSpec.php +++ b/src/applications/harbormaster/storage/HarbormasterSchemaSpec.php @@ -21,31 +21,6 @@ final class HarbormasterSchemaSpec extends PhabricatorConfigSchemaSpec { ), )); - - $this->buildRawSchema( - id(new HarbormasterBuildable())->getApplicationName(), - HarbormasterBuildLog::CHUNK_TABLE, - array( - 'id' => 'auto', - 'logID' => 'id', - 'encoding' => 'text32', - - // T6203/NULLABILITY - // Both the type and nullability of this column are crazily wrong. - 'size' => 'uint32?', - - 'chunk' => 'bytes', - ), - array( - 'PRIMARY' => array( - 'columns' => array('id'), - 'unique' => true, - ), - 'key_log' => array( - 'columns' => array('logID'), - ), - )); - } } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php index 50b1545c06..d5eaac8890 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php @@ -15,7 +15,6 @@ final class HarbormasterBuildLog private $isOpen; const CHUNK_BYTE_LIMIT = 102400; - const CHUNK_TABLE = 'harbormaster_buildlogchunk'; /** * The log is encoded as plain text. @@ -128,7 +127,7 @@ final class HarbormasterBuildLog // caller writes a single character over and over again, we'll currently // spend a lot of time flushing that. - $chunk_table = self::CHUNK_TABLE; + $chunk_table = id(new HarbormasterBuildLogChunk())->getTableName(); $chunk_limit = self::CHUNK_BYTE_LIMIT; $rope = $this->rope; @@ -198,9 +197,10 @@ final class HarbormasterBuildLog $result = queryfx_all( $conn, 'SELECT chunk '. - 'FROM harbormaster_buildlogchunk '. + 'FROM %T '. 'WHERE logID = %d '. 'ORDER BY id ASC', + id(new HarbormasterBuildLogChunk())->getTableName(), $this->getID()); $content = ''; diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php new file mode 100644 index 0000000000..aa1c0e1185 --- /dev/null +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php @@ -0,0 +1,32 @@ + false, + self::CONFIG_COLUMN_SCHEMA => array( + 'logID' => 'id', + 'encoding' => 'text32', + + // T6203/NULLABILITY + // Both the type and nullability of this column are crazily wrong. + 'size' => 'uint32?', + + 'chunk' => 'bytes', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_log' => array( + 'columns' => array('logID'), + ), + ), + ) + parent::getConfiguration(); + } + +} From 6514237c0e014b0d1768999673a95c38fd8ccf2e Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 1 Mar 2016 12:22:14 -0800 Subject: [PATCH 21/41] Implement an iterator for build log chunks Summary: Ref T5822. This will make it easier to compress and archive chunks without needing to hold them in memory. Test Plan: Ran a build, looked at some logs. Reviewers: chad Reviewed By: chad Maniphest Tasks: T5822 Differential Revision: https://secure.phabricator.com/D15378 --- src/__phutil_library_map__.php | 2 + .../storage/build/HarbormasterBuildLog.php | 41 +++++++------------ .../build/HarbormasterBuildLogChunk.php | 23 +++++++++++ .../HarbormasterBuildLogChunkIterator.php | 35 ++++++++++++++++ 4 files changed, 75 insertions(+), 26 deletions(-) create mode 100644 src/applications/harbormaster/storage/build/HarbormasterBuildLogChunkIterator.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b16ece7018..103dc46c51 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1045,6 +1045,7 @@ phutil_register_library_map(array( 'HarbormasterBuildLintMessage' => 'applications/harbormaster/storage/build/HarbormasterBuildLintMessage.php', 'HarbormasterBuildLog' => 'applications/harbormaster/storage/build/HarbormasterBuildLog.php', 'HarbormasterBuildLogChunk' => 'applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php', + 'HarbormasterBuildLogChunkIterator' => 'applications/harbormaster/storage/build/HarbormasterBuildLogChunkIterator.php', 'HarbormasterBuildLogPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildLogPHIDType.php', 'HarbormasterBuildLogQuery' => 'applications/harbormaster/query/HarbormasterBuildLogQuery.php', 'HarbormasterBuildMessage' => 'applications/harbormaster/storage/HarbormasterBuildMessage.php', @@ -5193,6 +5194,7 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', ), 'HarbormasterBuildLogChunk' => 'HarbormasterDAO', + 'HarbormasterBuildLogChunkIterator' => 'PhutilBufferedIterator', 'HarbormasterBuildLogPHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildMessage' => array( diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php index d5eaac8890..da77701d2c 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php @@ -16,11 +16,6 @@ final class HarbormasterBuildLog const CHUNK_BYTE_LIMIT = 102400; - /** - * The log is encoded as plain text. - */ - const ENCODING_TEXT = 'text'; - public function __construct() { $this->rope = new PhutilRope(); } @@ -129,6 +124,8 @@ final class HarbormasterBuildLog $chunk_table = id(new HarbormasterBuildLogChunk())->getTableName(); $chunk_limit = self::CHUNK_BYTE_LIMIT; + $encoding_text = HarbormasterBuildLogChunk::CHUNK_ENCODING_TEXT; + $rope = $this->rope; while (true) { @@ -147,7 +144,7 @@ final class HarbormasterBuildLog $can_append = ($tail) && - ($tail['encoding'] == self::ENCODING_TEXT) && + ($tail['encoding'] == $encoding_text) && ($tail['size'] < $chunk_limit); if ($can_append) { $append_id = $tail['id']; @@ -176,7 +173,7 @@ final class HarbormasterBuildLog VALUES (%d, %s, %d, %B)', $chunk_table, $this->getID(), - self::ENCODING_TEXT, + $encoding_text, $data_size, $append_data); } @@ -185,29 +182,21 @@ final class HarbormasterBuildLog } } + public function newChunkIterator() { + return new HarbormasterBuildLogChunkIterator($this); + } + public function getLogText() { - // TODO: This won't cope very well if we're pulling like a 700MB - // log file out of the DB. We should probably implement some sort - // of optional limit parameter so that when we're rendering out only - // 25 lines in the UI, we don't wastefully read in the whole log. + // TODO: Remove this method since it won't scale for big logs. - // We have to read our content out of the database and stitch all of - // the log data back together. - $conn = $this->establishConnection('r'); - $result = queryfx_all( - $conn, - 'SELECT chunk '. - 'FROM %T '. - 'WHERE logID = %d '. - 'ORDER BY id ASC', - id(new HarbormasterBuildLogChunk())->getTableName(), - $this->getID()); + $all_chunks = $this->newChunkIterator(); - $content = ''; - foreach ($result as $row) { - $content .= $row['chunk']; + $full_text = array(); + foreach ($all_chunks as $chunk) { + $full_text[] = $chunk->getChunkDisplayText(); } - return $content; + + return implode('', $full_text); } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php index aa1c0e1185..a7438b3ac4 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php @@ -8,6 +8,12 @@ final class HarbormasterBuildLogChunk protected $size; protected $chunk; + + /** + * The log is encoded as plain text. + */ + const CHUNK_ENCODING_TEXT = 'text'; + protected function getConfiguration() { return array( self::CONFIG_TIMESTAMPS => false, @@ -29,4 +35,21 @@ final class HarbormasterBuildLogChunk ) + parent::getConfiguration(); } + public function getChunkDisplayText() { + $data = $this->getChunk(); + $encoding = $this->getEncoding(); + + switch ($encoding) { + case self::CHUNK_ENCODING_TEXT: + // Do nothing, data is already plaintext. + break; + default: + throw new Exception( + pht('Unknown log chunk encoding ("%s")!', $encoding)); + } + + return $data; + } + + } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunkIterator.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunkIterator.php new file mode 100644 index 0000000000..f5e86e0062 --- /dev/null +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunkIterator.php @@ -0,0 +1,35 @@ +log = $log; + } + + protected function didRewind() { + $this->cursor = 0; + } + + public function key() { + return $this->current()->getID(); + } + + protected function loadPage() { + $results = id(new HarbormasterBuildLogChunk())->loadAllWhere( + 'logID = %d AND id > %d ORDER BY id ASC LIMIT %d', + $this->log->getID(), + $this->cursor, + $this->getPageSize()); + + if ($results) { + $this->cursor = last($results)->getID(); + } + + return $results; + } + +} From 078bf59f59b08b4b5170ab20e7e4f5cf3c48a378 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 1 Mar 2016 13:53:13 -0800 Subject: [PATCH 22/41] Compress Harbormaster build logs inline Summary: Ref T5822. - After a log is closed, compress it if possible. - Provide `bin/harbormaster archive-logs` to make it easier to change the storage format of logs. Test Plan: - Ran `bin/harbormaster archive-logs` on a bunch of logs, compressing and decompressing them without issues (same hashes, same decompressed size across multiple iterations). - Ran new builds, verified logs were compressed after they closed. Reviewers: chad Reviewed By: chad Maniphest Tasks: T5822 Differential Revision: https://secure.phabricator.com/D15380 --- src/__phutil_library_map__.php | 2 + ...bormasterManagementArchiveLogsWorkflow.php | 150 ++++++++++++++++++ .../storage/build/HarbormasterBuildLog.php | 126 ++++++++++++--- .../build/HarbormasterBuildLogChunk.php | 14 +- .../HarbormasterBuildLogChunkIterator.php | 20 ++- 5 files changed, 280 insertions(+), 32 deletions(-) create mode 100644 src/applications/harbormaster/management/HarbormasterManagementArchiveLogsWorkflow.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 103dc46c51..8d634c1c86 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1114,6 +1114,7 @@ phutil_register_library_map(array( 'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php', 'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php', 'HarbormasterLintPropertyView' => 'applications/harbormaster/view/HarbormasterLintPropertyView.php', + 'HarbormasterManagementArchiveLogsWorkflow' => 'applications/harbormaster/management/HarbormasterManagementArchiveLogsWorkflow.php', 'HarbormasterManagementBuildWorkflow' => 'applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php', 'HarbormasterManagementUpdateWorkflow' => 'applications/harbormaster/management/HarbormasterManagementUpdateWorkflow.php', 'HarbormasterManagementWorkflow' => 'applications/harbormaster/management/HarbormasterManagementWorkflow.php', @@ -5287,6 +5288,7 @@ phutil_register_library_map(array( 'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 'HarbormasterLintMessagesController' => 'HarbormasterController', 'HarbormasterLintPropertyView' => 'AphrontView', + 'HarbormasterManagementArchiveLogsWorkflow' => 'HarbormasterManagementWorkflow', 'HarbormasterManagementBuildWorkflow' => 'HarbormasterManagementWorkflow', 'HarbormasterManagementUpdateWorkflow' => 'HarbormasterManagementWorkflow', 'HarbormasterManagementWorkflow' => 'PhabricatorManagementWorkflow', diff --git a/src/applications/harbormaster/management/HarbormasterManagementArchiveLogsWorkflow.php b/src/applications/harbormaster/management/HarbormasterManagementArchiveLogsWorkflow.php new file mode 100644 index 0000000000..f6808b1fd5 --- /dev/null +++ b/src/applications/harbormaster/management/HarbormasterManagementArchiveLogsWorkflow.php @@ -0,0 +1,150 @@ +setName('archive-logs') + ->setExamples('**archive-logs** [__options__] --mode __mode__') + ->setSynopsis(pht('Compress, decompress, store or destroy build logs.')) + ->setArguments( + array( + array( + 'name' => 'mode', + 'param' => 'mode', + 'help' => pht( + 'Use "plain" to remove encoding, or "compress" to compress '. + 'logs.'), + ), + array( + 'name' => 'details', + 'help' => pht( + 'Show more details about operations as they are performed. '. + 'Slow! But also very reassuring!'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $viewer = $this->getViewer(); + + $mode = $args->getArg('mode'); + if (!$mode) { + throw new PhutilArgumentUsageException( + pht('Choose an archival mode with --mode.')); + } + + $valid_modes = array( + 'plain', + 'compress', + ); + + $valid_modes = array_fuse($valid_modes); + if (empty($valid_modes[$mode])) { + throw new PhutilArgumentUsageException( + pht( + 'Unknown mode "%s". Valid modes are: %s.', + $mode, + implode(', ', $valid_modes))); + } + + $log_table = new HarbormasterBuildLog(); + $logs = new LiskMigrationIterator($log_table); + + $show_details = $args->getArg('details'); + + if ($show_details) { + $total_old = 0; + $total_new = 0; + } + + foreach ($logs as $log) { + echo tsprintf( + "%s\n", + pht('Processing Harbormaster build log #%d...', $log->getID())); + + if ($show_details) { + $old_stats = $this->computeDetails($log); + } + + switch ($mode) { + case 'plain': + $log->decompressLog(); + break; + case 'compress': + $log->compressLog(); + break; + } + + if ($show_details) { + $new_stats = $this->computeDetails($log); + $this->printStats($old_stats, $new_stats); + + $total_old += $old_stats['bytes']; + $total_new += $new_stats['bytes']; + } + } + + if ($show_details) { + echo tsprintf( + "%s\n", + pht( + 'Done. Total byte size of affected logs: %s -> %s.', + new PhutilNumber($total_old), + new PhutilNumber($total_new))); + } + + return 0; + } + + private function computeDetails(HarbormasterBuildLog $log) { + $bytes = 0; + $chunks = 0; + $hash = hash_init('sha1'); + + foreach ($log->newChunkIterator() as $chunk) { + $bytes += strlen($chunk->getChunk()); + $chunks++; + hash_update($hash, $chunk->getChunkDisplayText()); + } + + return array( + 'bytes' => $bytes, + 'chunks' => $chunks, + 'hash' => hash_final($hash), + ); + } + + private function printStats(array $old_stats, array $new_stats) { + echo tsprintf( + " %s\n", + pht( + '%s: %s -> %s', + pht('Stored Bytes'), + new PhutilNumber($old_stats['bytes']), + new PhutilNumber($new_stats['bytes']))); + + echo tsprintf( + " %s\n", + pht( + '%s: %s -> %s', + pht('Stored Chunks'), + new PhutilNumber($old_stats['chunks']), + new PhutilNumber($new_stats['chunks']))); + + echo tsprintf( + " %s\n", + pht( + '%s: %s -> %s', + pht('Data Hash'), + $old_stats['hash'], + $new_stats['hash'])); + + if ($old_stats['hash'] !== $new_stats['hash']) { + throw new Exception( + pht('Log data hashes differ! Something is tragically wrong!')); + } + } + +} diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php index da77701d2c..40090d0ac3 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php @@ -52,9 +52,9 @@ final class HarbormasterBuildLog throw new Exception(pht('This build log is not open!')); } - // TODO: Encode the log contents in a gzipped format. - - $this->reload(); + if ($this->canCompressLog()) { + $this->compressLog(); + } $start = $this->getDateCreated(); $now = PhabricatorTime::getNow(); @@ -135,20 +135,15 @@ final class HarbormasterBuildLog } $conn_w = $this->establishConnection('w'); - $tail = queryfx_one( - $conn_w, - 'SELECT id, size, encoding FROM %T WHERE logID = %d - ORDER BY id DESC LIMIT 1', - $chunk_table, - $this->getID()); + $last = $this->loadLastChunkInfo(); $can_append = - ($tail) && - ($tail['encoding'] == $encoding_text) && - ($tail['size'] < $chunk_limit); + ($last) && + ($last['encoding'] == $encoding_text) && + ($last['size'] < $chunk_limit); if ($can_append) { - $append_id = $tail['id']; - $prefix_size = $tail['size']; + $append_id = $last['id']; + $prefix_size = $last['size']; } else { $append_id = null; $prefix_size = 0; @@ -167,23 +162,28 @@ final class HarbormasterBuildLog $prefix_size + $data_size, $append_id); } else { - queryfx( - $conn_w, - 'INSERT INTO %T (logID, encoding, size, chunk) - VALUES (%d, %s, %d, %B)', - $chunk_table, - $this->getID(), - $encoding_text, - $data_size, - $append_data); + $this->writeChunk($encoding_text, $data_size, $append_data); } - $rope->removeBytesFromHead(strlen($append_data)); + $rope->removeBytesFromHead($data_size); } } public function newChunkIterator() { - return new HarbormasterBuildLogChunkIterator($this); + return id(new HarbormasterBuildLogChunkIterator($this)) + ->setPageSize(32); + } + + private function loadLastChunkInfo() { + $chunk_table = new HarbormasterBuildLogChunk(); + $conn_w = $chunk_table->establishConnection('w'); + + return queryfx_one( + $conn_w, + 'SELECT id, size, encoding FROM %T WHERE logID = %d + ORDER BY id DESC LIMIT 1', + $chunk_table->getTableName(), + $this->getID()); } public function getLogText() { @@ -199,6 +199,82 @@ final class HarbormasterBuildLog return implode('', $full_text); } + private function canCompressLog() { + return function_exists('gzdeflate'); + } + + public function compressLog() { + $this->processLog(HarbormasterBuildLogChunk::CHUNK_ENCODING_GZIP); + } + + public function decompressLog() { + $this->processLog(HarbormasterBuildLogChunk::CHUNK_ENCODING_TEXT); + } + + private function processLog($mode) { + $chunks = $this->newChunkIterator(); + + // NOTE: Because we're going to insert new chunks, we need to stop the + // iterator once it hits the final chunk which currently exists. Otherwise, + // it may start consuming chunks we just wrote and run forever. + $last = $this->loadLastChunkInfo(); + if ($last) { + $chunks->setRange(null, $last['id']); + } + + $byte_limit = self::CHUNK_BYTE_LIMIT; + $rope = new PhutilRope(); + + $this->openTransaction(); + + foreach ($chunks as $chunk) { + $rope->append($chunk->getChunkDisplayText()); + $chunk->delete(); + + while ($rope->getByteLength() > $byte_limit) { + $this->writeEncodedChunk($rope, $byte_limit, $mode); + } + } + + while ($rope->getByteLength()) { + $this->writeEncodedChunk($rope, $byte_limit, $mode); + } + + $this->saveTransaction(); + } + + private function writeEncodedChunk(PhutilRope $rope, $length, $mode) { + $data = $rope->getPrefixBytes($length); + $size = strlen($data); + + switch ($mode) { + case HarbormasterBuildLogChunk::CHUNK_ENCODING_TEXT: + // Do nothing. + break; + case HarbormasterBuildLogChunk::CHUNK_ENCODING_GZIP: + $data = gzdeflate($data); + if ($data === false) { + throw new Exception(pht('Failed to gzdeflate() log data!')); + } + break; + default: + throw new Exception(pht('Unknown chunk encoding "%s"!', $mode)); + } + + $this->writeChunk($mode, $size, $data); + + $rope->removeBytesFromHead($size); + } + + private function writeChunk($encoding, $raw_size, $data) { + return id(new HarbormasterBuildLogChunk()) + ->setLogID($this->getID()) + ->setEncoding($encoding) + ->setSize($raw_size) + ->setChunk($data) + ->save(); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php index a7438b3ac4..e69bad4caf 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php @@ -8,15 +8,15 @@ final class HarbormasterBuildLogChunk protected $size; protected $chunk; - - /** - * The log is encoded as plain text. - */ const CHUNK_ENCODING_TEXT = 'text'; + const CHUNK_ENCODING_GZIP = 'gzip'; protected function getConfiguration() { return array( self::CONFIG_TIMESTAMPS => false, + self::CONFIG_BINARY => array( + 'chunk' => true, + ), self::CONFIG_COLUMN_SCHEMA => array( 'logID' => 'id', 'encoding' => 'text32', @@ -43,6 +43,12 @@ final class HarbormasterBuildLogChunk case self::CHUNK_ENCODING_TEXT: // Do nothing, data is already plaintext. break; + case self::CHUNK_ENCODING_GZIP: + $data = gzinflate($data); + if ($data === false) { + throw new Exception(pht('Unable to inflate log chunk!')); + } + break; default: throw new Exception( pht('Unknown log chunk encoding ("%s")!', $encoding)); diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunkIterator.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunkIterator.php index f5e86e0062..754248cc67 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunkIterator.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunkIterator.php @@ -6,27 +6,41 @@ final class HarbormasterBuildLogChunkIterator private $log; private $cursor; + private $min = 0; + private $max = PHP_INT_MAX; + public function __construct(HarbormasterBuildLog $log) { $this->log = $log; } protected function didRewind() { - $this->cursor = 0; + $this->cursor = $this->min; } public function key() { return $this->current()->getID(); } + public function setRange($min, $max) { + $this->min = (int)$min; + $this->max = (int)$max; + return $this; + } + protected function loadPage() { + if ($this->cursor > $this->max) { + return array(); + } + $results = id(new HarbormasterBuildLogChunk())->loadAllWhere( - 'logID = %d AND id > %d ORDER BY id ASC LIMIT %d', + 'logID = %d AND id >= %d AND id <= %d ORDER BY id ASC LIMIT %d', $this->log->getID(), $this->cursor, + $this->max, $this->getPageSize()); if ($results) { - $this->cursor = last($results)->getID(); + $this->cursor = last($results)->getID() + 1; } return $results; From 2c43cccddf36b5259dd7ec355354573f1ae5d341 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 2 Mar 2016 08:45:53 -0800 Subject: [PATCH 23/41] Update Almanac for PHUITwoColumnView Summary: Updates Almanac to the new layout, adds some header icons for interest. Test Plan: Click on all the different almanac pages. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15381 --- resources/celerity/map.php | 18 +++--- .../almanac/controller/AlmanacController.php | 7 +- .../AlmanacDeviceViewController.php | 50 ++++++++------- .../AlmanacNamespaceViewController.php | 31 +++++---- .../AlmanacNetworkViewController.php | 27 +++++--- .../AlmanacServiceViewController.php | 64 +++++++++++++------ src/view/phui/PHUIHeaderView.php | 6 +- webroot/rsrc/css/aphront/table-view.css | 3 +- webroot/rsrc/css/phui/phui-box.css | 5 ++ webroot/rsrc/css/phui/phui-header-view.css | 5 ++ .../rsrc/css/phui/phui-two-column-view.css | 17 ++++- 11 files changed, 150 insertions(+), 83 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index b9cde6ad51..2f74d82db8 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '80c32191', + 'core.pkg.css' => 'd5d34907', 'core.pkg.js' => '7d8faf57', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', @@ -25,7 +25,7 @@ return array( 'rsrc/css/aphront/notification.css' => '7f684b62', 'rsrc/css/aphront/panel-view.css' => '8427b78d', 'rsrc/css/aphront/phabricator-nav-view.css' => 'ac79a758', - 'rsrc/css/aphront/table-view.css' => 'aba95954', + 'rsrc/css/aphront/table-view.css' => '036b6cdc', 'rsrc/css/aphront/tokenizer.css' => '056da01b', 'rsrc/css/aphront/tooltip.css' => '1a07aea8', 'rsrc/css/aphront/typeahead-browse.css' => 'd8581d2c', @@ -123,7 +123,7 @@ return array( 'rsrc/css/phui/phui-action-panel.css' => '91c7b835', 'rsrc/css/phui/phui-badge.css' => 'f25c3476', 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', - 'rsrc/css/phui/phui-box.css' => '348bd872', + 'rsrc/css/phui/phui-box.css' => 'c9e01148', 'rsrc/css/phui/phui-button.css' => 'a64a8de6', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-crumbs-view.css' => '79d536e5', @@ -135,7 +135,7 @@ return array( 'rsrc/css/phui/phui-form-view.css' => '4a1a0f5e', 'rsrc/css/phui/phui-form.css' => 'aac1d51d', 'rsrc/css/phui/phui-head-thing.css' => '11731da0', - 'rsrc/css/phui/phui-header-view.css' => '6152c91b', + 'rsrc/css/phui/phui-header-view.css' => 'fc4acf14', 'rsrc/css/phui/phui-hovercard.css' => 'de1a2119', 'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad', 'rsrc/css/phui/phui-icon.css' => '3f33ab57', @@ -155,7 +155,7 @@ return array( 'rsrc/css/phui/phui-status.css' => '888cedb8', 'rsrc/css/phui/phui-tag-view.css' => '9d5d4400', 'rsrc/css/phui/phui-timeline-view.css' => '2efceff8', - 'rsrc/css/phui/phui-two-column-view.css' => '7c5d0741', + 'rsrc/css/phui/phui-two-column-view.css' => 'cc0b8a09', 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7', 'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647', 'rsrc/css/phui/workboards/phui-workcard.css' => '3646fb96', @@ -524,7 +524,7 @@ return array( 'aphront-list-filter-view-css' => '5d6f0526', 'aphront-multi-column-view-css' => 'fd18389d', 'aphront-panel-view-css' => '8427b78d', - 'aphront-table-view-css' => 'aba95954', + 'aphront-table-view-css' => '036b6cdc', 'aphront-tokenizer-control-css' => '056da01b', 'aphront-tooltip-css' => '1a07aea8', 'aphront-typeahead-control-css' => 'd4f16145', @@ -803,7 +803,7 @@ return array( 'phui-action-panel-css' => '91c7b835', 'phui-badge-view-css' => 'f25c3476', 'phui-big-info-view-css' => 'bd903741', - 'phui-box-css' => '348bd872', + 'phui-box-css' => 'c9e01148', 'phui-button-css' => 'a64a8de6', 'phui-calendar-css' => 'ccabe893', 'phui-calendar-day-css' => 'd1cf6f93', @@ -820,7 +820,7 @@ return array( 'phui-form-css' => 'aac1d51d', 'phui-form-view-css' => '4a1a0f5e', 'phui-head-thing-view-css' => '11731da0', - 'phui-header-view-css' => '6152c91b', + 'phui-header-view-css' => 'fc4acf14', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'de1a2119', 'phui-icon-set-selector-css' => '1ab67aad', @@ -843,7 +843,7 @@ return array( 'phui-tag-view-css' => '9d5d4400', 'phui-theme-css' => '027ba77e', 'phui-timeline-view-css' => '2efceff8', - 'phui-two-column-view-css' => '7c5d0741', + 'phui-two-column-view-css' => 'cc0b8a09', 'phui-workboard-color-css' => 'ac6fe6a7', 'phui-workboard-view-css' => 'e6d89647', 'phui-workcard-view-css' => '3646fb96', diff --git a/src/applications/almanac/controller/AlmanacController.php b/src/applications/almanac/controller/AlmanacController.php index 3b354b0803..c76fdf11b5 100644 --- a/src/applications/almanac/controller/AlmanacController.php +++ b/src/applications/almanac/controller/AlmanacController.php @@ -158,16 +158,16 @@ abstract class AlmanacController ->setIcon('fa-plus'); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Properties')) + ->setHeader(pht('PROPERTIES')) ->addActionLink($add_button); return id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); } protected function addClusterMessage( - PHUIObjectBoxView $box, $positive, $negative) { @@ -194,14 +194,13 @@ abstract class AlmanacController $icon = id(new PHUIIconView()) ->setIcon('fa-sitemap'); - $error_view = id(new PHUIInfoView()) + return id(new PHUIInfoView()) ->setSeverity($severity) ->setErrors( array( array($icon, ' ', $message, ' ', $doc_link), )); - $box->setInfoView($error_view); } protected function getPropertyDeleteURI($object) { diff --git a/src/applications/almanac/controller/AlmanacDeviceViewController.php b/src/applications/almanac/controller/AlmanacDeviceViewController.php index 4836198763..f6bf697e31 100644 --- a/src/applications/almanac/controller/AlmanacDeviceViewController.php +++ b/src/applications/almanac/controller/AlmanacDeviceViewController.php @@ -23,22 +23,18 @@ final class AlmanacDeviceViewController $title = pht('Device %s', $device->getName()); - $property_list = $this->buildPropertyList($device); - $action_list = $this->buildActionList($device); - $property_list->setActionList($action_list); + $properties = $this->buildPropertyList($device); + $actions = $this->buildActionList($device); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($device->getName()) - ->setPolicyObject($device); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($property_list); + ->setPolicyObject($device) + ->setHeaderIcon('fa-server'); + $issue = null; if ($device->isClusterDevice()) { - $this->addClusterMessage( - $box, + $issue = $this->addClusterMessage( pht('This device is bound to a cluster service.'), pht( 'This device is bound to a cluster service. You do not have '. @@ -50,24 +46,33 @@ final class AlmanacDeviceViewController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($device->getName()); + $crumbs->setBorder(true); $timeline = $this->buildTransactionTimeline( $device, new AlmanacDeviceTransactionQuery()); $timeline->setShouldTerminate(true); - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild( - array( - $box, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn(array( + $issue, $interfaces, $this->buildAlmanacPropertiesTable($device), $this->buildSSHKeysTable($device), $this->buildServicesTable($device), $timeline, - )); + )) + ->setPropertyList($properties) + ->setActionList($actions); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, + )); } private function buildPropertyList(AlmanacDevice $device) { @@ -123,7 +128,7 @@ final class AlmanacDeviceViewController ->setCanEdit($can_edit); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Device Interfaces')) + ->setHeader(pht('DEVICE INTERFACES')) ->addActionLink( id(new PHUIButtonView()) ->setTag('a') @@ -135,6 +140,7 @@ final class AlmanacDeviceViewController return id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); } @@ -172,7 +178,7 @@ final class AlmanacDeviceViewController $upload_uri = '/auth/sshkey/upload/?objectPHID='.$device_phid; $header = id(new PHUIHeaderView()) - ->setHeader(pht('SSH Public Keys')) + ->setHeader(pht('SSH PUBLIC KEYS')) ->addActionLink( id(new PHUIButtonView()) ->setTag('a') @@ -196,9 +202,8 @@ final class AlmanacDeviceViewController return id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); - - } private function buildServicesTable(AlmanacDevice $device) { @@ -244,7 +249,8 @@ final class AlmanacDeviceViewController )); return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Bound Services')) + ->setHeaderText(pht('BOUND SERVICES')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); } diff --git a/src/applications/almanac/controller/AlmanacNamespaceViewController.php b/src/applications/almanac/controller/AlmanacNamespaceViewController.php index 4f140683a2..b999f219fc 100644 --- a/src/applications/almanac/controller/AlmanacNamespaceViewController.php +++ b/src/applications/almanac/controller/AlmanacNamespaceViewController.php @@ -21,42 +21,49 @@ final class AlmanacNamespaceViewController $title = pht('Namespace %s', $namespace->getName()); - $property_list = $this->buildPropertyList($namespace); - $action_list = $this->buildActionList($namespace); - $property_list->setActionList($action_list); + $properties = $this->buildPropertyList($namespace); + $actions = $this->buildActionList($namespace); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($namespace->getName()) - ->setPolicyObject($namespace); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($property_list); + ->setPolicyObject($namespace) + ->setHeaderIcon('fa-asterisk'); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($namespace->getName()); + $crumbs->setBorder(true); $timeline = $this->buildTransactionTimeline( $namespace, new AlmanacNamespaceTransactionQuery()); $timeline->setShouldTerminate(true); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn(array( + $timeline, + )) + ->setPropertyList($properties) + ->setActionList($actions); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $box, - $timeline, - )); + $view, + )); } private function buildPropertyList(AlmanacNamespace $namespace) { $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) - ->setUser($viewer); + ->setUser($viewer) + ->setObject($namespace); + + $properties->invokeWillRenderEvent(); return $properties; } diff --git a/src/applications/almanac/controller/AlmanacNetworkViewController.php b/src/applications/almanac/controller/AlmanacNetworkViewController.php index 70bd0cb87e..11e6eaf799 100644 --- a/src/applications/almanac/controller/AlmanacNetworkViewController.php +++ b/src/applications/almanac/controller/AlmanacNetworkViewController.php @@ -21,34 +21,38 @@ final class AlmanacNetworkViewController $title = pht('Network %s', $network->getName()); - $property_list = $this->buildPropertyList($network); - $action_list = $this->buildActionList($network); - $property_list->setActionList($action_list); + $properties = $this->buildPropertyList($network); + $actions = $this->buildActionList($network); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($network->getName()) + ->setHeaderIcon('fa-globe') ->setPolicyObject($network); - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($property_list); - $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($network->getName()); + $crumbs->setBorder(true); $timeline = $this->buildTransactionTimeline( $network, new AlmanacNetworkTransactionQuery()); $timeline->setShouldTerminate(true); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn(array( + $timeline, + )) + ->setPropertyList($properties) + ->setActionList($actions); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $box, - $timeline, + $view, )); } @@ -56,7 +60,10 @@ final class AlmanacNetworkViewController $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) - ->setUser($viewer); + ->setUser($viewer) + ->setObject($network); + + $properties->invokeWillRenderEvent(); return $properties; } diff --git a/src/applications/almanac/controller/AlmanacServiceViewController.php b/src/applications/almanac/controller/AlmanacServiceViewController.php index 68defec4b9..0b59a47442 100644 --- a/src/applications/almanac/controller/AlmanacServiceViewController.php +++ b/src/applications/almanac/controller/AlmanacServiceViewController.php @@ -23,22 +23,19 @@ final class AlmanacServiceViewController $title = pht('Service %s', $service->getName()); - $property_list = $this->buildPropertyList($service); - $action_list = $this->buildActionList($service); - $property_list->setActionList($action_list); + $properties = $this->buildPropertyList($service); + $actions = $this->buildActionList($service); + $details = $this->buildPropertySection($service); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($service->getName()) - ->setPolicyObject($service); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($property_list); + ->setPolicyObject($service) + ->setHeaderIcon('fa-plug'); + $issue = null; if ($service->isClusterService()) { - $this->addClusterMessage( - $box, + $issue = $this->addClusterMessage( pht('This is a cluster service.'), pht( 'This service is a cluster service. You do not have permission to '. @@ -49,36 +46,62 @@ final class AlmanacServiceViewController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($service->getName()); + $crumbs->setBorder(true); $timeline = $this->buildTransactionTimeline( $service, new AlmanacServiceTransactionQuery()); $timeline->setShouldTerminate(true); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn(array( + $issue, + $details, + $bindings, + $this->buildAlmanacPropertiesTable($service), + $timeline, + )) + ->setPropertyList($properties) + ->setActionList($actions); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $box, - $bindings, - $this->buildAlmanacPropertiesTable($service), - $timeline, - )); + $view, + )); } - private function buildPropertyList(AlmanacService $service) { + private function buildPropertyList( + AlmanacService $service) { + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setObject($service); + + $view->invokeWillRenderEvent(); + + return $view; + } + + private function buildPropertySection( + AlmanacService $service) { $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($service); + ->setUser($viewer); $properties->addProperty( pht('Service Type'), $service->getServiceImplementation()->getServiceTypeShortName()); - return $properties; + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('DETAILS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($properties); } private function buildActionList(AlmanacService $service) { @@ -126,7 +149,7 @@ final class AlmanacServiceViewController ->setHideServiceColumn(true); $header = id(new PHUIHeaderView()) - ->setHeader(pht('Service Bindings')) + ->setHeader(pht('SERVICE BINDINGS')) ->addActionLink( id(new PHUIButtonView()) ->setTag('a') @@ -138,6 +161,7 @@ final class AlmanacServiceViewController return id(new PHUIObjectBoxView()) ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); } diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php index 7227bed0a7..0e12bc9abe 100644 --- a/src/view/phui/PHUIHeaderView.php +++ b/src/view/phui/PHUIHeaderView.php @@ -313,10 +313,11 @@ final class PHUIHeaderView extends AphrontTagView { $action_list); } + $icon = null; if ($this->headerIcon) { $icon = id(new PHUIIconView()) - ->setIcon($this->headerIcon); - $left[] = $icon; + ->setIcon($this->headerIcon) + ->addClass('phui-header-icon'); } $header_content = $this->header; @@ -338,6 +339,7 @@ final class PHUIHeaderView extends AphrontTagView { ), array( $space_header, + $icon, $header_content, )); diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css index 40dc1be987..b2efc8c3c0 100644 --- a/webroot/rsrc/css/aphront/table-view.css +++ b/webroot/rsrc/css/aphront/table-view.css @@ -248,10 +248,11 @@ span.single-display-line-content { } .aphront-table-view tr.no-data td { - padding: 12px; + padding: 16px; text-align: center; color: {$lightgreytext}; font-style: italic; + font-size: {$normalfontsize}; } .aphront-table-view td.thumb img { diff --git a/webroot/rsrc/css/phui/phui-box.css b/webroot/rsrc/css/phui/phui-box.css index 38ca1d7610..7eaca8b741 100644 --- a/webroot/rsrc/css/phui/phui-box.css +++ b/webroot/rsrc/css/phui/phui-box.css @@ -66,6 +66,11 @@ padding: 0; } +.phui-box.phui-box-blue-property .phui-header-action-link { + margin-top: 0; + margin-bottom: 0; +} + .device .phui-box.phui-box-blue-property { padding: 0; } diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index 1ce8efd9e2..cd4b7b1238 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -75,6 +75,11 @@ body .phui-header-shell.phui-bleed-header color: {$darkbluetext}; } +.phui-header-header .phui-header-icon { + margin-right: 8px; + color: {$lightbluetext}; +} + .phui-object-box .phui-header-tall .phui-header-header, .phui-document-view .phui-header-tall .phui-header-header { font-size: 18px; diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index 91e236ec6b..d0dd4eebdf 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -75,6 +75,10 @@ margin: 0 0 20px 0; } +.phui-two-column-view .phui-object-box.phui-object-box-collapsed { + padding: 0; +} + /* Timeline */ .phui-two-column-view .phui-timeline-view { @@ -112,6 +116,7 @@ .phui-two-column-properties .phabricator-action-list-view { padding-top: 4px; + padding-bottom: 12px; } .device-desktop .phui-two-column-view .phui-property-list-container { @@ -128,19 +133,19 @@ .phui-two-column-properties .phui-property-list-stacked .phui-property-list-properties .phui-property-list-key { - margin: 20px 0 8px 0; + margin: 4px 0 8px 0; padding: 20px 4px 0; border-top: 1px solid rgba({$alphablue}, .2); } .phui-two-column-properties .phui-property-list-stacked .phui-property-list-properties .phui-property-list-value { - margin: 0; + margin: 0 0 20px 0; padding: 0 4px; } .device-desktop .phui-two-column-properties .phui-property-list-container { - padding: 0 0 20px 0; + padding: 0; } .device .phui-two-column-properties .phui-property-list-stacked @@ -190,3 +195,9 @@ .device .phui-two-column-view .phui-property-list-text-content { margin: 0 8px; } + +/* Info View */ + +.phui-two-column-view .phui-two-column-content .phui-info-view { + margin: 0 0 20px 0; +} From caadd1025a1f8564bb3b1e7bf89dd39655432a06 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 1 Mar 2016 12:14:28 -0800 Subject: [PATCH 24/41] Give PHUITwoColumnView an addPropertySection method Summary: Simplifies building pages a little more, adds a helper method to just add a property section to the main column automatically above other content. Test Plan: Review Ponder, Herald, Passphrase, Countdown. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15377 --- .../controller/HeraldRuleViewController.php | 15 +++---- .../PassphraseCredentialViewController.php | 12 +++--- .../PonderQuestionViewController.php | 13 ++----- src/view/phui/PHUITwoColumnView.php | 39 +++++++++++++++---- 4 files changed, 46 insertions(+), 33 deletions(-) diff --git a/src/applications/herald/controller/HeraldRuleViewController.php b/src/applications/herald/controller/HeraldRuleViewController.php index da9f35c6e4..f1f6bcff7a 100644 --- a/src/applications/herald/controller/HeraldRuleViewController.php +++ b/src/applications/herald/controller/HeraldRuleViewController.php @@ -34,7 +34,7 @@ final class HeraldRuleViewController extends HeraldController { $actions = $this->buildActionView($rule); $properties = $this->buildPropertyView($rule); - $details = $this->buildDetailsView($rule); + $details = $this->buildPropertySectionView($rule); $id = $rule->getID(); @@ -55,10 +55,8 @@ final class HeraldRuleViewController extends HeraldController { $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setMainColumn(array( - $details, - $timeline, - )) + ->setMainColumn($timeline) + ->addPropertySection(pht('DETAILS'), $details) ->setPropertyList($properties) ->setActionList($actions); @@ -127,7 +125,7 @@ final class HeraldRuleViewController extends HeraldController { return $view; } - private function buildDetailsView( + private function buildPropertySectionView( HeraldRule $rule) { $viewer = $this->getRequest()->getUser(); @@ -167,10 +165,7 @@ final class HeraldRuleViewController extends HeraldController { $view->addTextContent($rule_text); } - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('DETAILS')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($view); + return $view; } } diff --git a/src/applications/passphrase/controller/PassphraseCredentialViewController.php b/src/applications/passphrase/controller/PassphraseCredentialViewController.php index b4ccf0ad9e..5ec2fe7c07 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialViewController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialViewController.php @@ -34,12 +34,13 @@ final class PassphraseCredentialViewController extends PassphraseController { $actions = $this->buildActionView($credential, $type); $properties = $this->buildPropertyView($credential, $type); $subheader = $this->buildSubheaderView($credential); - $content = $this->buildDetailsView($credential, $type); + $content = $this->buildPropertySectionView($credential, $type); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setSubheader($subheader) - ->setMainColumn(array($content, $timeline)) + ->setMainColumn($timeline) + ->addPropertySection(pht('PROPERTIES'), $content) ->setPropertyList($properties) ->setActionList($actions); @@ -186,7 +187,7 @@ final class PassphraseCredentialViewController extends PassphraseController { return $actions; } - private function buildDetailsView( + private function buildPropertySectionView( PassphraseCredential $credential, PassphraseCredentialType $type) { $viewer = $this->getRequest()->getUser(); @@ -231,10 +232,7 @@ final class PassphraseCredentialViewController extends PassphraseController { new PHUIRemarkupView($viewer, $description)); } - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('PROPERTIES')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($properties); + return $properties; } private function buildPropertyView( diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index 75a458fdc8..3a1a210075 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -44,7 +44,7 @@ final class PonderQuestionViewController extends PonderController { $properties = $this->buildPropertyListView($question); $actions = $this->buildActionListView($question); - $details = $this->buildDetailsPropertyView($question); + $details = $this->buildPropertySectionView($question); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -107,7 +107,6 @@ final class PonderQuestionViewController extends PonderController { 'class' => 'ponder-question-content', ), array( - $details, $footer, $comment_view, $answer_wiki, @@ -120,6 +119,7 @@ final class PonderQuestionViewController extends PonderController { ->setSubheader($subheader) ->setMainColumn($ponder_content) ->setPropertyList($properties) + ->addPropertySection(pht('DETAILS'), $details) ->setActionList($actions) ->addClass('ponder-question-view'); @@ -222,7 +222,7 @@ final class PonderQuestionViewController extends PonderController { ->setContent($content); } - private function buildDetailsPropertyView( + private function buildPropertySectionView( PonderQuestion $question) { $viewer = $this->getViewer(); @@ -241,11 +241,7 @@ final class PonderQuestionViewController extends PonderController { $question_details = phutil_tag_div( 'phabricator-remarkup ml', $question_details); - return id(new PHUIObjectBoxView()) - ->setHeaderText(pht('DETAILS')) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->setFlush(true) - ->appendChild($question_details); + return $question_details; } /** @@ -274,7 +270,6 @@ final class PonderQuestionViewController extends PonderController { ->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT))); $xactions = $timeline->getTransactions(); - $view[] = id(new PonderAnswerView()) ->setUser($viewer) ->setAnswer($answer) diff --git a/src/view/phui/PHUITwoColumnView.php b/src/view/phui/PHUITwoColumnView.php index 62db9c16e7..666935c669 100644 --- a/src/view/phui/PHUITwoColumnView.php +++ b/src/view/phui/PHUITwoColumnView.php @@ -8,6 +8,7 @@ final class PHUITwoColumnView extends AphrontTagView { private $fluid; private $header; private $subheader; + private $propertySection = array(); private $actionList; private $propertyList; @@ -34,6 +35,11 @@ final class PHUITwoColumnView extends AphrontTagView { return $this; } + public function addPropertySection($title, $section) { + $this->propertySection[] = array($title, $section); + return $this; + } + public function setActionList(PhabricatorActionListView $list) { $this->actionList = $list; return $this; @@ -83,13 +89,7 @@ final class PHUITwoColumnView extends AphrontTagView { protected function getTagContent() { require_celerity_resource('phui-two-column-view-css'); - $main = phutil_tag( - 'div', - array( - 'class' => 'phui-main-column', - ), - $this->mainColumn); - + $main = $this->buildMainColumn(); $side = $this->buildSideColumn(); $order = array($side, $main); @@ -123,6 +123,31 @@ final class PHUITwoColumnView extends AphrontTagView { )); } + private function buildMainColumn() { + + $view = array(); + $sections = $this->propertySection; + + if ($sections) { + foreach ($sections as $content) { + $view[] = id(new PHUIObjectBoxView()) + ->setHeaderText($content[0]) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($content[1]); + } + } + + return phutil_tag( + 'div', + array( + 'class' => 'phui-main-column', + ), + array( + $view, + $this->mainColumn, + )); + } + private function buildSideColumn() { $property_list = $this->propertyList; $action_list = $this->actionList; From 6f481aa84faff80ad07c9d125edb3162241cbb42 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 2 Mar 2016 10:16:19 -0800 Subject: [PATCH 25/41] Convert Macro to PHUITwoColumnView Summary: Converts Macro to new layout Test Plan: Add Macro, Edit Macro, Mobile, Desktop layouts Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15372 --- .../PhabricatorMacroViewController.php | 120 ++++++++++++------ 1 file changed, 81 insertions(+), 39 deletions(-) diff --git a/src/applications/macro/controller/PhabricatorMacroViewController.php b/src/applications/macro/controller/PhabricatorMacroViewController.php index fb6db3cf07..4e43c796cf 100644 --- a/src/applications/macro/controller/PhabricatorMacroViewController.php +++ b/src/applications/macro/controller/PhabricatorMacroViewController.php @@ -20,29 +20,18 @@ final class PhabricatorMacroViewController return new Aphront404Response(); } - $file = $macro->getFile(); - $title_short = pht('Macro "%s"', $macro->getName()); $title_long = pht('Image Macro "%s"', $macro->getName()); $actions = $this->buildActionView($macro); + $subheader = $this->buildSubheaderView($macro); + $properties = $this->buildPropertyView($macro); + $file = $this->buildFileView($macro); + $details = $this->buildPropertySectionView($macro); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb( - $title_short, - $this->getApplicationURI('/view/'.$macro->getID().'/')); - - $properties = $this->buildPropertyView($macro, $actions); - if ($file) { - $file_view = new PHUIPropertyListView(); - $file_view->addImageContent( - phutil_tag( - 'img', - array( - 'src' => $file->getViewURI(), - 'class' => 'phabricator-image-macro-hero', - ))); - } + $crumbs->addTextCrumb($macro->getName()); + $crumbs->setBorder(true); $timeline = $this->buildTransactionTimeline( $macro, @@ -75,28 +64,30 @@ final class PhabricatorMacroViewController ->setAction($this->getApplicationURI('/comment/'.$macro->getID().'/')) ->setSubmitButtonName(pht('Add Comment')); - $object_box = id(new PHUIObjectBoxView()) + $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->addPropertyList($properties); - - if ($file_view) { - $object_box->addPropertyList($file_view); - } - - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, + ->setSubheader($subheader) + ->setMainColumn(array( $timeline, $add_comment_form, - ), - array( - 'title' => $title_short, - 'pageObjects' => array($macro->getPHID()), + )) + ->addPropertySection(pht('MACRO'), $file) + ->addPropertySection(pht('DETAILS'), $details) + ->setPropertyList($properties) + ->setActionList($actions); + + return $this->newPage() + ->setTitle($title_short) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs(array($macro->getPHID())) + ->appendChild( + array( + $view, )); } - private function buildActionView(PhabricatorFileImageMacro $macro) { + private function buildActionView( + PhabricatorFileImageMacro $macro) { $can_manage = $this->hasApplicationCapability( PhabricatorMacroManageCapability::CAPABILITY); @@ -141,15 +132,34 @@ final class PhabricatorMacroViewController return $view; } - private function buildPropertyView( - PhabricatorFileImageMacro $macro, - PhabricatorActionListView $actions) { + private function buildSubheaderView( + PhabricatorFileImageMacro $macro) { + $viewer = $this->getViewer(); + + $author_phid = $macro->getAuthorPHID(); + + $author = $viewer->renderHandle($author_phid)->render(); + $date = phabricator_datetime($macro->getDateCreated(), $viewer); + $author = phutil_tag('strong', array(), $author); + + $handles = $viewer->loadHandles(array($author_phid)); + $image_uri = $handles[$author_phid]->getImageURI(); + $image_href = $handles[$author_phid]->getURI(); + + $content = pht('Masterfully imagined by %s on %s.', $author, $date); + + return id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); + } + + private function buildPropertySectionView( + PhabricatorFileImageMacro $macro) { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setUser($this->getRequest()->getUser()) - ->setObject($macro) - ->setActionList($actions); + ->setUser($viewer); switch ($macro->getAudioBehavior()) { case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_ONCE: @@ -167,6 +177,38 @@ final class PhabricatorMacroViewController $viewer->renderHandle($audio_phid)); } + return $view; + } + + private function buildFileView( + PhabricatorFileImageMacro $macro) { + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $file = $macro->getFile(); + if ($file) { + $view->addImageContent( + phutil_tag( + 'img', + array( + 'src' => $file->getViewURI(), + 'class' => 'phabricator-image-macro-hero', + ))); + return $view; + } + return null; + } + + private function buildPropertyView( + PhabricatorFileImageMacro $macro) { + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()) + ->setUser($this->getRequest()->getUser()) + ->setObject($macro); + $view->invokeWillRenderEvent(); return $view; From b9a13e56e43e288d05920c7cdd81b49943a0ad25 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 2 Mar 2016 11:05:26 -0800 Subject: [PATCH 26/41] Update Badges for PHUITwoColumnView Summary: Updates the layout to two column, moves "Add Receipients" to dialog. Ref T10318 Test Plan: Give badges, revoke badges. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T10318 Differential Revision: https://secure.phabricator.com/D15382 --- resources/celerity/map.php | 4 +- src/__phutil_library_map__.php | 2 +- ...bricatorBadgesEditRecipientsController.php | 39 +++++----------- ...icatorBadgesRemoveRecipientsController.php | 7 ++- .../PhabricatorBadgesViewController.php | 46 +++++++++++++------ .../PhabricatorBadgesRecipientsListView.php | 4 +- .../rsrc/css/phui/phui-two-column-view.css | 6 +++ 7 files changed, 57 insertions(+), 51 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 2f74d82db8..4aa7284757 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -155,7 +155,7 @@ return array( 'rsrc/css/phui/phui-status.css' => '888cedb8', 'rsrc/css/phui/phui-tag-view.css' => '9d5d4400', 'rsrc/css/phui/phui-timeline-view.css' => '2efceff8', - 'rsrc/css/phui/phui-two-column-view.css' => 'cc0b8a09', + 'rsrc/css/phui/phui-two-column-view.css' => '64355d16', 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7', 'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647', 'rsrc/css/phui/workboards/phui-workcard.css' => '3646fb96', @@ -843,7 +843,7 @@ return array( 'phui-tag-view-css' => '9d5d4400', 'phui-theme-css' => '027ba77e', 'phui-timeline-view-css' => '2efceff8', - 'phui-two-column-view-css' => 'cc0b8a09', + 'phui-two-column-view-css' => '64355d16', 'phui-workboard-color-css' => 'ac6fe6a7', 'phui-workboard-view-css' => 'e6d89647', 'phui-workcard-view-css' => '3646fb96', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 8d634c1c86..32a5f5cdf1 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -6123,7 +6123,7 @@ phutil_register_library_map(array( 'PhabricatorBadgesMailReceiver' => 'PhabricatorObjectMailReceiver', 'PhabricatorBadgesPHIDType' => 'PhabricatorPHIDType', 'PhabricatorBadgesQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', - 'PhabricatorBadgesRecipientsListView' => 'AphrontTagView', + 'PhabricatorBadgesRecipientsListView' => 'AphrontView', 'PhabricatorBadgesRemoveRecipientsController' => 'PhabricatorBadgesController', 'PhabricatorBadgesReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorBadgesSchemaSpec' => 'PhabricatorConfigSchemaSpec', diff --git a/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php b/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php index 872b715e5b..70e3d8f029 100644 --- a/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php +++ b/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php @@ -22,6 +22,7 @@ final class PhabricatorBadgesEditRecipientsController } $recipient_phids = $badge->getRecipientPHIDs(); + $view_uri = $this->getApplicationURI('view/'.$badge->getID().'/'); if ($request->isFormPost()) { $recipient_spec = array(); @@ -53,7 +54,7 @@ final class PhabricatorBadgesEditRecipientsController ->applyTransactions($badge, $xactions); return id(new AphrontRedirectResponse()) - ->setURI($request->getRequestURI()); + ->setURI($view_uri); } $recipient_phids = array_reverse($recipient_phids); @@ -76,44 +77,26 @@ final class PhabricatorBadgesEditRecipientsController $title = pht('Add Recipient'); if ($can_edit) { $header_name = pht('Edit Recipients'); - $view_uri = $this->getApplicationURI('view/'.$badge->getID().'/'); $form = new AphrontFormView(); $form ->setUser($viewer) + ->setFullWidth(true) ->appendControl( id(new AphrontFormTokenizerControl()) ->setName('phids') ->setLabel(pht('Add Recipients')) - ->setDatasource(new PhabricatorPeopleDatasource())) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($view_uri) - ->setValue(pht('Add Recipients'))); - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->setForm($form); + ->setDatasource(new PhabricatorPeopleDatasource())); } - $recipient_list = id(new PhabricatorBadgesRecipientsListView()) - ->setBadge($badge) - ->setHandles($handles) - ->setUser($viewer); + $dialog = id(new AphrontDialogView()) + ->setUser($viewer) + ->setTitle(pht('Award Badges')) + ->appendForm($form) + ->addCancelButton($view_uri) + ->addSubmitButton(pht('Add Recipients')); - $badge_url = $this->getApplicationURI('view/'.$id.'/'); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($badge->getName(), $badge_url); - $crumbs->addTextCrumb(pht('Recipients')); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild( - array( - $form_box, - $recipient_list, - )); + return $dialog; } } diff --git a/src/applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php b/src/applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php index 0282854790..bc467b703d 100644 --- a/src/applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php +++ b/src/applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php @@ -28,8 +28,7 @@ final class PhabricatorBadgesRemoveRecipientsController return new Aphront404Response(); } - $recipients_uri = - $this->getApplicationURI('recipients/'.$badge->getID().'/'); + $view_uri = $this->getApplicationURI('view/'.$badge->getID().'/'); if ($request->isFormPost()) { $recipient_spec = array(); @@ -52,7 +51,7 @@ final class PhabricatorBadgesRemoveRecipientsController ->applyTransactions($badge, $xactions); return id(new AphrontRedirectResponse()) - ->setURI($recipients_uri); + ->setURI($view_uri); } $handle = id(new PhabricatorHandleQuery()) @@ -68,7 +67,7 @@ final class PhabricatorBadgesRemoveRecipientsController 'Really revoke the badge "%s" from %s?', phutil_tag('strong', array(), $badge->getName()), phutil_tag('strong', array(), $handle->getName()))) - ->addCancelButton($recipients_uri) + ->addCancelButton($view_uri) ->addSubmitButton(pht('Revoke Badge')); return $dialog; diff --git a/src/applications/badges/controller/PhabricatorBadgesViewController.php b/src/applications/badges/controller/PhabricatorBadgesViewController.php index 81241edd9f..3858965d41 100644 --- a/src/applications/badges/controller/PhabricatorBadgesViewController.php +++ b/src/applications/badges/controller/PhabricatorBadgesViewController.php @@ -22,6 +22,7 @@ final class PhabricatorBadgesViewController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($badge->getName()); + $crumbs->setBorder(true); $title = $badge->getName(); if ($badge->isArchived()) { @@ -39,15 +40,12 @@ final class PhabricatorBadgesViewController ->setHeader($badge->getName()) ->setUser($viewer) ->setPolicyObject($badge) - ->setStatus($status_icon, $status_color, $status_name); + ->setStatus($status_icon, $status_color, $status_name) + ->setHeaderIcon('fa-trophy'); $properties = $this->buildPropertyListView($badge); $actions = $this->buildActionListView($badge); - $properties->setActionList($actions); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $details = $this->buildDetailsView($badge); $timeline = $this->buildTransactionTimeline( $badge, @@ -64,26 +62,47 @@ final class PhabricatorBadgesViewController $add_comment = $this->buildCommentForm($badge); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn(array( + $recipient_list, + $timeline, + $add_comment, + )) + ->setPropertyList($properties) + ->setActionList($actions) + ->addPropertySection(pht('BADGE DETAILS'), $details); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->setPageObjectPHIDs(array($badge->getPHID())) ->appendChild( array( - $box, - $recipient_list, - $timeline, - $add_comment, + $view, )); } - private function buildPropertyListView(PhabricatorBadgesBadge $badge) { + private function buildPropertyListView( + PhabricatorBadgesBadge $badge) { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($badge); + $view->invokeWillRenderEvent(); + + return $view; + } + + private function buildDetailsView( + PhabricatorBadgesBadge $badge) { + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + $quality = idx($badge->getQualityNameMap(), $badge->getQuality()); $view->addProperty( @@ -99,8 +118,6 @@ final class PhabricatorBadgesViewController pht('Flavor'), $badge->getFlavor()); - $view->invokeWillRenderEvent(); - $description = $badge->getDescription(); if (strlen($description)) { $view->addSectionHeader( @@ -160,9 +177,10 @@ final class PhabricatorBadgesViewController $view->addAction( id(new PhabricatorActionView()) - ->setName('Manage Recipients') + ->setName('Add Recipients') ->setIcon('fa-users') ->setDisabled(!$can_edit) + ->setWorkflow(true) ->setHref($this->getApplicationURI("/recipients/{$id}/"))); return $view; diff --git a/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php b/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php index 30d883bb57..6b633bed5b 100644 --- a/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php +++ b/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php @@ -1,6 +1,6 @@ user; diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index d0dd4eebdf..d6f87c3828 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -200,4 +200,10 @@ .phui-two-column-view .phui-two-column-content .phui-info-view { margin: 0 0 20px 0; + padding: 16px; +} + +.phui-two-column-view .phui-two-column-content .phui-object-box + .phui-info-view { + margin: 0; } From 86d61916336a748b7a97cf7de3de69353761221f Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 2 Mar 2016 11:18:35 -0800 Subject: [PATCH 27/41] Update Fund for PHUITwoColumnView Summary: Updates the Fund application to use a two column layout Test Plan: Make an initiative. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15383 --- .../FundInitiativeViewController.php | 51 +++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/src/applications/fund/controller/FundInitiativeViewController.php b/src/applications/fund/controller/FundInitiativeViewController.php index 6f780e4e72..f4535d574e 100644 --- a/src/applications/fund/controller/FundInitiativeViewController.php +++ b/src/applications/fund/controller/FundInitiativeViewController.php @@ -7,7 +7,6 @@ final class FundInitiativeViewController return true; } - public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); @@ -22,6 +21,7 @@ final class FundInitiativeViewController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($initiative->getMonogram()); + $crumbs->setBorder(true); $title = pht( '%s %s', @@ -43,32 +43,32 @@ final class FundInitiativeViewController ->setHeader($initiative->getName()) ->setUser($viewer) ->setPolicyObject($initiative) - ->setStatus($status_icon, $status_color, $status_name); + ->setStatus($status_icon, $status_color, $status_name) + ->setHeaderIcon('fa-heart'); $properties = $this->buildPropertyListView($initiative); $actions = $this->buildActionListView($initiative); - $properties->setActionList($actions); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); - + $details = $this->buildPropertySectionView($initiative); $timeline = $this->buildTransactionTimeline( $initiative, new FundInitiativeTransactionQuery()); - $timeline - ->setShouldTerminate(true); + $timeline->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - $timeline, - ), - array( - 'title' => $title, - 'pageObjects' => array($initiative->getPHID()), + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn($timeline) + ->setPropertyList($properties) + ->addPropertySection(pht('DETAILS'), $details) + ->setActionList($actions); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs(array($initiative->getPHID())) + ->appendChild( + array( + $view, )); } @@ -79,6 +79,17 @@ final class FundInitiativeViewController ->setUser($viewer) ->setObject($initiative); + $view->invokeWillRenderEvent(); + + return $view; + } + + private function buildPropertySectionView(FundInitiative $initiative) { + $viewer = $this->getRequest()->getUser(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + $owner_phid = $initiative->getOwnerPHID(); $merchant_phid = $initiative->getMerchantPHID(); @@ -94,8 +105,6 @@ final class FundInitiativeViewController pht('Total Funding'), $initiative->getTotalAsCurrency()->formatForDisplay()); - $view->invokeWillRenderEvent(); - $description = $initiative->getDescription(); if (strlen($description)) { $description = new PHUIRemarkupView($viewer, $description); From e3ed8b5fe1f57cb014788de9eefd2b6d8f8f5015 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 2 Mar 2016 11:38:59 -0800 Subject: [PATCH 28/41] Sync up UI with actual policy rules in Phame Summary: Fixes T10504. The "Create Blog" buttons weren't generated by EditEngine, but should be, so that the UI and policies are in sync. Test Plan: - Viewed blog list as user with and without permission to create blogs. Saw correct button state. - Tried to create blogs, saw correct result. - Viewed empty state of home, clicked "New Blog" buttons. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10504 Differential Revision: https://secure.phabricator.com/D15384 --- .../PhabricatorPhameApplication.php | 4 +- .../phame/controller/PhameHomeController.php | 33 ++++++----------- .../blog/PhameBlogListController.php | 37 +++---------------- .../phame/editor/PhameBlogEditEngine.php | 14 ++++++- .../phame/view/PhameBlogListView.php | 2 +- 5 files changed, 33 insertions(+), 57 deletions(-) diff --git a/src/applications/phame/application/PhabricatorPhameApplication.php b/src/applications/phame/application/PhabricatorPhameApplication.php index 637b51a66c..30d1b9cb3d 100644 --- a/src/applications/phame/application/PhabricatorPhameApplication.php +++ b/src/applications/phame/application/PhabricatorPhameApplication.php @@ -60,11 +60,11 @@ final class PhabricatorPhameApplication extends PhabricatorApplication { 'blog/' => array( '(?:query/(?P[^/]+)/)?' => 'PhameBlogListController', 'archive/(?P[^/]+)/' => 'PhameBlogArchiveController', - 'edit/(?P[^/]+)/' => 'PhameBlogEditController', + $this->getEditRoutePattern('edit/') + => 'PhameBlogEditController', 'view/(?P\d+)/' => 'PhameBlogViewController', 'manage/(?P[^/]+)/' => 'PhameBlogManageController', 'feed/(?P[^/]+)/' => 'PhameBlogFeedController', - 'new/' => 'PhameBlogEditController', 'picture/(?P[1-9]\d*)/' => 'PhameBlogProfilePictureController', ), ) + $this->getResourceSubroutes(), diff --git a/src/applications/phame/controller/PhameHomeController.php b/src/applications/phame/controller/PhameHomeController.php index cea86735b5..95d1544abe 100644 --- a/src/applications/phame/controller/PhameHomeController.php +++ b/src/applications/phame/controller/PhameHomeController.php @@ -6,6 +6,16 @@ final class PhameHomeController extends PhamePostController { return true; } + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + id(new PhameBlogEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); + + return $crumbs; + } + public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); @@ -44,7 +54,7 @@ final class PhameHomeController extends PhamePostController { $create_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Create a Blog')) - ->setHref('/phame/blog/new/') + ->setHref('/phame/blog/edit/') ->setColor(PHUIButtonView::GREEN); $post_list = id(new PHUIBigInfoView()) @@ -116,27 +126,6 @@ final class PhameHomeController extends PhamePostController { array( $phame_home, )); - - - } - - private function renderBlogs($viewer, $blogs) {} - - protected function buildApplicationCrumbs() { - $crumbs = parent::buildApplicationCrumbs(); - - $can_create = $this->hasApplicationCapability( - PhameBlogCreateCapability::CAPABILITY); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('New Blog')) - ->setHref($this->getApplicationURI('/blog/new/')) - ->setIcon('fa-plus-square') - ->setDisabled(!$can_create) - ->setWorkflow(!$can_create)); - - return $crumbs; } } diff --git a/src/applications/phame/controller/blog/PhameBlogListController.php b/src/applications/phame/controller/blog/PhameBlogListController.php index 9bef5c1cf9..ff14178087 100644 --- a/src/applications/phame/controller/blog/PhameBlogListController.php +++ b/src/applications/phame/controller/blog/PhameBlogListController.php @@ -7,43 +7,18 @@ final class PhameBlogListController extends PhameBlogController { } public function handleRequest(AphrontRequest $request) { - $query_key = $request->getURIData('queryKey'); - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($query_key) - ->setSearchEngine(new PhameBlogSearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); + return id(new PhameBlogSearchEngine()) + ->setController($this) + ->buildResponse(); } - public function buildSideNavView() { - $viewer = $this->getRequest()->getUser(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - id(new PhameBlogSearchEngine()) - ->setViewer($viewer) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; - } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $can_create = $this->hasApplicationCapability( - PhameBlogCreateCapability::CAPABILITY); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('New Blog')) - ->setHref($this->getApplicationURI('/blog/new/')) - ->setIcon('fa-plus-square') - ->setDisabled(!$can_create) - ->setWorkflow(!$can_create)); + id(new PhameBlogEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } diff --git a/src/applications/phame/editor/PhameBlogEditEngine.php b/src/applications/phame/editor/PhameBlogEditEngine.php index 92e267096d..503a6bbd27 100644 --- a/src/applications/phame/editor/PhameBlogEditEngine.php +++ b/src/applications/phame/editor/PhameBlogEditEngine.php @@ -46,12 +46,24 @@ final class PhameBlogEditEngine return pht('Create Blog'); } + protected function getObjectCreateCancelURI($object) { + return $this->getApplication()->getApplicationURI('blog/'); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('blog/edit/'); + } + protected function getObjectViewURI($object) { return $object->getManageURI(); } - protected function buildCustomEditFields($object) { + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + PhameBlogCreateCapability::CAPABILITY); + } + protected function buildCustomEditFields($object) { return array( id(new PhabricatorTextEditField()) ->setKey('name') diff --git a/src/applications/phame/view/PhameBlogListView.php b/src/applications/phame/view/PhameBlogListView.php index f2149b8cfb..0d897b730a 100644 --- a/src/applications/phame/view/PhameBlogListView.php +++ b/src/applications/phame/view/PhameBlogListView.php @@ -72,7 +72,7 @@ final class PhameBlogListView extends AphrontTagView { $list = phutil_tag( 'a', array( - 'href' => '/phame/blog/new/', + 'href' => '/phame/blog/edit/', ), pht('Create a Blog')); } From e08d70a9a5c82650a220708a839158f89a5f332c Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 3 Mar 2016 00:23:11 +0000 Subject: [PATCH 29/41] Touch up HeraldView a bit more Summary: Better colors, move description to own box, cleaner spacing. Test Plan: View a few herald rules Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15386 --- resources/celerity/map.php | 4 +-- .../controller/HeraldRuleViewController.php | 28 +++++++++++++------ src/view/phui/PHUITwoColumnView.php | 10 ++++--- .../rsrc/css/application/herald/herald.css | 10 +++---- 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4aa7284757..61647309ac 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -72,7 +72,7 @@ return array( 'rsrc/css/application/flag/flag.css' => '5337623f', 'rsrc/css/application/harbormaster/harbormaster.css' => '834879db', 'rsrc/css/application/herald/herald-test.css' => 'a52e323e', - 'rsrc/css/application/herald/herald.css' => '826075fa', + 'rsrc/css/application/herald/herald.css' => '46596280', 'rsrc/css/application/maniphest/batch-editor.css' => 'b0f0b6d5', 'rsrc/css/application/maniphest/report.css' => '9b9580b7', 'rsrc/css/application/maniphest/task-edit.css' => 'fda62a9b', @@ -560,7 +560,7 @@ return array( 'font-lato' => 'c7ccd872', 'global-drag-and-drop-css' => '5c1b47c2', 'harbormaster-css' => '834879db', - 'herald-css' => '826075fa', + 'herald-css' => '46596280', 'herald-rule-editor' => '746ca158', 'herald-test-css' => 'a52e323e', 'inline-comment-summary-css' => '51efda3a', diff --git a/src/applications/herald/controller/HeraldRuleViewController.php b/src/applications/herald/controller/HeraldRuleViewController.php index f1f6bcff7a..4036be250b 100644 --- a/src/applications/herald/controller/HeraldRuleViewController.php +++ b/src/applications/herald/controller/HeraldRuleViewController.php @@ -18,7 +18,8 @@ final class HeraldRuleViewController extends HeraldController { $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($rule->getName()) - ->setPolicyObject($rule); + ->setPolicyObject($rule) + ->setHeaderIcon('fa-bullhorn'); if ($rule->getIsDisabled()) { $header->setStatus( @@ -35,6 +36,7 @@ final class HeraldRuleViewController extends HeraldController { $actions = $this->buildActionView($rule); $properties = $this->buildPropertyView($rule); $details = $this->buildPropertySectionView($rule); + $description = $this->buildDescriptionView($rule); $id = $rule->getID(); @@ -57,6 +59,7 @@ final class HeraldRuleViewController extends HeraldController { ->setHeader($header) ->setMainColumn($timeline) ->addPropertySection(pht('DETAILS'), $details) + ->addPropertySection(pht('DESCRIPTION'), $description) ->setPropertyList($properties) ->setActionList($actions); @@ -155,17 +158,24 @@ final class HeraldRuleViewController extends HeraldController { pht('Trigger Object'), $viewer->renderHandle($rule->getTriggerObjectPHID())); } - - $view->addSectionHeader( - pht('Rule Description'), - PHUIPropertyListView::ICON_SUMMARY); - - $handles = $viewer->loadHandles(HeraldAdapter::getHandlePHIDs($rule)); - $rule_text = $adapter->renderRuleAsText($rule, $handles, $viewer); - $view->addTextContent($rule_text); } return $view; } + private function buildDescriptionView(HeraldRule $rule) { + $viewer = $this->getRequest()->getUser(); + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $adapter = HeraldAdapter::getAdapterForContentType($rule->getContentType()); + if ($adapter) { + $handles = $viewer->loadHandles(HeraldAdapter::getHandlePHIDs($rule)); + $rule_text = $adapter->renderRuleAsText($rule, $handles, $viewer); + $view->addTextContent($rule_text); + return $view; + } + return null; + } + } diff --git a/src/view/phui/PHUITwoColumnView.php b/src/view/phui/PHUITwoColumnView.php index 666935c669..fb27a1a50d 100644 --- a/src/view/phui/PHUITwoColumnView.php +++ b/src/view/phui/PHUITwoColumnView.php @@ -130,10 +130,12 @@ final class PHUITwoColumnView extends AphrontTagView { if ($sections) { foreach ($sections as $content) { - $view[] = id(new PHUIObjectBoxView()) - ->setHeaderText($content[0]) - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) - ->appendChild($content[1]); + if ($content[1]) { + $view[] = id(new PHUIObjectBoxView()) + ->setHeaderText($content[0]) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($content[1]); + } } } diff --git a/webroot/rsrc/css/application/herald/herald.css b/webroot/rsrc/css/application/herald/herald.css index 8feebd122e..26cb9cfd71 100644 --- a/webroot/rsrc/css/application/herald/herald.css +++ b/webroot/rsrc/css/application/herald/herald.css @@ -42,16 +42,16 @@ } .herald-list-description { - color: {$darkgreytext}; + color: {$bluetext}; + font-weight: bold; padding: 8px 0; } .herald-list-icon { - margin-left: 12px; - margin-right: 2px; + margin-right: 8px; } .herald-list-item { - padding-bottom: 4px; - color: {$greytext}; + padding-bottom: 20px; + color: {$darkbluetext}; } From 61c45e4927a06295ee04a8d5c1d12b0abe05aa09 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 2 Mar 2016 18:09:13 -0800 Subject: [PATCH 30/41] Update Calendar for PHUITwoColumnView Summary: Moves calendar event view to PHUITwoColumnView. Tightened up some phui status spacing. Test Plan: Review Calendar Events, Differential status list. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15387 --- resources/celerity/map.php | 10 +- ...PhabricatorCalendarEventViewController.php | 91 ++++++++++++------- src/view/phui/PHUIStatusItemView.php | 11 +-- webroot/rsrc/css/phui/phui-status.css | 3 +- .../rsrc/css/phui/phui-two-column-view.css | 2 +- 5 files changed, 69 insertions(+), 48 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 61647309ac..50877bd5fc 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'd5d34907', + 'core.pkg.css' => 'dd1447be', 'core.pkg.js' => '7d8faf57', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', @@ -152,10 +152,10 @@ return array( 'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591', 'rsrc/css/phui/phui-segment-bar-view.css' => '46342871', 'rsrc/css/phui/phui-spacing.css' => '042804d6', - 'rsrc/css/phui/phui-status.css' => '888cedb8', + 'rsrc/css/phui/phui-status.css' => '37309046', 'rsrc/css/phui/phui-tag-view.css' => '9d5d4400', 'rsrc/css/phui/phui-timeline-view.css' => '2efceff8', - 'rsrc/css/phui/phui-two-column-view.css' => '64355d16', + 'rsrc/css/phui/phui-two-column-view.css' => 'd0ad8c10', 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7', 'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647', 'rsrc/css/phui/workboards/phui-workcard.css' => '3646fb96', @@ -839,11 +839,11 @@ return array( 'phui-remarkup-preview-css' => '1a8f2591', 'phui-segment-bar-view-css' => '46342871', 'phui-spacing-css' => '042804d6', - 'phui-status-list-view-css' => '888cedb8', + 'phui-status-list-view-css' => '37309046', 'phui-tag-view-css' => '9d5d4400', 'phui-theme-css' => '027ba77e', 'phui-timeline-view-css' => '2efceff8', - 'phui-two-column-view-css' => '64355d16', + 'phui-two-column-view-css' => 'd0ad8c10', 'phui-workboard-color-css' => 'ac6fe6a7', 'phui-workboard-view-css' => 'e6d89647', 'phui-workcard-view-css' => '3646fb96', diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index d494fa7a0b..8d13f85e61 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -52,7 +52,8 @@ final class PhabricatorCalendarEventViewController $title = 'E'.$event->getID(); $page_title = $title.' '.$event->getName(); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($title, '/E'.$event->getID()); + $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); } if (!$event->getIsGhostEvent()) { @@ -63,12 +64,9 @@ final class PhabricatorCalendarEventViewController $header = $this->buildHeaderView($event); $actions = $this->buildActionView($event); - $properties = $this->buildPropertyView($event); - - $properties->setActionList($actions); - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $properties = $this->buildPropertyListView($event); + $details = $this->buildPropertySection($event); + $description = $this->buildDescriptionView($event); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $add_comment_header = $is_serious @@ -90,26 +88,32 @@ final class PhabricatorCalendarEventViewController ->setAction($comment_uri) ->setSubmitButtonName(pht('Add Comment')); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - $timeline, - $add_comment_form, - ), - array( - 'title' => $page_title, - 'pageObjects' => array($event->getPHID()), + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn($timeline) + ->setPropertyList($properties) + ->addPropertySection(pht('DETAILS'), $details) + ->addPropertySection(pht('DESCRIPTION'), $description) + ->setActionList($actions); + + return $this->newPage() + ->setTitle($page_title) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs(array($event->getPHID())) + ->appendChild( + array( + $view, )); } - private function buildHeaderView(PhabricatorCalendarEvent $event) { - $viewer = $this->getRequest()->getUser(); + private function buildHeaderView( + PhabricatorCalendarEvent $event) { + $viewer = $this->getViewer(); $id = $event->getID(); $is_cancelled = $event->getIsCancelled(); - $icon = $is_cancelled ? ('fa-times') : ('fa-calendar'); - $color = $is_cancelled ? ('grey') : ('green'); + $icon = $is_cancelled ? ('fa-ban') : ('fa-check'); + $color = $is_cancelled ? ('red') : ('bluegrey'); $status = $is_cancelled ? pht('Cancelled') : pht('Active'); $invite_status = $event->getUserInviteStatus($viewer->getPHID()); @@ -120,7 +124,8 @@ final class PhabricatorCalendarEventViewController ->setUser($viewer) ->setHeader($event->getName()) ->setStatus($icon, $color, $status) - ->setPolicyObject($event); + ->setPolicyObject($event) + ->setHeaderIcon('fa-calendar'); if ($is_invite_pending) { $decline_button = id(new PHUIButtonView()) @@ -245,13 +250,26 @@ final class PhabricatorCalendarEventViewController return $actions; } - private function buildPropertyView(PhabricatorCalendarEvent $event) { - $viewer = $this->getRequest()->getUser(); + private function buildPropertyListView( + PhabricatorCalendarEvent $event) { + $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) ->setObject($event); + $properties->invokeWillRenderEvent(); + + return $properties; + } + + private function buildPropertySection( + PhabricatorCalendarEvent $event) { + $viewer = $this->getViewer(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer); + if ($event->getIsAllDay()) { $date_start = phabricator_date($event->getDateFrom(), $viewer); $date_end = phabricator_date($event->getDateTo(), $viewer); @@ -362,16 +380,23 @@ final class PhabricatorCalendarEventViewController id(new PhabricatorCalendarIconSet()) ->getIconLabel($event->getIcon())); - if (strlen($event->getDescription())) { - $description = new PHUIRemarkupView($viewer, $event->getDescription()); - $properties->addSectionHeader( - pht('Description'), - PHUIPropertyListView::ICON_SUMMARY); - - $properties->addTextContent($description); - } - return $properties; } + private function buildDescriptionView( + PhabricatorCalendarEvent $event) { + $viewer = $this->getViewer(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer); + + if (strlen($event->getDescription())) { + $description = new PHUIRemarkupView($viewer, $event->getDescription()); + $properties->addTextContent($description); + return $properties; + } + + return null; + } + } diff --git a/src/view/phui/PHUIStatusItemView.php b/src/view/phui/PHUIStatusItemView.php index dae7e7d472..1e859f5fd2 100644 --- a/src/view/phui/PHUIStatusItemView.php +++ b/src/view/phui/PHUIStatusItemView.php @@ -83,17 +83,15 @@ final class PHUIStatusItemView extends AphrontTagView { } } - $icon_cell = phutil_tag( - 'td', - array(), - $icon); - $target_cell = phutil_tag( 'td', array( 'class' => 'phui-status-item-target', ), - $this->target); + array( + $icon, + $this->target, + )); $note_cell = phutil_tag( 'td', @@ -103,7 +101,6 @@ final class PHUIStatusItemView extends AphrontTagView { $this->note); return array( - $icon_cell, $target_cell, $note_cell, ); diff --git a/webroot/rsrc/css/phui/phui-status.css b/webroot/rsrc/css/phui/phui-status.css index 9dc446d536..d8f46c002f 100644 --- a/webroot/rsrc/css/phui/phui-status.css +++ b/webroot/rsrc/css/phui/phui-status.css @@ -7,10 +7,9 @@ } .phui-status-list-view .phui-icon-view { - display: block; width: 14px; height: 14px; - margin: 3px 4px; + margin: 2px 4px 2px 0; } .phui-status-item-target { diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index d6f87c3828..6c36aa8bd1 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -108,7 +108,7 @@ .device-desktop .phui-main-column .phui-property-list-value { margin-left: 8px; - width: auto; + width: calc(100% - 180px); } From ac729278328ed9679229d11ba1eefdad784b59e2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 3 Mar 2016 05:46:02 -0800 Subject: [PATCH 31/41] Fix overzealous transactions on dashboard panels Summary: Fixes T10474. This had the same root cause as T10024 -- a missing call to `didSetValueFromStorage()` because of the way the fields work. Test Plan: - Edited a text panel before change, without changing text: got silly transaction. - Made change, edited text panel without changing text, no transaction. - Made a real edit, got a good transaction. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10474 Differential Revision: https://secure.phabricator.com/D15391 --- .../customfield/PhabricatorDashboardPanelCoreCustomField.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php b/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php index d002b1e2c3..cf045265b9 100644 --- a/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php +++ b/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php @@ -21,6 +21,7 @@ final class PhabricatorDashboardPanelCoreCustomField public function readValueFromObject(PhabricatorCustomFieldInterface $object) { $key = $this->getProxy()->getRawStandardFieldKey(); $this->setValueFromStorage($object->getProperty($key)); + $this->didSetValueFromStorage(); } public function applyApplicationTransactionInternalEffects( From ba5b32f5bb37c0db2e623332437d65ab0f332b69 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 3 Mar 2016 22:08:00 +0000 Subject: [PATCH 32/41] Add headericons a little more consistently Summary: I kinda like these to differentiate the headers and different object types. Somethings duplicitive, but helps orient the clean header a bit. Test Plan: Review each in sandbox. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15394 --- .../controller/PhabricatorCountdownViewController.php | 3 ++- .../ponder/controller/PonderQuestionViewController.php | 1 + .../slowvote/controller/PhabricatorSlowvotePollController.php | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/applications/countdown/controller/PhabricatorCountdownViewController.php b/src/applications/countdown/controller/PhabricatorCountdownViewController.php index 423bfe5207..9a983f0ed9 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownViewController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownViewController.php @@ -46,7 +46,8 @@ final class PhabricatorCountdownViewController ->setHeader($title) ->setUser($viewer) ->setPolicyObject($countdown) - ->setStatus($icon, $color, $status); + ->setStatus($icon, $color, $status) + ->setHeaderIcon('fa-rocket'); $actions = $this->buildActionListView($countdown); $properties = $this->buildPropertyListView($countdown); diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index 3a1a210075..439119512b 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -31,6 +31,7 @@ final class PonderQuestionViewController extends PonderController { $header->setHeader($question->getTitle()); $header->setUser($viewer); $header->setPolicyObject($question); + $header->setHeaderIcon('fa-university'); if ($question->getStatus() == PonderQuestionStatus::STATUS_OPEN) { $header->setStatus('fa-square-o', 'bluegrey', pht('Open')); diff --git a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php index 8603185c73..2eb2c08c75 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php @@ -43,7 +43,8 @@ final class PhabricatorSlowvotePollController ->setHeader($poll->getQuestion()) ->setUser($viewer) ->setStatus($header_icon, $header_color, $header_name) - ->setPolicyObject($poll); + ->setPolicyObject($poll) + ->setHeaderIcon('fa-bar-chart'); $actions = $this->buildActionView($poll); $properties = $this->buildPropertyView($poll); From 01379958fa3d330fcb14ea329f835a7e7342f05e Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 3 Mar 2016 04:17:24 -0800 Subject: [PATCH 33/41] Allow Drydock blueprints to be searched by name Summary: Ref T10457. The ngram indexing seems to be working well; extend it into Drydock. Also clean up the list controller a little bit. Test Plan: - Ran migrations. - Searched for blueprints by name. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10457 Differential Revision: https://secure.phabricator.com/D15389 --- .../autopatches/20160303.drydock.1.bluen.sql | 7 +++++++ .../autopatches/20160303.drydock.2.bluei.php | 11 +++++++++++ src/__phutil_library_map__.php | 3 +++ .../DrydockBlueprintListController.php | 12 +++--------- .../drydock/editor/DrydockBlueprintEditor.php | 4 ++++ .../drydock/query/DrydockBlueprintQuery.php | 6 ++++++ .../query/DrydockBlueprintSearchEngine.php | 8 ++++++++ .../drydock/storage/DrydockBlueprint.php | 13 ++++++++++++- .../storage/DrydockBlueprintNameNgrams.php | 18 ++++++++++++++++++ 9 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 resources/sql/autopatches/20160303.drydock.1.bluen.sql create mode 100644 resources/sql/autopatches/20160303.drydock.2.bluei.php create mode 100644 src/applications/drydock/storage/DrydockBlueprintNameNgrams.php diff --git a/resources/sql/autopatches/20160303.drydock.1.bluen.sql b/resources/sql/autopatches/20160303.drydock.1.bluen.sql new file mode 100644 index 0000000000..c0cbf90492 --- /dev/null +++ b/resources/sql/autopatches/20160303.drydock.1.bluen.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_drydock.drydock_blueprintname_ngrams ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + objectID INT UNSIGNED NOT NULL, + ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}, + KEY `key_object` (objectID), + KEY `key_ngram` (ngram, objectID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160303.drydock.2.bluei.php b/resources/sql/autopatches/20160303.drydock.2.bluei.php new file mode 100644 index 0000000000..c0b68c2262 --- /dev/null +++ b/resources/sql/autopatches/20160303.drydock.2.bluei.php @@ -0,0 +1,11 @@ +getPHID(), + array( + 'force' => true, + )); +} diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 32a5f5cdf1..5750dee24e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -876,6 +876,7 @@ phutil_register_library_map(array( 'DrydockBlueprintImplementation' => 'applications/drydock/blueprint/DrydockBlueprintImplementation.php', 'DrydockBlueprintImplementationTestCase' => 'applications/drydock/blueprint/__tests__/DrydockBlueprintImplementationTestCase.php', 'DrydockBlueprintListController' => 'applications/drydock/controller/DrydockBlueprintListController.php', + 'DrydockBlueprintNameNgrams' => 'applications/drydock/storage/DrydockBlueprintNameNgrams.php', 'DrydockBlueprintPHIDType' => 'applications/drydock/phid/DrydockBlueprintPHIDType.php', 'DrydockBlueprintQuery' => 'applications/drydock/query/DrydockBlueprintQuery.php', 'DrydockBlueprintSearchEngine' => 'applications/drydock/query/DrydockBlueprintSearchEngine.php', @@ -4972,6 +4973,7 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', 'PhabricatorCustomFieldInterface', + 'PhabricatorNgramsInterface', ), 'DrydockBlueprintController' => 'DrydockController', 'DrydockBlueprintCoreCustomField' => array( @@ -4987,6 +4989,7 @@ phutil_register_library_map(array( 'DrydockBlueprintImplementation' => 'Phobject', 'DrydockBlueprintImplementationTestCase' => 'PhabricatorTestCase', 'DrydockBlueprintListController' => 'DrydockBlueprintController', + 'DrydockBlueprintNameNgrams' => 'PhabricatorSearchNgrams', 'DrydockBlueprintPHIDType' => 'PhabricatorPHIDType', 'DrydockBlueprintQuery' => 'DrydockQuery', 'DrydockBlueprintSearchEngine' => 'PhabricatorApplicationSearchEngine', diff --git a/src/applications/drydock/controller/DrydockBlueprintListController.php b/src/applications/drydock/controller/DrydockBlueprintListController.php index ff09f19ff8..98757c99b0 100644 --- a/src/applications/drydock/controller/DrydockBlueprintListController.php +++ b/src/applications/drydock/controller/DrydockBlueprintListController.php @@ -7,15 +7,9 @@ final class DrydockBlueprintListController extends DrydockBlueprintController { } public function handleRequest(AphrontRequest $request) { - $querykey = $request->getURIData('queryKey'); - - $request = $this->getRequest(); - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($querykey) - ->setSearchEngine(new DrydockBlueprintSearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); + return id(new DrydockBlueprintSearchEngine()) + ->setController($this) + ->buildResponse(); } protected function buildApplicationCrumbs() { diff --git a/src/applications/drydock/editor/DrydockBlueprintEditor.php b/src/applications/drydock/editor/DrydockBlueprintEditor.php index b5fd584945..315f91cd5c 100644 --- a/src/applications/drydock/editor/DrydockBlueprintEditor.php +++ b/src/applications/drydock/editor/DrydockBlueprintEditor.php @@ -11,6 +11,10 @@ final class DrydockBlueprintEditor return pht('Drydock Blueprints'); } + protected function supportsSearch() { + return true; + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); diff --git a/src/applications/drydock/query/DrydockBlueprintQuery.php b/src/applications/drydock/query/DrydockBlueprintQuery.php index 169e47b4f7..6c92927bb8 100644 --- a/src/applications/drydock/query/DrydockBlueprintQuery.php +++ b/src/applications/drydock/query/DrydockBlueprintQuery.php @@ -39,6 +39,12 @@ final class DrydockBlueprintQuery extends DrydockQuery { return $this; } + public function withNameNgrams($ngrams) { + return $this->withNgramsConstraint( + new DrydockBlueprintNameNgrams(), + $ngrams); + } + public function newResultObject() { return new DrydockBlueprint(); } diff --git a/src/applications/drydock/query/DrydockBlueprintSearchEngine.php b/src/applications/drydock/query/DrydockBlueprintSearchEngine.php index 64859649df..bb7bd02cc9 100644 --- a/src/applications/drydock/query/DrydockBlueprintSearchEngine.php +++ b/src/applications/drydock/query/DrydockBlueprintSearchEngine.php @@ -18,6 +18,10 @@ final class DrydockBlueprintSearchEngine protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); + if ($map['match'] !== null) { + $query->withNameNgrams($map['match']); + } + if ($map['isDisabled'] !== null) { $query->withDisabled($map['isDisabled']); } @@ -27,6 +31,10 @@ final class DrydockBlueprintSearchEngine protected function buildCustomSearchFields() { return array( + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Name Contains')) + ->setKey('match') + ->setDescription(pht('Search for blueprints by name substring.')), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Disabled')) ->setKey('isDisabled') diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php index 4bc3dbe86d..dd50c29820 100644 --- a/src/applications/drydock/storage/DrydockBlueprint.php +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -8,7 +8,8 @@ final class DrydockBlueprint extends DrydockDAO implements PhabricatorApplicationTransactionInterface, PhabricatorPolicyInterface, - PhabricatorCustomFieldInterface { + PhabricatorCustomFieldInterface, + PhabricatorNgramsInterface { protected $className; protected $blueprintName; @@ -343,4 +344,14 @@ final class DrydockBlueprint extends DrydockDAO } +/* -( PhabricatorNgramInterface )------------------------------------------ */ + + + public function newNgrams() { + return array( + id(new DrydockBlueprintNameNgrams()) + ->setValue($this->getBlueprintName()), + ); + } + } diff --git a/src/applications/drydock/storage/DrydockBlueprintNameNgrams.php b/src/applications/drydock/storage/DrydockBlueprintNameNgrams.php new file mode 100644 index 0000000000..f140e11ca9 --- /dev/null +++ b/src/applications/drydock/storage/DrydockBlueprintNameNgrams.php @@ -0,0 +1,18 @@ + Date: Thu, 3 Mar 2016 04:34:47 -0800 Subject: [PATCH 34/41] Convert DrydockBlueprints to EditEngine Summary: Ref T10457. Fixes T10024. This primarily just modernizes blueprints to use EditEngine. This also fixes T10024, which was an issue with stored properties not being flagged correctly. Also slightly improves typeaheads for blueprints (more information, disabled state). Test Plan: - Created and edited various types of blueprints. - Set and removed limits. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10024, T10457 Differential Revision: https://secure.phabricator.com/D15390 --- src/__phutil_library_map__.php | 6 +- .../PhabricatorDrydockApplication.php | 4 +- .../controller/DrydockBlueprintController.php | 16 +- .../DrydockBlueprintCreateController.php | 79 ------- .../DrydockBlueprintEditController.php | 193 ++++++------------ .../DrydockBlueprintListController.php | 15 +- .../DrydockBlueprintCoreCustomField.php | 1 + .../editor/DrydockBlueprintEditEngine.php | 115 +++++++++++ .../drydock/editor/DrydockBlueprintEditor.php | 32 +++ .../typeahead/DrydockBlueprintDatasource.php | 15 +- .../editfield/PhabricatorStaticEditField.php | 18 ++ ...abricatorStandardCustomFieldCredential.php | 12 ++ ...habricatorStandardCustomFieldTokenizer.php | 12 ++ 13 files changed, 275 insertions(+), 243 deletions(-) delete mode 100644 src/applications/drydock/controller/DrydockBlueprintCreateController.php create mode 100644 src/applications/drydock/editor/DrydockBlueprintEditEngine.php create mode 100644 src/applications/transactions/editfield/PhabricatorStaticEditField.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5750dee24e..73d24c3d7d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -867,11 +867,11 @@ phutil_register_library_map(array( 'DrydockBlueprint' => 'applications/drydock/storage/DrydockBlueprint.php', 'DrydockBlueprintController' => 'applications/drydock/controller/DrydockBlueprintController.php', 'DrydockBlueprintCoreCustomField' => 'applications/drydock/customfield/DrydockBlueprintCoreCustomField.php', - 'DrydockBlueprintCreateController' => 'applications/drydock/controller/DrydockBlueprintCreateController.php', 'DrydockBlueprintCustomField' => 'applications/drydock/customfield/DrydockBlueprintCustomField.php', 'DrydockBlueprintDatasource' => 'applications/drydock/typeahead/DrydockBlueprintDatasource.php', 'DrydockBlueprintDisableController' => 'applications/drydock/controller/DrydockBlueprintDisableController.php', 'DrydockBlueprintEditController' => 'applications/drydock/controller/DrydockBlueprintEditController.php', + 'DrydockBlueprintEditEngine' => 'applications/drydock/editor/DrydockBlueprintEditEngine.php', 'DrydockBlueprintEditor' => 'applications/drydock/editor/DrydockBlueprintEditor.php', 'DrydockBlueprintImplementation' => 'applications/drydock/blueprint/DrydockBlueprintImplementation.php', 'DrydockBlueprintImplementationTestCase' => 'applications/drydock/blueprint/__tests__/DrydockBlueprintImplementationTestCase.php', @@ -3287,6 +3287,7 @@ phutil_register_library_map(array( 'PhabricatorStandardCustomFieldUsers' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldUsers.php', 'PhabricatorStandardPageView' => 'view/page/PhabricatorStandardPageView.php', 'PhabricatorStandardSelectCustomFieldDatasource' => 'infrastructure/customfield/datasource/PhabricatorStandardSelectCustomFieldDatasource.php', + 'PhabricatorStaticEditField' => 'applications/transactions/editfield/PhabricatorStaticEditField.php', 'PhabricatorStatusController' => 'applications/system/controller/PhabricatorStatusController.php', 'PhabricatorStatusUIExample' => 'applications/uiexample/examples/PhabricatorStatusUIExample.php', 'PhabricatorStorageFixtureScopeGuard' => 'infrastructure/testing/fixture/PhabricatorStorageFixtureScopeGuard.php', @@ -4980,11 +4981,11 @@ phutil_register_library_map(array( 'DrydockBlueprintCustomField', 'PhabricatorStandardCustomFieldInterface', ), - 'DrydockBlueprintCreateController' => 'DrydockBlueprintController', 'DrydockBlueprintCustomField' => 'PhabricatorCustomField', 'DrydockBlueprintDatasource' => 'PhabricatorTypeaheadDatasource', 'DrydockBlueprintDisableController' => 'DrydockBlueprintController', 'DrydockBlueprintEditController' => 'DrydockBlueprintController', + 'DrydockBlueprintEditEngine' => 'PhabricatorEditEngine', 'DrydockBlueprintEditor' => 'PhabricatorApplicationTransactionEditor', 'DrydockBlueprintImplementation' => 'Phobject', 'DrydockBlueprintImplementationTestCase' => 'PhabricatorTestCase', @@ -7834,6 +7835,7 @@ phutil_register_library_map(array( 'AphrontResponseProducerInterface', ), 'PhabricatorStandardSelectCustomFieldDatasource' => 'PhabricatorTypeaheadDatasource', + 'PhabricatorStaticEditField' => 'PhabricatorEditField', 'PhabricatorStatusController' => 'PhabricatorController', 'PhabricatorStatusUIExample' => 'PhabricatorUIExample', 'PhabricatorStorageFixtureScopeGuard' => 'Phobject', diff --git a/src/applications/drydock/application/PhabricatorDrydockApplication.php b/src/applications/drydock/application/PhabricatorDrydockApplication.php index d19e7cb78d..cb229e3410 100644 --- a/src/applications/drydock/application/PhabricatorDrydockApplication.php +++ b/src/applications/drydock/application/PhabricatorDrydockApplication.php @@ -60,8 +60,8 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication { 'authorizations/(?:query/(?P[^/]+)/)?' => 'DrydockAuthorizationListController', ), - 'create/' => 'DrydockBlueprintCreateController', - 'edit/(?:(?P[1-9]\d*)/)?' => 'DrydockBlueprintEditController', + $this->getEditRoutePattern('edit/') + => 'DrydockBlueprintEditController', ), '(?Presource)/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockResourceListController', diff --git a/src/applications/drydock/controller/DrydockBlueprintController.php b/src/applications/drydock/controller/DrydockBlueprintController.php index b995192eb4..c251871426 100644 --- a/src/applications/drydock/controller/DrydockBlueprintController.php +++ b/src/applications/drydock/controller/DrydockBlueprintController.php @@ -3,24 +3,18 @@ abstract class DrydockBlueprintController extends DrydockController { - public function buildSideNavView() { - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - id(new DrydockBlueprintSearchEngine()) - ->setViewer($this->getRequest()->getUser()) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; + public function buildApplicationMenu() { + return $this->newApplicationMenu() + ->setSearchEngine(new DrydockBlueprintSearchEngine()); } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); + $crumbs->addTextCrumb( pht('Blueprints'), $this->getApplicationURI('blueprint/')); + return $crumbs; } diff --git a/src/applications/drydock/controller/DrydockBlueprintCreateController.php b/src/applications/drydock/controller/DrydockBlueprintCreateController.php deleted file mode 100644 index 5398a2327d..0000000000 --- a/src/applications/drydock/controller/DrydockBlueprintCreateController.php +++ /dev/null @@ -1,79 +0,0 @@ -getViewer(); - - $this->requireApplicationCapability( - DrydockCreateBlueprintsCapability::CAPABILITY); - - $implementations = - DrydockBlueprintImplementation::getAllBlueprintImplementations(); - - $errors = array(); - $e_blueprint = null; - - if ($request->isFormPost()) { - $class = $request->getStr('blueprint-type'); - if (!isset($implementations[$class])) { - $e_blueprint = pht('Required'); - $errors[] = pht('You must choose a blueprint type.'); - } - - if (!$errors) { - $edit_uri = $this->getApplicationURI('blueprint/edit/?class='.$class); - return id(new AphrontRedirectResponse())->setURI($edit_uri); - } - } - - $control = id(new AphrontFormRadioButtonControl()) - ->setName('blueprint-type') - ->setLabel(pht('Blueprint Type')) - ->setError($e_blueprint); - - foreach ($implementations as $implementation_name => $implementation) { - $disabled = !$implementation->isEnabled(); - - $control->addButton( - $implementation_name, - $implementation->getBlueprintName(), - array( - pht('Provides: %s', $implementation->getType()), - phutil_tag('br'), - phutil_tag('br'), - $implementation->getDescription(), - ), - $disabled ? 'disabled' : null, - $disabled); - } - - $title = pht('Create New Blueprint'); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('New Blueprint')); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild($control) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($this->getApplicationURI('blueprint/')) - ->setValue(pht('Continue'))); - - $box = id(new PHUIObjectBoxView()) - ->setFormErrors($errors) - ->setHeaderText($title) - ->setForm($form); - - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, - )); - } - -} diff --git a/src/applications/drydock/controller/DrydockBlueprintEditController.php b/src/applications/drydock/controller/DrydockBlueprintEditController.php index 2e61ec868c..fbda9d0d29 100644 --- a/src/applications/drydock/controller/DrydockBlueprintEditController.php +++ b/src/applications/drydock/controller/DrydockBlueprintEditController.php @@ -3,168 +3,89 @@ final class DrydockBlueprintEditController extends DrydockBlueprintController { public function handleRequest(AphrontRequest $request) { - $viewer = $request->getViewer(); + $engine = id(new DrydockBlueprintEditEngine()) + ->setController($this); + $id = $request->getURIData('id'); - - if ($id) { - $blueprint = id(new DrydockBlueprintQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$blueprint) { - return new Aphront404Response(); - } - - $impl = $blueprint->getImplementation(); - $cancel_uri = $this->getApplicationURI('blueprint/'.$id.'/'); - } else { + if (!$id) { $this->requireApplicationCapability( DrydockCreateBlueprintsCapability::CAPABILITY); - $class = $request->getStr('class'); + $type = $request->getStr('blueprintType'); - $impl = DrydockBlueprintImplementation::getNamedImplementation($class); + $impl = DrydockBlueprintImplementation::getNamedImplementation($type); if (!$impl || !$impl->isEnabled()) { - return new Aphront400Response(); + return $this->buildTypeSelectionResponse(); } - $blueprint = DrydockBlueprint::initializeNewBlueprint($viewer) - ->setClassName($class) - ->attachImplementation($impl); - - $cancel_uri = $this->getApplicationURI('blueprint/'); + $engine + ->addContextParameter('blueprintType', $type) + ->setBlueprintImplementation($impl); } - $field_list = PhabricatorCustomField::getObjectFields( - $blueprint, - PhabricatorCustomField::ROLE_EDIT); - $field_list - ->setViewer($viewer) - ->readFieldsFromStorage($blueprint); + return $engine->buildResponse(); + } + + private function buildTypeSelectionResponse() { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $implementations = + DrydockBlueprintImplementation::getAllBlueprintImplementations(); - $v_name = $blueprint->getBlueprintName(); - $e_name = true; $errors = array(); - $validation_exception = null; + $e_blueprint = null; if ($request->isFormPost()) { - $v_view_policy = $request->getStr('viewPolicy'); - $v_edit_policy = $request->getStr('editPolicy'); - $v_name = $request->getStr('name'); - if (!strlen($v_name)) { - $e_name = pht('Required'); - $errors[] = pht('You must name this blueprint.'); - } - - if (!$errors) { - $xactions = array(); - - $xactions = $field_list->buildFieldTransactionsFromRequest( - new DrydockBlueprintTransaction(), - $request); - - $xactions[] = id(new DrydockBlueprintTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) - ->setNewValue($v_view_policy); - - $xactions[] = id(new DrydockBlueprintTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) - ->setNewValue($v_edit_policy); - - $xactions[] = id(new DrydockBlueprintTransaction()) - ->setTransactionType(DrydockBlueprintTransaction::TYPE_NAME) - ->setNewValue($v_name); - - $editor = id(new DrydockBlueprintEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - try { - $editor->applyTransactions($blueprint, $xactions); - - $id = $blueprint->getID(); - $save_uri = $this->getApplicationURI("blueprint/{$id}/"); - - return id(new AphrontRedirectResponse())->setURI($save_uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - } + $class = $request->getStr('blueprintType'); + if (!isset($implementations[$class])) { + $e_blueprint = pht('Required'); + $errors[] = pht('You must choose a blueprint type.'); } } - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($blueprint) - ->execute(); + $control = id(new AphrontFormRadioButtonControl()) + ->setName('blueprintType') + ->setLabel(pht('Blueprint Type')) + ->setError($e_blueprint); + + foreach ($implementations as $implementation_name => $implementation) { + $disabled = !$implementation->isEnabled(); + + $control->addButton( + $implementation_name, + $implementation->getBlueprintName(), + array( + pht('Provides: %s', $implementation->getType()), + phutil_tag('br'), + phutil_tag('br'), + $implementation->getDescription(), + ), + $disabled ? 'disabled' : null, + $disabled); + } + + $title = pht('Create New Blueprint'); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('New Blueprint')); $form = id(new AphrontFormView()) ->setUser($viewer) - ->addHiddenInput('class', $request->getStr('class')) + ->appendChild($control) ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Name')) - ->setName('name') - ->setValue($v_name) - ->setError($e_name)) - ->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel(pht('Blueprint Type')) - ->setValue($impl->getBlueprintName())) - ->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)); - - $field_list->appendFieldsToForm($form); - - $crumbs = $this->buildApplicationCrumbs(); - - if ($blueprint->getID()) { - $title = pht('Edit Blueprint'); - $header = pht('Edit Blueprint %d', $blueprint->getID()); - $crumbs->addTextCrumb(pht('Blueprint %d', $blueprint->getID())); - $crumbs->addTextCrumb(pht('Edit')); - $submit = pht('Save Blueprint'); - } else { - $title = pht('New Blueprint'); - $header = pht('New Blueprint'); - $crumbs->addTextCrumb(pht('New Blueprint')); - $submit = pht('Create Blueprint'); - } - - $form->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue($submit) - ->addCancelButton($cancel_uri)); + id(new AphrontFormSubmitControl()) + ->addCancelButton($this->getApplicationURI('blueprint/')) + ->setValue(pht('Continue'))); $box = id(new PHUIObjectBoxView()) - ->setHeaderText($header) - ->setValidationException($validation_exception) ->setFormErrors($errors) + ->setHeaderText($title) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($box); } } diff --git a/src/applications/drydock/controller/DrydockBlueprintListController.php b/src/applications/drydock/controller/DrydockBlueprintListController.php index 98757c99b0..91de6e3425 100644 --- a/src/applications/drydock/controller/DrydockBlueprintListController.php +++ b/src/applications/drydock/controller/DrydockBlueprintListController.php @@ -13,17 +13,12 @@ final class DrydockBlueprintListController extends DrydockBlueprintController { } protected function buildApplicationCrumbs() { - $can_create = $this->hasApplicationCapability( - DrydockCreateBlueprintsCapability::CAPABILITY); - $crumbs = parent::buildApplicationCrumbs(); - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('New Blueprint')) - ->setHref($this->getApplicationURI('/blueprint/create/')) - ->setDisabled(!$can_create) - ->setWorkflow(!$can_create) - ->setIcon('fa-plus-square')); + + id(new DrydockBlueprintEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); + return $crumbs; } diff --git a/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php b/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php index 9af418b4d1..32a4ac1ed8 100644 --- a/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php +++ b/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php @@ -28,6 +28,7 @@ final class DrydockBlueprintCoreCustomField public function readValueFromObject(PhabricatorCustomFieldInterface $object) { $key = $this->getProxy()->getRawStandardFieldKey(); $this->setValueFromStorage($object->getDetail($key)); + $this->didSetValueFromStorage(); } public function applyApplicationTransactionInternalEffects( diff --git a/src/applications/drydock/editor/DrydockBlueprintEditEngine.php b/src/applications/drydock/editor/DrydockBlueprintEditEngine.php new file mode 100644 index 0000000000..bec86e5d4c --- /dev/null +++ b/src/applications/drydock/editor/DrydockBlueprintEditEngine.php @@ -0,0 +1,115 @@ +blueprintImplementation = $impl; + return $this; + } + + public function getBlueprintImplementation() { + return $this->blueprintImplementation; + } + + protected function newEditableObject() { + $viewer = $this->getViewer(); + $blueprint = DrydockBlueprint::initializeNewBlueprint($viewer); + + $impl = $this->getBlueprintImplementation(); + if ($impl) { + $blueprint + ->setClassName(get_class($impl)) + ->attachImplementation(clone $impl); + } + + return $blueprint; + } + + protected function newObjectQuery() { + return new DrydockBlueprintQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Blueprint'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Blueprint'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Blueprint: %s', $object->getBlueprintName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Blueprint'); + } + + protected function getObjectCreateShortText() { + return pht('Create Blueprint'); + } + + protected function getEditorURI() { + return '/drydock/blueprint/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/drydock/blueprint/'; + } + + protected function getObjectViewURI($object) { + $id = $object->getID(); + return "/drydock/blueprint/{$id}/"; + } + + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + DrydockCreateBlueprintsCapability::CAPABILITY); + } + + protected function buildCustomEditFields($object) { + $impl = $object->getImplementation(); + + return array( + id(new PhabricatorStaticEditField()) + ->setKey('type') + ->setLabel(pht('Blueprint Type')) + ->setDescription(pht('Type of blueprint.')) + ->setValue($impl->getBlueprintName()), + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the blueprint.')) + ->setTransactionType(DrydockBlueprintTransaction::TYPE_NAME) + ->setIsRequired(true) + ->setValue($object->getBlueprintName()), + ); + } + +} diff --git a/src/applications/drydock/editor/DrydockBlueprintEditor.php b/src/applications/drydock/editor/DrydockBlueprintEditor.php index 315f91cd5c..125c36811a 100644 --- a/src/applications/drydock/editor/DrydockBlueprintEditor.php +++ b/src/applications/drydock/editor/DrydockBlueprintEditor.php @@ -84,4 +84,36 @@ final class DrydockBlueprintEditor return parent::applyCustomExternalTransaction($object, $xaction); } + + protected function validateTransaction( + PhabricatorLiskDAO $object, + $type, + array $xactions) { + + $errors = parent::validateTransaction($object, $type, $xactions); + + switch ($type) { + case DrydockBlueprintTransaction::TYPE_NAME: + $missing = $this->validateIsEmptyTextField( + $object->getBlueprintName(), + $xactions); + + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('You must choose a name for this blueprint.'), + nonempty(last($xactions), null)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + continue; + } + + break; + } + + return $errors; + } + } diff --git a/src/applications/drydock/typeahead/DrydockBlueprintDatasource.php b/src/applications/drydock/typeahead/DrydockBlueprintDatasource.php index 8b838cd2b6..79229bfab5 100644 --- a/src/applications/drydock/typeahead/DrydockBlueprintDatasource.php +++ b/src/applications/drydock/typeahead/DrydockBlueprintDatasource.php @@ -26,11 +26,20 @@ final class DrydockBlueprintDatasource ->execute(); $results = array(); - foreach ($handles as $handle) { - $results[] = id(new PhabricatorTypeaheadResult()) - ->setName($handle->getName()) + foreach ($blueprints as $blueprint) { + $handle = $handles[$blueprint->getPHID()]; + + $result = id(new PhabricatorTypeaheadResult()) + ->setName($handle->getFullName()) ->setPHID($handle->getPHID()); + + if ($blueprint->getIsDisabled()) { + $result->setClosed(pht('Disabled')); + } + + $results[] = $result; } + return $results; } } diff --git a/src/applications/transactions/editfield/PhabricatorStaticEditField.php b/src/applications/transactions/editfield/PhabricatorStaticEditField.php new file mode 100644 index 0000000000..62c5a9a888 --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorStaticEditField.php @@ -0,0 +1,18 @@ +getDatasource(); } + protected function getHTTPParameterType() { + return new AphrontPHIDListHTTPParameterType(); + } + + protected function newConduitSearchParameterType() { + return new ConduitPHIDListParameterType(); + } + + protected function newConduitEditParameterType() { + return new ConduitPHIDListParameterType(); + } + } From fc0dc02bb92d8d11259cab2a2dbfd68990d1c9ea Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 3 Mar 2016 05:52:21 -0800 Subject: [PATCH 35/41] Allow Drydock blueprints to be tagged and searched, and give types some little icons Summary: Ref T10457. - Let blueprints be tagged so you can search and annotate them a little more easily. - Give each blueprint type an optional icon to make things a little easier to parse visually. Test Plan: - Tagged blueprints. - Searched by tags. - Looked at nice little icons. {F1139712} Reviewers: chad Reviewed By: chad Subscribers: yelirekim Maniphest Tasks: T10457 Differential Revision: https://secure.phabricator.com/D15392 --- .../autopatches/20160303.drydock.3.edge.sql | 16 ++++++++ src/__phutil_library_map__.php | 3 ++ ...anacServiceHostBlueprintImplementation.php | 4 ++ .../DrydockBlueprintImplementation.php | 4 ++ ...dockWorkingCopyBlueprintImplementation.php | 4 ++ .../DrydockBlueprintEditController.php | 8 +++- .../DrydockBlueprintViewController.php | 6 ++- .../query/DrydockBlueprintSearchEngine.php | 37 +++++++++++++++++-- .../drydock/storage/DrydockBlueprint.php | 8 +++- .../drydock/storage/DrydockSchemaSpec.php | 9 +++++ .../HarbormasterBuildPlanSearchEngine.php | 1 - 11 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 resources/sql/autopatches/20160303.drydock.3.edge.sql create mode 100644 src/applications/drydock/storage/DrydockSchemaSpec.php diff --git a/resources/sql/autopatches/20160303.drydock.3.edge.sql b/resources/sql/autopatches/20160303.drydock.3.edge.sql new file mode 100644 index 0000000000..2de8506ba9 --- /dev/null +++ b/resources/sql/autopatches/20160303.drydock.3.edge.sql @@ -0,0 +1,16 @@ +CREATE TABLE {$NAMESPACE}_drydock.edge ( + src VARBINARY(64) NOT NULL, + type INT UNSIGNED NOT NULL, + dst VARBINARY(64) NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + seq INT UNSIGNED NOT NULL, + dataID INT UNSIGNED, + PRIMARY KEY (src, type, dst), + KEY `src` (src, type, dateCreated, seq), + UNIQUE KEY `key_dst` (dst, type, src) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; + +CREATE TABLE {$NAMESPACE}_drydock.edgedata ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT} +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 73d24c3d7d..7ca4586375 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -966,6 +966,7 @@ phutil_register_library_map(array( 'DrydockResourceViewController' => 'applications/drydock/controller/DrydockResourceViewController.php', 'DrydockSFTPFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockSFTPFilesystemInterface.php', 'DrydockSSHCommandInterface' => 'applications/drydock/interface/command/DrydockSSHCommandInterface.php', + 'DrydockSchemaSpec' => 'applications/drydock/storage/DrydockSchemaSpec.php', 'DrydockSlotLock' => 'applications/drydock/storage/DrydockSlotLock.php', 'DrydockSlotLockException' => 'applications/drydock/exception/DrydockSlotLockException.php', 'DrydockSlotLockFailureLogType' => 'applications/drydock/logtype/DrydockSlotLockFailureLogType.php', @@ -4975,6 +4976,7 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorCustomFieldInterface', 'PhabricatorNgramsInterface', + 'PhabricatorProjectInterface', ), 'DrydockBlueprintController' => 'DrydockController', 'DrydockBlueprintCoreCustomField' => array( @@ -5094,6 +5096,7 @@ phutil_register_library_map(array( 'DrydockResourceViewController' => 'DrydockResourceController', 'DrydockSFTPFilesystemInterface' => 'DrydockFilesystemInterface', 'DrydockSSHCommandInterface' => 'DrydockCommandInterface', + 'DrydockSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'DrydockSlotLock' => 'DrydockDAO', 'DrydockSlotLockException' => 'Exception', 'DrydockSlotLockFailureLogType' => 'DrydockLogType', diff --git a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php index 443b0e8965..ef904e1019 100644 --- a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php @@ -15,6 +15,10 @@ final class DrydockAlmanacServiceHostBlueprintImplementation return pht('Almanac Hosts'); } + public function getBlueprintIcon() { + return 'fa-server'; + } + public function getDescription() { return pht( 'Allows Drydock to lease existing hosts defined in an Almanac service '. diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index 01c2067280..76a81d7ef1 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -15,6 +15,10 @@ abstract class DrydockBlueprintImplementation extends Phobject { abstract public function getBlueprintName(); abstract public function getDescription(); + public function getBlueprintIcon() { + return 'fa-map-o'; + } + public function getFieldSpecifications() { $fields = array(); diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php index d2f7191c06..8b138405eb 100644 --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -13,6 +13,10 @@ final class DrydockWorkingCopyBlueprintImplementation return pht('Working Copy'); } + public function getBlueprintIcon() { + return 'fa-folder-open'; + } + public function getDescription() { return pht('Allows Drydock to check out working copies of repositories.'); } diff --git a/src/applications/drydock/controller/DrydockBlueprintEditController.php b/src/applications/drydock/controller/DrydockBlueprintEditController.php index fbda9d0d29..f25dc01bff 100644 --- a/src/applications/drydock/controller/DrydockBlueprintEditController.php +++ b/src/applications/drydock/controller/DrydockBlueprintEditController.php @@ -52,9 +52,15 @@ final class DrydockBlueprintEditController extends DrydockBlueprintController { foreach ($implementations as $implementation_name => $implementation) { $disabled = !$implementation->isEnabled(); + $impl_icon = $implementation->getBlueprintIcon(); + $impl_name = $implementation->getBlueprintName(); + + $impl_icon = id(new PHUIIconView()) + ->setIcon($impl_icon, 'lightgreytext'); + $control->addButton( $implementation_name, - $implementation->getBlueprintName(), + array($impl_icon, ' ', $impl_name), array( pht('Provides: %s', $implementation->getType()), phutil_tag('br'), diff --git a/src/applications/drydock/controller/DrydockBlueprintViewController.php b/src/applications/drydock/controller/DrydockBlueprintViewController.php index b23b869fd4..21f3a395d3 100644 --- a/src/applications/drydock/controller/DrydockBlueprintViewController.php +++ b/src/applications/drydock/controller/DrydockBlueprintViewController.php @@ -127,8 +127,12 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController { private function buildPropertyListView( DrydockBlueprint $blueprint, PhabricatorActionListView $actions) { + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setObject($blueprint); - $view = new PHUIPropertyListView(); $view->setActionList($actions); $view->addProperty( diff --git a/src/applications/drydock/query/DrydockBlueprintSearchEngine.php b/src/applications/drydock/query/DrydockBlueprintSearchEngine.php index bb7bd02cc9..3c80ca2700 100644 --- a/src/applications/drydock/query/DrydockBlueprintSearchEngine.php +++ b/src/applications/drydock/query/DrydockBlueprintSearchEngine.php @@ -77,15 +77,29 @@ final class DrydockBlueprintSearchEngine assert_instances_of($blueprints, 'DrydockBlueprint'); $viewer = $this->requireViewer(); + + if ($blueprints) { + $edge_query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs(mpull($blueprints, 'getPHID')) + ->withEdgeTypes( + array( + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, + )); + + $edge_query->execute(); + } + $view = new PHUIObjectItemListView(); foreach ($blueprints as $blueprint) { + $impl = $blueprint->getImplementation(); + $item = id(new PHUIObjectItemView()) ->setHeader($blueprint->getBlueprintName()) - ->setHref($this->getApplicationURI('/blueprint/'.$blueprint->getID())) + ->setHref($blueprint->getURI()) ->setObjectName(pht('Blueprint %d', $blueprint->getID())); - if (!$blueprint->getImplementation()->isEnabled()) { + if (!$impl->isEnabled()) { $item->setDisabled(true); $item->addIcon('fa-chain-broken grey', pht('Implementation')); } @@ -95,7 +109,24 @@ final class DrydockBlueprintSearchEngine $item->addIcon('fa-ban grey', pht('Disabled')); } - $item->addAttribute($blueprint->getImplementation()->getBlueprintName()); + $impl_icon = $impl->getBlueprintIcon(); + $impl_name = $impl->getBlueprintName(); + + $impl_icon = id(new PHUIIconView()) + ->setIcon($impl_icon, 'lightgreytext'); + + $item->addAttribute(array($impl_icon, ' ', $impl_name)); + + $phid = $blueprint->getPHID(); + $project_phids = $edge_query->getDestinationPHIDs(array($phid)); + if ($project_phids) { + $project_handles = $viewer->loadHandles($project_phids); + $item->addAttribute( + id(new PHUIHandleTagListView()) + ->setLimit(4) + ->setSlim(true) + ->setHandles($project_handles)); + } $view->addItem($item); } diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php index dd50c29820..a7deb73cc3 100644 --- a/src/applications/drydock/storage/DrydockBlueprint.php +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -9,7 +9,8 @@ final class DrydockBlueprint extends DrydockDAO PhabricatorApplicationTransactionInterface, PhabricatorPolicyInterface, PhabricatorCustomFieldInterface, - PhabricatorNgramsInterface { + PhabricatorNgramsInterface, + PhabricatorProjectInterface { protected $className; protected $blueprintName; @@ -119,6 +120,11 @@ final class DrydockBlueprint extends DrydockDAO return $log->save(); } + public function getURI() { + $id = $this->getID(); + return "/drydock/blueprint/{$id}/"; + } + /* -( Allocating Resources )----------------------------------------------- */ diff --git a/src/applications/drydock/storage/DrydockSchemaSpec.php b/src/applications/drydock/storage/DrydockSchemaSpec.php new file mode 100644 index 0000000000..d8a99f6ee4 --- /dev/null +++ b/src/applications/drydock/storage/DrydockSchemaSpec.php @@ -0,0 +1,9 @@ +buildEdgeSchemata(new DrydockBlueprint()); + } + +} diff --git a/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php index 36f86b9ec5..709df7f94c 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php +++ b/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php @@ -84,7 +84,6 @@ final class HarbormasterBuildPlanSearchEngine $viewer = $this->requireViewer(); - if ($plans) { $edge_query = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(mpull($plans, 'getPHID')) From e6158391d27d7c7c04f67e10a81e89b83fb6f05f Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 3 Mar 2016 06:34:18 -0800 Subject: [PATCH 36/41] Make Drydock repository operations a little more modern and consistent Summary: Ref T10457. Use modern controller and UI tech to build the list view and actions. Test Plan: - Viewed operation list. - Viewed operation detail. - Checked menus on mobile. {F1139757} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10457 Differential Revision: https://secure.phabricator.com/D15393 --- src/__phutil_library_map__.php | 10 ++++--- .../DrydockRepositoryOperationController.php | 11 +++++++ ...ckRepositoryOperationDismissController.php | 2 +- ...ydockRepositoryOperationListController.php | 29 +++---------------- ...ockRepositoryOperationStatusController.php | 2 +- ...ydockRepositoryOperationViewController.php | 20 ++++++------- ...DrydockRepositoryOperationSearchEngine.php | 16 +++++----- 7 files changed, 41 insertions(+), 49 deletions(-) create mode 100644 src/applications/drydock/controller/DrydockRepositoryOperationController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7ca4586375..aafbab4636 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -939,6 +939,7 @@ phutil_register_library_map(array( 'DrydockObjectAuthorizationView' => 'applications/drydock/view/DrydockObjectAuthorizationView.php', 'DrydockQuery' => 'applications/drydock/query/DrydockQuery.php', 'DrydockRepositoryOperation' => 'applications/drydock/storage/DrydockRepositoryOperation.php', + 'DrydockRepositoryOperationController' => 'applications/drydock/controller/DrydockRepositoryOperationController.php', 'DrydockRepositoryOperationDismissController' => 'applications/drydock/controller/DrydockRepositoryOperationDismissController.php', 'DrydockRepositoryOperationListController' => 'applications/drydock/controller/DrydockRepositoryOperationListController.php', 'DrydockRepositoryOperationPHIDType' => 'applications/drydock/phid/DrydockRepositoryOperationPHIDType.php', @@ -5066,16 +5067,17 @@ phutil_register_library_map(array( 'DrydockDAO', 'PhabricatorPolicyInterface', ), - 'DrydockRepositoryOperationDismissController' => 'DrydockController', - 'DrydockRepositoryOperationListController' => 'DrydockController', + 'DrydockRepositoryOperationController' => 'DrydockController', + 'DrydockRepositoryOperationDismissController' => 'DrydockRepositoryOperationController', + 'DrydockRepositoryOperationListController' => 'DrydockRepositoryOperationController', 'DrydockRepositoryOperationPHIDType' => 'PhabricatorPHIDType', 'DrydockRepositoryOperationQuery' => 'DrydockQuery', 'DrydockRepositoryOperationSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'DrydockRepositoryOperationStatusController' => 'DrydockController', + 'DrydockRepositoryOperationStatusController' => 'DrydockRepositoryOperationController', 'DrydockRepositoryOperationStatusView' => 'AphrontView', 'DrydockRepositoryOperationType' => 'Phobject', 'DrydockRepositoryOperationUpdateWorker' => 'DrydockWorker', - 'DrydockRepositoryOperationViewController' => 'DrydockController', + 'DrydockRepositoryOperationViewController' => 'DrydockRepositoryOperationController', 'DrydockResource' => array( 'DrydockDAO', 'PhabricatorPolicyInterface', diff --git a/src/applications/drydock/controller/DrydockRepositoryOperationController.php b/src/applications/drydock/controller/DrydockRepositoryOperationController.php new file mode 100644 index 0000000000..67b580b01a --- /dev/null +++ b/src/applications/drydock/controller/DrydockRepositoryOperationController.php @@ -0,0 +1,11 @@ +newApplicationMenu() + ->setSearchEngine(new DrydockRepositoryOperationSearchEngine()); + } + +} diff --git a/src/applications/drydock/controller/DrydockRepositoryOperationDismissController.php b/src/applications/drydock/controller/DrydockRepositoryOperationDismissController.php index 28e14bcb90..eb0fb5bb4b 100644 --- a/src/applications/drydock/controller/DrydockRepositoryOperationDismissController.php +++ b/src/applications/drydock/controller/DrydockRepositoryOperationDismissController.php @@ -1,7 +1,7 @@ getViewer(); diff --git a/src/applications/drydock/controller/DrydockRepositoryOperationListController.php b/src/applications/drydock/controller/DrydockRepositoryOperationListController.php index 581302817b..42350d2b2a 100644 --- a/src/applications/drydock/controller/DrydockRepositoryOperationListController.php +++ b/src/applications/drydock/controller/DrydockRepositoryOperationListController.php @@ -1,37 +1,16 @@ getURIData('queryKey'); - - $engine = new DrydockRepositoryOperationSearchEngine(); - - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($query_key) - ->setSearchEngine($engine) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); - } - - public function buildSideNavView() { - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - $engine = id(new DrydockRepositoryOperationSearchEngine()) - ->setViewer($this->getViewer()); - - $engine->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; + return id(new DrydockRepositoryOperationSearchEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/drydock/controller/DrydockRepositoryOperationStatusController.php b/src/applications/drydock/controller/DrydockRepositoryOperationStatusController.php index 6503886481..c6d38791f3 100644 --- a/src/applications/drydock/controller/DrydockRepositoryOperationStatusController.php +++ b/src/applications/drydock/controller/DrydockRepositoryOperationStatusController.php @@ -1,7 +1,7 @@ setUser($viewer) ->setOperation($operation); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - $status_view, - ), - array( - 'title' => $title, - )); - + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $object_box, + $status_view, + )); } private function buildActionListView(DrydockRepositoryOperation $operation) { diff --git a/src/applications/drydock/query/DrydockRepositoryOperationSearchEngine.php b/src/applications/drydock/query/DrydockRepositoryOperationSearchEngine.php index c4befe8201..1de32be28f 100644 --- a/src/applications/drydock/query/DrydockRepositoryOperationSearchEngine.php +++ b/src/applications/drydock/query/DrydockRepositoryOperationSearchEngine.php @@ -70,7 +70,8 @@ final class DrydockRepositoryOperationSearchEngine $icon = DrydockRepositoryOperation::getOperationStateIcon($state); $name = DrydockRepositoryOperation::getOperationStateName($state); - $item->addIcon($icon, $name); + $item->setStatusIcon($icon, $name); + $item->addByline( array( pht('Via:'), @@ -78,13 +79,14 @@ final class DrydockRepositoryOperationSearchEngine $viewer->renderHandle($operation->getAuthorPHID()), )); - $item->addAttribute( - $viewer->renderHandle( - $operation->getObjectPHID())); + $object_phid = $operation->getObjectPHID(); + $repository_phid = $operation->getRepositoryPHID(); - $item->addAttribute( - $viewer->renderHandle( - $operation->getRepositoryPHID())); + $item->addAttribute($viewer->renderHandle($object_phid)); + + if ($repository_phid !== $object_phid) { + $item->addAttribute($viewer->renderHandle($repository_phid)); + } $view->addItem($item); } From 13813d226847cd5bebc3aec2dd21c3975a3c1ed8 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 3 Mar 2016 15:18:41 -0800 Subject: [PATCH 37/41] Minor Ponder spacing updates Summary: Fix an issue where you've already answered, moved the summary section. Test Plan: Review an answer with a wiki that i've already answered Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15395 --- resources/celerity/map.php | 4 ++-- .../ponder/controller/PonderQuestionViewController.php | 2 +- src/applications/ponder/view/PonderAddAnswerView.php | 3 ++- webroot/rsrc/css/application/ponder/ponder-view.css | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 50877bd5fc..be8d5b9d1c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -92,7 +92,7 @@ return array( 'rsrc/css/application/policy/policy-edit.css' => '815c66f7', 'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43', 'rsrc/css/application/policy/policy.css' => '957ea14c', - 'rsrc/css/application/ponder/ponder-view.css' => '4e321d68', + 'rsrc/css/application/ponder/ponder-view.css' => 'fbd45f96', 'rsrc/css/application/project/project-card-view.css' => '9418c97d', 'rsrc/css/application/project/project-view.css' => '9ce99f21', 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', @@ -857,7 +857,7 @@ return array( 'policy-css' => '957ea14c', 'policy-edit-css' => '815c66f7', 'policy-transaction-detail-css' => '82100a43', - 'ponder-view-css' => '4e321d68', + 'ponder-view-css' => 'fbd45f96', 'project-card-view-css' => '9418c97d', 'project-view-css' => '9ce99f21', 'releeph-core' => '9b3c5733', diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index 439119512b..41819a34f0 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -108,9 +108,9 @@ final class PonderQuestionViewController extends PonderController { 'class' => 'ponder-question-content', ), array( + $answer_wiki, $footer, $comment_view, - $answer_wiki, $answers, $answer_add_panel, )); diff --git a/src/applications/ponder/view/PonderAddAnswerView.php b/src/applications/ponder/view/PonderAddAnswerView.php index fb52bd17ed..989837aa94 100644 --- a/src/applications/ponder/view/PonderAddAnswerView.php +++ b/src/applications/ponder/view/PonderAddAnswerView.php @@ -22,13 +22,14 @@ final class PonderAddAnswerView extends AphrontView { $authors = mpull($question->getAnswers(), null, 'getAuthorPHID'); if (isset($authors[$viewer->getPHID()])) { - return id(new PHUIInfoView()) + $view = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setTitle(pht('Already Answered')) ->appendChild( pht( 'You have already answered this question. You can not answer '. 'twice, but you can edit your existing answer.')); + return phutil_tag_div('ponder-add-answer-view', $view); } $info_panel = null; diff --git a/webroot/rsrc/css/application/ponder/ponder-view.css b/webroot/rsrc/css/application/ponder/ponder-view.css index 8d0556ef87..966fb6a1fb 100644 --- a/webroot/rsrc/css/application/ponder/ponder-view.css +++ b/webroot/rsrc/css/application/ponder/ponder-view.css @@ -117,7 +117,7 @@ body .phui-main-column .ponder-question-content .ponder-answer-view } .ponder-answer-section { - margin-top: 32px; + margin-top: 48px; } .ponder-add-answer-header { From 167da4ec525e27514966aaaa8e5b72cd274487b8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 4 Mar 2016 16:08:38 -0800 Subject: [PATCH 38/41] Move Almanac Services to EditEngine Summary: Ref T10449. This modernizes the service creation/editing flow and updates the list view code a little bit. Test Plan: - Created a service. - Edited a service. - Browsed services. - Hit policy exception for editing cluster services with no permission. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10449 Differential Revision: https://secure.phabricator.com/D15398 --- src/__phutil_library_map__.php | 2 + .../PhabricatorAlmanacApplication.php | 2 +- .../controller/AlmanacServiceController.php | 5 + .../AlmanacServiceEditController.php | 184 ++---------------- .../AlmanacServiceListController.php | 38 +--- .../editor/AlmanacServiceEditEngine.php | 97 +++++++++ .../almanac/editor/AlmanacServiceEditor.php | 24 +++ 7 files changed, 151 insertions(+), 201 deletions(-) create mode 100644 src/applications/almanac/editor/AlmanacServiceEditEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index aafbab4636..1bebc01152 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -106,6 +106,7 @@ phutil_register_library_map(array( 'AlmanacServiceController' => 'applications/almanac/controller/AlmanacServiceController.php', 'AlmanacServiceDatasource' => 'applications/almanac/typeahead/AlmanacServiceDatasource.php', 'AlmanacServiceEditController' => 'applications/almanac/controller/AlmanacServiceEditController.php', + 'AlmanacServiceEditEngine' => 'applications/almanac/editor/AlmanacServiceEditEngine.php', 'AlmanacServiceEditor' => 'applications/almanac/editor/AlmanacServiceEditor.php', 'AlmanacServiceListController' => 'applications/almanac/controller/AlmanacServiceListController.php', 'AlmanacServiceNameNgrams' => 'applications/almanac/storage/AlmanacServiceNameNgrams.php', @@ -4145,6 +4146,7 @@ phutil_register_library_map(array( 'AlmanacServiceController' => 'AlmanacController', 'AlmanacServiceDatasource' => 'PhabricatorTypeaheadDatasource', 'AlmanacServiceEditController' => 'AlmanacServiceController', + 'AlmanacServiceEditEngine' => 'PhabricatorEditEngine', 'AlmanacServiceEditor' => 'AlmanacEditor', 'AlmanacServiceListController' => 'AlmanacServiceController', 'AlmanacServiceNameNgrams' => 'PhabricatorSearchNgrams', diff --git a/src/applications/almanac/application/PhabricatorAlmanacApplication.php b/src/applications/almanac/application/PhabricatorAlmanacApplication.php index 08b9c5fbc8..43b6a2c3c5 100644 --- a/src/applications/almanac/application/PhabricatorAlmanacApplication.php +++ b/src/applications/almanac/application/PhabricatorAlmanacApplication.php @@ -45,7 +45,7 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication { '' => 'AlmanacConsoleController', '(?Pservice)/' => array( $this->getQueryRoutePattern() => 'AlmanacServiceListController', - 'edit/(?:(?P\d+)/)?' => 'AlmanacServiceEditController', + $this->getEditRoutePattern('edit/') => 'AlmanacServiceEditController', 'view/(?P[^/]+)/' => 'AlmanacServiceViewController', ), '(?Pdevice)/' => array( diff --git a/src/applications/almanac/controller/AlmanacServiceController.php b/src/applications/almanac/controller/AlmanacServiceController.php index 12bfbf94ea..b77e8e3853 100644 --- a/src/applications/almanac/controller/AlmanacServiceController.php +++ b/src/applications/almanac/controller/AlmanacServiceController.php @@ -11,6 +11,11 @@ abstract class AlmanacServiceController extends AlmanacController { return $crumbs; } + public function buildApplicationMenu() { + return $this->newApplicationMenu() + ->setSearchEngine(new AlmanacServiceSearchEngine()); + } + protected function getPropertyDeleteURI($object) { $id = $object->getID(); return "/almanac/service/delete/{$id}/"; diff --git a/src/applications/almanac/controller/AlmanacServiceEditController.php b/src/applications/almanac/controller/AlmanacServiceEditController.php index 2b6f5a9f5d..b09deaa95d 100644 --- a/src/applications/almanac/controller/AlmanacServiceEditController.php +++ b/src/applications/almanac/controller/AlmanacServiceEditController.php @@ -4,175 +4,28 @@ final class AlmanacServiceEditController extends AlmanacServiceController { public function handleRequest(AphrontRequest $request) { - $viewer = $request->getViewer(); - - $list_uri = $this->getApplicationURI('service/'); + $engine = id(new AlmanacServiceEditEngine()) + ->setController($this); $id = $request->getURIData('id'); - if ($id) { - $service = id(new AlmanacServiceQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$service) { - return new Aphront404Response(); - } - - $is_new = false; - $service_uri = $service->getURI(); - $cancel_uri = $service_uri; - $title = pht('Edit Service'); - $save_button = pht('Save Changes'); - } else { - $cancel_uri = $list_uri; - + if (!$id) { $this->requireApplicationCapability( AlmanacCreateServicesCapability::CAPABILITY); + $list_uri = $this->getApplicationURI('service/'); + $service_type = $request->getStr('serviceType'); - - try { - $service = AlmanacService::initializeNewService($service_type); - } catch (Exception $ex) { - return $this->buildServiceTypeResponse($cancel_uri); + $service_types = AlmanacServiceType::getAllServiceTypes(); + if (empty($service_types[$service_type])) { + return $this->buildServiceTypeResponse($list_uri); } - if ($service->isClusterService()) { - $this->requireApplicationCapability( - AlmanacManageClusterServicesCapability::CAPABILITY); - } - - $is_new = true; - - $title = pht('Create Service'); - $save_button = pht('Create Service'); + $engine + ->addContextParameter('serviceType', $service_type) + ->setServiceType($service_type); } - $v_name = $service->getName(); - $e_name = true; - $validation_exception = null; - - if ($is_new) { - $v_projects = array(); - } else { - $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( - $service->getPHID(), - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); - $v_projects = array_reverse($v_projects); - } - - if ($request->isFormPost() && $request->getStr('edit')) { - $v_name = $request->getStr('name'); - $v_view = $request->getStr('viewPolicy'); - $v_edit = $request->getStr('editPolicy'); - $v_projects = $request->getArr('projects'); - - $type_name = AlmanacServiceTransaction::TYPE_NAME; - $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; - $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; - - $xactions = array(); - - $xactions[] = id(new AlmanacServiceTransaction()) - ->setTransactionType($type_name) - ->setNewValue($v_name); - - $xactions[] = id(new AlmanacServiceTransaction()) - ->setTransactionType($type_view) - ->setNewValue($v_view); - - $xactions[] = id(new AlmanacServiceTransaction()) - ->setTransactionType($type_edit) - ->setNewValue($v_edit); - - $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - $xactions[] = id(new AlmanacServiceTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $proj_edge_type) - ->setNewValue(array('=' => array_fuse($v_projects))); - - $editor = id(new AlmanacServiceEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - try { - $editor->applyTransactions($service, $xactions); - - $service_uri = $service->getURI(); - return id(new AphrontRedirectResponse())->setURI($service_uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - $e_name = $ex->getShortMessage($type_name); - - $service->setViewPolicy($v_view); - $service->setEditPolicy($v_edit); - } - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($service) - ->execute(); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->addHiddenInput('edit', true) - ->addHiddenInput('serviceType', $service->getServiceType()) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Name')) - ->setName('name') - ->setValue($v_name) - ->setError($e_name)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('viewPolicy') - ->setPolicyObject($service) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('editPolicy') - ->setPolicyObject($service) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicies($policies)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Projects')) - ->setName('projects') - ->setValue($v_projects) - ->setDatasource(new PhabricatorProjectDatasource())) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($cancel_uri) - ->setValue($save_button)); - - $box = id(new PHUIObjectBoxView()) - ->setValidationException($validation_exception) - ->setHeaderText($title) - ->appendChild($form); - - $crumbs = $this->buildApplicationCrumbs(); - if ($is_new) { - $crumbs->addTextCrumb(pht('Create Service')); - } else { - $crumbs->addTextCrumb($service->getName(), $service_uri); - $crumbs->addTextCrumb(pht('Edit')); - } - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild( - array( - $box, - )); + return $engine->buildResponse(); } private function buildServiceTypeResponse($cancel_uri) { @@ -194,7 +47,6 @@ final class AlmanacServiceEditController pht('You have permission to create cluster services.'), pht('You do not have permission to create new cluster services.')); - $type_control = id(new AphrontFormRadioButtonControl()) ->setLabel(pht('Service Type')) ->setName('serviceType') @@ -239,14 +91,10 @@ final class AlmanacServiceEditController ->setHeaderText($title) ->setForm($form); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($box); } } diff --git a/src/applications/almanac/controller/AlmanacServiceListController.php b/src/applications/almanac/controller/AlmanacServiceListController.php index e09a199fb4..a46452beb1 100644 --- a/src/applications/almanac/controller/AlmanacServiceListController.php +++ b/src/applications/almanac/controller/AlmanacServiceListController.php @@ -8,45 +8,19 @@ final class AlmanacServiceListController } public function handleRequest(AphrontRequest $request) { - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($request->getURIData('queryKey')) - ->setSearchEngine(new AlmanacServiceSearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); + return id(new AlmanacServiceSearchEngine()) + ->setController($this) + ->buildResponse(); } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $can_create = $this->hasApplicationCapability( - AlmanacCreateServicesCapability::CAPABILITY); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create Service')) - ->setHref($this->getApplicationURI('service/edit/')) - ->setIcon('fa-plus-square') - ->setDisabled(!$can_create) - ->setWorkflow(!$can_create)); + id(new AlmanacServiceEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } - public function buildSideNavView() { - $viewer = $this->getViewer(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - id(new AlmanacServiceSearchEngine()) - ->setViewer($viewer) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; - } - - } diff --git a/src/applications/almanac/editor/AlmanacServiceEditEngine.php b/src/applications/almanac/editor/AlmanacServiceEditEngine.php new file mode 100644 index 0000000000..00e4e076de --- /dev/null +++ b/src/applications/almanac/editor/AlmanacServiceEditEngine.php @@ -0,0 +1,97 @@ +serviceType = $service_type; + return $this; + } + + public function getServiceType() { + return $this->serviceType; + } + + public function isEngineConfigurable() { + return false; + } + + public function getEngineName() { + return pht('Almanac Services'); + } + + public function getSummaryHeader() { + return pht('Edit Almanac Service Configurations'); + } + + public function getSummaryText() { + return pht('This engine is used to edit Almanac services.'); + } + + public function getEngineApplicationClass() { + return 'PhabricatorAlmanacApplication'; + } + + protected function newEditableObject() { + $service_type = $this->getServiceType(); + return AlmanacService::initializeNewService($service_type); + } + + protected function newObjectQuery() { + return new AlmanacServiceQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Service'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Service'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Service: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Service'); + } + + protected function getObjectCreateShortText() { + return pht('Create Service'); + } + + protected function getEditorURI() { + return '/almanac/service/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/almanac/service/'; + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + AlmanacCreateServicesCapability::CAPABILITY); + } + + protected function buildCustomEditFields($object) { + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the service.')) + ->setTransactionType(AlmanacServiceTransaction::TYPE_NAME) + ->setIsRequired(true) + ->setValue($object->getName()), + ); + } + +} diff --git a/src/applications/almanac/editor/AlmanacServiceEditor.php b/src/applications/almanac/editor/AlmanacServiceEditor.php index 1f6fe09cfa..8b0326f749 100644 --- a/src/applications/almanac/editor/AlmanacServiceEditor.php +++ b/src/applications/almanac/editor/AlmanacServiceEditor.php @@ -152,4 +152,28 @@ final class AlmanacServiceEditor return $errors; } + + protected function validateAllTransactions( + PhabricatorLiskDAO $object, + array $xactions) { + + $errors = parent::validateAllTransactions($object, $xactions); + + if ($object->isClusterService()) { + $can_manage = PhabricatorPolicyFilter::hasCapability( + $this->getActor(), + new PhabricatorAlmanacApplication(), + AlmanacManageClusterServicesCapability::CAPABILITY); + if (!$can_manage) { + $errors[] = new PhabricatorApplicationTransactionValidationError( + null, + pht('Restricted'), + pht('You do not have permission to manage cluster services.'), + null); + } + } + + return $errors; + } + } From 85bf04ea0292290ad821665436e24d22810d9fae Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 4 Mar 2016 16:14:27 -0800 Subject: [PATCH 39/41] Use EditEngine for AlmanacDevice Summary: Ref T10449. Modernize the AlmanacDevice code a bit. Test Plan: - Created a device. - Edited a device. - Listed devices. - Viewed a device. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10449 Differential Revision: https://secure.phabricator.com/D15399 --- src/__phutil_library_map__.php | 2 + .../PhabricatorAlmanacApplication.php | 2 +- .../controller/AlmanacDeviceController.php | 5 + .../AlmanacDeviceEditController.php | 157 +----------------- .../AlmanacDeviceListController.php | 38 +---- .../editor/AlmanacDeviceEditEngine.php | 85 ++++++++++ 6 files changed, 102 insertions(+), 187 deletions(-) create mode 100644 src/applications/almanac/editor/AlmanacDeviceEditEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 1bebc01152..0a5b5e7cd9 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -36,6 +36,7 @@ phutil_register_library_map(array( 'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php', 'AlmanacDeviceController' => 'applications/almanac/controller/AlmanacDeviceController.php', 'AlmanacDeviceEditController' => 'applications/almanac/controller/AlmanacDeviceEditController.php', + 'AlmanacDeviceEditEngine' => 'applications/almanac/editor/AlmanacDeviceEditEngine.php', 'AlmanacDeviceEditor' => 'applications/almanac/editor/AlmanacDeviceEditor.php', 'AlmanacDeviceListController' => 'applications/almanac/controller/AlmanacDeviceListController.php', 'AlmanacDeviceNameNgrams' => 'applications/almanac/storage/AlmanacDeviceNameNgrams.php', @@ -4045,6 +4046,7 @@ phutil_register_library_map(array( ), 'AlmanacDeviceController' => 'AlmanacController', 'AlmanacDeviceEditController' => 'AlmanacDeviceController', + 'AlmanacDeviceEditEngine' => 'PhabricatorEditEngine', 'AlmanacDeviceEditor' => 'AlmanacEditor', 'AlmanacDeviceListController' => 'AlmanacDeviceController', 'AlmanacDeviceNameNgrams' => 'PhabricatorSearchNgrams', diff --git a/src/applications/almanac/application/PhabricatorAlmanacApplication.php b/src/applications/almanac/application/PhabricatorAlmanacApplication.php index 43b6a2c3c5..2a9ef848ab 100644 --- a/src/applications/almanac/application/PhabricatorAlmanacApplication.php +++ b/src/applications/almanac/application/PhabricatorAlmanacApplication.php @@ -50,7 +50,7 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication { ), '(?Pdevice)/' => array( $this->getQueryRoutePattern() => 'AlmanacDeviceListController', - 'edit/(?:(?P\d+)/)?' => 'AlmanacDeviceEditController', + $this->getEditRoutePattern('edit/') => 'AlmanacDeviceEditController', 'view/(?P[^/]+)/' => 'AlmanacDeviceViewController', ), 'interface/' => array( diff --git a/src/applications/almanac/controller/AlmanacDeviceController.php b/src/applications/almanac/controller/AlmanacDeviceController.php index cdcb90c63a..442a0797e3 100644 --- a/src/applications/almanac/controller/AlmanacDeviceController.php +++ b/src/applications/almanac/controller/AlmanacDeviceController.php @@ -2,6 +2,11 @@ abstract class AlmanacDeviceController extends AlmanacController { + public function buildApplicationMenu() { + return $this->newApplicationMenu() + ->setSearchEngine(new AlmanacDeviceSearchEngine()); + } + protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); diff --git a/src/applications/almanac/controller/AlmanacDeviceEditController.php b/src/applications/almanac/controller/AlmanacDeviceEditController.php index 3bbc388838..f8674a8a04 100644 --- a/src/applications/almanac/controller/AlmanacDeviceEditController.php +++ b/src/applications/almanac/controller/AlmanacDeviceEditController.php @@ -4,160 +4,9 @@ final class AlmanacDeviceEditController extends AlmanacDeviceController { public function handleRequest(AphrontRequest $request) { - $viewer = $request->getViewer(); - - $list_uri = $this->getApplicationURI('device/'); - - $id = $request->getURIData('id'); - if ($id) { - $device = id(new AlmanacDeviceQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$device) { - return new Aphront404Response(); - } - - $is_new = false; - $device_uri = $device->getURI(); - $cancel_uri = $device_uri; - $title = pht('Edit Device'); - $save_button = pht('Save Changes'); - } else { - $this->requireApplicationCapability( - AlmanacCreateDevicesCapability::CAPABILITY); - - $device = AlmanacDevice::initializeNewDevice(); - $is_new = true; - - $cancel_uri = $list_uri; - $title = pht('Create Device'); - $save_button = pht('Create Device'); - } - - $v_name = $device->getName(); - $e_name = true; - $validation_exception = null; - - if ($is_new) { - $v_projects = array(); - } else { - $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( - $device->getPHID(), - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); - $v_projects = array_reverse($v_projects); - } - - if ($request->isFormPost()) { - $v_name = $request->getStr('name'); - $v_view = $request->getStr('viewPolicy'); - $v_edit = $request->getStr('editPolicy'); - $v_projects = $request->getArr('projects'); - - $type_name = AlmanacDeviceTransaction::TYPE_NAME; - $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; - $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; - - $xactions = array(); - - $xactions[] = id(new AlmanacDeviceTransaction()) - ->setTransactionType($type_name) - ->setNewValue($v_name); - - $xactions[] = id(new AlmanacDeviceTransaction()) - ->setTransactionType($type_view) - ->setNewValue($v_view); - - $xactions[] = id(new AlmanacDeviceTransaction()) - ->setTransactionType($type_edit) - ->setNewValue($v_edit); - - $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - $xactions[] = id(new AlmanacDeviceTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $proj_edge_type) - ->setNewValue(array('=' => array_fuse($v_projects))); - - $editor = id(new AlmanacDeviceEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - try { - $editor->applyTransactions($device, $xactions); - - $device_uri = $device->getURI(); - return id(new AphrontRedirectResponse())->setURI($device_uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - $e_name = $ex->getShortMessage($type_name); - - $device->setViewPolicy($v_view); - $device->setEditPolicy($v_edit); - } - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($device) - ->execute(); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Name')) - ->setName('name') - ->setValue($v_name) - ->setError($e_name)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('viewPolicy') - ->setPolicyObject($device) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicies($policies)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setName('editPolicy') - ->setPolicyObject($device) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicies($policies)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Projects')) - ->setName('projects') - ->setValue($v_projects) - ->setDatasource(new PhabricatorProjectDatasource())) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($cancel_uri) - ->setValue($save_button)); - - $box = id(new PHUIObjectBoxView()) - ->setValidationException($validation_exception) - ->setHeaderText($title) - ->setForm($form); - - $crumbs = $this->buildApplicationCrumbs(); - if ($is_new) { - $crumbs->addTextCrumb(pht('Create Device')); - } else { - $crumbs->addTextCrumb($device->getName(), $device_uri); - $crumbs->addTextCrumb(pht('Edit')); - } - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->appendChild( - array( - $box, - )); + return id(new AlmanacDeviceEditEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/almanac/controller/AlmanacDeviceListController.php b/src/applications/almanac/controller/AlmanacDeviceListController.php index 99ede8a181..6ab486ee8e 100644 --- a/src/applications/almanac/controller/AlmanacDeviceListController.php +++ b/src/applications/almanac/controller/AlmanacDeviceListController.php @@ -8,45 +8,19 @@ final class AlmanacDeviceListController } public function handleRequest(AphrontRequest $request) { - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($request->getURIData('queryKey')) - ->setSearchEngine(new AlmanacDeviceSearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); + return id(new AlmanacDeviceSearchEngine()) + ->setController($this) + ->buildResponse(); } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $can_create = $this->hasApplicationCapability( - AlmanacCreateDevicesCapability::CAPABILITY); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create Device')) - ->setHref($this->getApplicationURI('device/edit/')) - ->setIcon('fa-plus-square') - ->setDisabled(!$can_create) - ->setWorkflow(!$can_create)); + id(new AlmanacDeviceEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } - public function buildSideNavView() { - $viewer = $this->getViewer(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - id(new AlmanacDeviceSearchEngine()) - ->setViewer($viewer) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; - } - - } diff --git a/src/applications/almanac/editor/AlmanacDeviceEditEngine.php b/src/applications/almanac/editor/AlmanacDeviceEditEngine.php new file mode 100644 index 0000000000..f8ace15caf --- /dev/null +++ b/src/applications/almanac/editor/AlmanacDeviceEditEngine.php @@ -0,0 +1,85 @@ +getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Device'); + } + + protected function getObjectCreateShortText() { + return pht('Create Device'); + } + + protected function getEditorURI() { + return '/almanac/device/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/almanac/device/'; + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + AlmanacCreateDevicesCapability::CAPABILITY); + } + + protected function buildCustomEditFields($object) { + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the device.')) + ->setTransactionType(AlmanacDeviceTransaction::TYPE_NAME) + ->setIsRequired(true) + ->setValue($object->getName()), + ); + } + +} From 809646c8d2e962bea958fcd7e2b2a70034e947cf Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 4 Mar 2016 16:33:49 -0800 Subject: [PATCH 40/41] Unprototype Almanac Summary: Fixes T10449. Almanac doesn't do a whole lot for the average user, but is in good shape technically and works well, and exposing it in the cluster won't let installs destroy themselves now. Test Plan: Re-read documentation; grepped for `TODO` (there are a couple, but reasonable to push off); browsed around all the UI things (new two-column looks great), called API methods. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10449 Differential Revision: https://secure.phabricator.com/D15400 --- .../PhabricatorAlmanacApplication.php | 4 ---- src/docs/user/userguide/almanac.diviner | 23 +++++++++++-------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/applications/almanac/application/PhabricatorAlmanacApplication.php b/src/applications/almanac/application/PhabricatorAlmanacApplication.php index 2a9ef848ab..76caa3a06b 100644 --- a/src/applications/almanac/application/PhabricatorAlmanacApplication.php +++ b/src/applications/almanac/application/PhabricatorAlmanacApplication.php @@ -35,10 +35,6 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication { ); } - public function isPrototype() { - return true; - } - public function getRoutes() { return array( '/almanac/' => array( diff --git a/src/docs/user/userguide/almanac.diviner b/src/docs/user/userguide/almanac.diviner index 5a820c6ae2..b7c30cf77a 100644 --- a/src/docs/user/userguide/almanac.diviner +++ b/src/docs/user/userguide/almanac.diviner @@ -6,21 +6,24 @@ Using Almanac to manage devices and services. Overview ======== -IMPORTANT: Almanac is a prototype application. See -@{article:User Guide: Prototype Applications}. - Almanac is a device and service inventory application. It allows you to create lists of //devices// and //services// that humans and other applications can use to keep track of what is running where. +Almanac is an infrastructure application that will normally be used by +administrators to configure advanced Phabricator features. In most cases, +normal users will very rarely interact with Almanac directly. + At a very high level, Almanac can be thought of as a bit like a DNS server. Callers ask it for information about services, and it responds with details about which devices host those services. However, it can respond to a broader range of queries and provide more detailed responses than DNS alone can. -Today, the primary use cases for Almanac involve configuring Phabricator -itself: Almanac is used to configure Phabricator to operate in a cluster setup, -and to expose hardware to Drydock so it can run build and integration tasks. +Today, the primary use cases for Almanac are internal to Phabricator: + + - Providing a list of build servers to Drydock so it can run build and + integration tasks. + - Configuring Phabricator to operate in a cluster setup. Beyond internal uses, Almanac is a general-purpose service and device inventory application and can be used to configure and manage other types of service and @@ -37,13 +40,13 @@ introduction to Almanac concepts and a better idea of how the pieces fit together. In this scenario, we want to use Drydock to run some sort of build process. To -do this, Drydock needs hardware to run on. We're going to use Almanac to tell -Drydock about the hardware it should use. +do this, Drydock needs hardware to run on. We're going to use Almanac to give +Drydock a list of hosts it should use. In this scenario, Almanac will work a bit like a DNS server. When we're done, Drydock will be able to query Almanac for information about a service (like `build.mycompany.com`) and get back information about which hosts are part of -that service and where it should connect to. +that service and which addresses/ports it should connect to. Before getting started, we need to create a **network**. For simplicity, let's suppose everything will be connected through the public internet. If you @@ -52,7 +55,7 @@ haven't already, you'd create a "Public Internet" network first. Once we have a network, we create the actual physical or virtual hosts by launching instances in EC2, or racking and powering on some servers, or already having some hardware on hand we want to use. We set the hosts up normally and -connect them to the internet or network. +connect them to the internet (or another network). After the hosts exist, we add them to Almanac as **devices**, like `build001.mycompany.com`, `build002.mycompany.com`, and so on. In Almanac, From 2f14a59b1d818b88afd5ca41da2be0806e27ffa6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 4 Mar 2016 17:14:06 -0800 Subject: [PATCH 41/41] Unprototype Drydock Summary: Ref T10246. Drydock still has a few outstanding issues, but it's mostly minor UI stuff and the documentation has reasonable caveats about it. Broadly, I don't expect to make any major changes to Drydock in the future (i.e., all the fundamentals seem sound at this point) and it doesn't have any major technical debt or, like, obsolete APIs or anything. Test Plan: Saw "Drydock" as not-a-prototype in Applications. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10246 Differential Revision: https://secure.phabricator.com/D15401 --- .../drydock/application/PhabricatorDrydockApplication.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/applications/drydock/application/PhabricatorDrydockApplication.php b/src/applications/drydock/application/PhabricatorDrydockApplication.php index cb229e3410..fef2c843ff 100644 --- a/src/applications/drydock/application/PhabricatorDrydockApplication.php +++ b/src/applications/drydock/application/PhabricatorDrydockApplication.php @@ -30,10 +30,6 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication { return self::GROUP_UTILITIES; } - public function isPrototype() { - return true; - } - public function getHelpDocumentationArticles(PhabricatorUser $viewer) { return array( array(