From 2b1ac4fcec820c56decbfdd3228f0a39a03b5bd9 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 4 Mar 2016 15:44:24 -0800 Subject: [PATCH 01/57] Update Maniphest for PHUITwoColumnView Summary: Reworks Maniphest into a two column view. Moves priority and color to header, assignee to sidebar. quest points to header, and author to gutter. may be some confusion since priority only displays on open tickets. Test Plan: with and without description, custom fields, points, tablet, mobile and desktop. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15396 --- resources/celerity/map.php | 18 +-- .../constants/DifferentialRevisionStatus.php | 39 ++--- .../PhabricatorManiphestConfigOptions.php | 3 + .../constants/ManiphestTaskStatus.php | 31 ++-- .../ManiphestTaskDetailController.php | 139 ++++++++++-------- src/view/phui/PHUIHeaderView.php | 38 ++--- webroot/rsrc/css/font/phui-font-icon-base.css | 2 +- webroot/rsrc/css/phui/phui-header-view.css | 34 ++--- webroot/rsrc/css/phui/phui-tag-view.css | 20 +++ .../rsrc/css/phui/phui-two-column-view.css | 20 ++- 10 files changed, 180 insertions(+), 164 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index be8d5b9d1c..927c5d1eff 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'dd1447be', + 'core.pkg.css' => 'db1cd0bd', 'core.pkg.js' => '7d8faf57', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', @@ -111,7 +111,7 @@ return array( 'rsrc/css/font/font-aleo.css' => '8bdb2835', 'rsrc/css/font/font-awesome.css' => 'c43323c5', 'rsrc/css/font/font-lato.css' => 'c7ccd872', - 'rsrc/css/font/phui-font-icon-base.css' => 'ecbbb4c2', + 'rsrc/css/font/phui-font-icon-base.css' => '6449bce8', 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', 'rsrc/css/layout/phabricator-side-menu-view.css' => '3a3d9f41', 'rsrc/css/layout/phabricator-source-code-view.css' => 'cbeef983', @@ -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' => 'fc4acf14', + 'rsrc/css/phui/phui-header-view.css' => 'bfb9fed3', 'rsrc/css/phui/phui-hovercard.css' => 'de1a2119', 'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad', 'rsrc/css/phui/phui-icon.css' => '3f33ab57', @@ -153,9 +153,9 @@ return array( 'rsrc/css/phui/phui-segment-bar-view.css' => '46342871', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => '37309046', - 'rsrc/css/phui/phui-tag-view.css' => '9d5d4400', + 'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2', 'rsrc/css/phui/phui-timeline-view.css' => '2efceff8', - 'rsrc/css/phui/phui-two-column-view.css' => 'd0ad8c10', + 'rsrc/css/phui/phui-two-column-view.css' => '097630a3', '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', @@ -815,12 +815,12 @@ return array( 'phui-document-view-css' => '9c71d2bf', 'phui-document-view-pro-css' => '92d5b648', 'phui-feed-story-css' => '04aec08f', - 'phui-font-icon-base-css' => 'ecbbb4c2', + 'phui-font-icon-base-css' => '6449bce8', 'phui-fontkit-css' => '9cda225e', 'phui-form-css' => 'aac1d51d', 'phui-form-view-css' => '4a1a0f5e', 'phui-head-thing-view-css' => '11731da0', - 'phui-header-view-css' => 'fc4acf14', + 'phui-header-view-css' => 'bfb9fed3', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'de1a2119', 'phui-icon-set-selector-css' => '1ab67aad', @@ -840,10 +840,10 @@ return array( 'phui-segment-bar-view-css' => '46342871', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => '37309046', - 'phui-tag-view-css' => '9d5d4400', + 'phui-tag-view-css' => '6bbd83e2', 'phui-theme-css' => '027ba77e', 'phui-timeline-view-css' => '2efceff8', - 'phui-two-column-view-css' => 'd0ad8c10', + 'phui-two-column-view-css' => '097630a3', 'phui-workboard-color-css' => 'ac6fe6a7', 'phui-workboard-view-css' => 'e6d89647', 'phui-workcard-view-css' => '3646fb96', diff --git a/src/applications/differential/constants/DifferentialRevisionStatus.php b/src/applications/differential/constants/DifferentialRevisionStatus.php index 50bc903c40..4f332838ac 100644 --- a/src/applications/differential/constants/DifferentialRevisionStatus.php +++ b/src/applications/differential/constants/DifferentialRevisionStatus.php @@ -8,10 +8,11 @@ final class DifferentialRevisionStatus extends Phobject { - const COLOR_STATUS_DEFAULT = 'status'; - const COLOR_STATUS_DARK = 'status-dark'; - const COLOR_STATUS_GREEN = 'status-green'; - const COLOR_STATUS_RED = 'status-red'; + const COLOR_STATUS_DEFAULT = 'bluegrey'; + const COLOR_STATUS_DARK = 'indigo'; + const COLOR_STATUS_BLUE = 'blue'; + const COLOR_STATUS_GREEN = 'green'; + const COLOR_STATUS_RED = 'red'; public static function getRevisionStatusColor($status) { $default = self::COLOR_STATUS_DEFAULT; @@ -30,7 +31,7 @@ final class DifferentialRevisionStatus extends Phobject { ArcanistDifferentialRevisionStatus::ABANDONED => self::COLOR_STATUS_DARK, ArcanistDifferentialRevisionStatus::IN_PREPARATION => - self::COLOR_STATUS_DARK, + self::COLOR_STATUS_BLUE, ); return idx($map, $status, $default); } @@ -42,38 +43,30 @@ final class DifferentialRevisionStatus extends Phobject { ArcanistDifferentialRevisionStatus::NEEDS_REVIEW => 'fa-square-o bluegrey', ArcanistDifferentialRevisionStatus::NEEDS_REVISION => - 'fa-refresh red', + 'fa-refresh', ArcanistDifferentialRevisionStatus::CHANGES_PLANNED => - 'fa-headphones red', + 'fa-headphones', ArcanistDifferentialRevisionStatus::ACCEPTED => - 'fa-check green', + 'fa-check', ArcanistDifferentialRevisionStatus::CLOSED => 'fa-check-square-o', ArcanistDifferentialRevisionStatus::ABANDONED => - 'fa-check-square-o', + 'fa-plane', ArcanistDifferentialRevisionStatus::IN_PREPARATION => - 'fa-question-circle blue', + 'fa-question-circle', ); return idx($map, $status, $default); } public static function renderFullDescription($status) { - $color = self::getRevisionStatusColor($status); $status_name = ArcanistDifferentialRevisionStatus::getNameForRevisionStatus($status); - $img = id(new PHUIIconView()) - ->setIcon(self::getRevisionStatusIcon($status)); - - $tag = phutil_tag( - 'span', - array( - 'class' => 'phui-header-status phui-header-'.$color, - ), - array( - $img, - $status_name, - )); + $tag = id(new PHUITagView()) + ->setName($status_name) + ->setIcon(self::getRevisionStatusIcon($status)) + ->setShade(self::getRevisionStatusColor($status)) + ->setType(PHUITagView::TYPE_SHADE); return $tag; } diff --git a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php index 724309175d..dc140d380a 100644 --- a/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php +++ b/src/applications/maniphest/config/PhabricatorManiphestConfigOptions.php @@ -77,6 +77,7 @@ final class PhabricatorManiphestConfigOptions 'name.full' => pht('Closed, Resolved'), 'closed' => true, 'special' => ManiphestTaskStatus::SPECIAL_CLOSED, + 'transaction.icon' => 'fa-check-circle', 'prefixes' => array( 'closed', 'closes', @@ -97,6 +98,7 @@ final class PhabricatorManiphestConfigOptions 'wontfix' => array( 'name' => pht('Wontfix'), 'name.full' => pht('Closed, Wontfix'), + 'transaction.icon' => 'fa-ban', 'closed' => true, 'prefixes' => array( 'wontfix', @@ -110,6 +112,7 @@ final class PhabricatorManiphestConfigOptions 'invalid' => array( 'name' => pht('Invalid'), 'name.full' => pht('Closed, Invalid'), + 'transaction.icon' => 'fa-minus-circle', 'closed' => true, 'claim' => false, 'prefixes' => array( diff --git a/src/applications/maniphest/constants/ManiphestTaskStatus.php b/src/applications/maniphest/constants/ManiphestTaskStatus.php index 3a839d8fac..eeeb4cf165 100644 --- a/src/applications/maniphest/constants/ManiphestTaskStatus.php +++ b/src/applications/maniphest/constants/ManiphestTaskStatus.php @@ -82,29 +82,22 @@ final class ManiphestTaskStatus extends ManiphestConstants { return self::getStatusAttribute($status, 'name', pht('Unknown Status')); } - public static function renderFullDescription($status) { + public static function renderFullDescription($status, $priority) { if (self::isOpenStatus($status)) { - $color = 'status'; - $icon_color = 'bluegrey'; + $name = pht('%s, %s', self::getTaskStatusFullName($status), $priority); + $color = 'grey'; + $icon = 'fa-square-o'; } else { - $color = 'status-dark'; - $icon_color = ''; + $name = self::getTaskStatusFullName($status); + $color = 'indigo'; + $icon = 'fa-check-square-o'; } - $icon = self::getStatusIcon($status); - - $img = id(new PHUIIconView()) - ->setIcon($icon.' '.$icon_color); - - $tag = phutil_tag( - 'span', - array( - 'class' => 'phui-header-status phui-header-'.$color, - ), - array( - $img, - self::getTaskStatusFullName($status), - )); + $tag = id(new PHUITagView()) + ->setName($name) + ->setIcon($icon) + ->setType(PHUITagView::TYPE_SHADE) + ->setShade($color); return $tag; } diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index a15b8f4594..cbb55413ad 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -65,24 +65,16 @@ final class ManiphestTaskDetailController extends ManiphestController { new ManiphestTransactionQuery(), $engine); - $actions = $this->buildActionView($task); - $monogram = $task->getMonogram(); $crumbs = $this->buildApplicationCrumbs() - ->addTextCrumb($monogram, '/'.$monogram); + ->addTextCrumb($monogram) + ->setBorder(true); $header = $this->buildHeaderView($task); - $properties = $this->buildPropertyView( - $task, $field_list, $edges, $actions, $handles); + $details = $this->buildPropertyView($task, $field_list, $edges, $handles); $description = $this->buildDescriptionView($task, $engine); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); - - if ($description) { - $object_box->addPropertyList($description); - } + $actions = $this->buildActionView($task); + $properties = $this->buildPropertyListView($task, $handles); $title = pht('%s %s', $monogram, $task->getTitle()); @@ -93,6 +85,17 @@ final class ManiphestTaskDetailController extends ManiphestController { $timeline->setQuoteRef($monogram); $comment_view->setTransactionTimeline($timeline); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn(array( + $timeline, + $comment_view, + )) + ->addPropertySection(pht('DETAILS'), $details) + ->addPropertySection(pht('DESCRIPTION'), $description) + ->setPropertyList($properties) + ->setActionList($actions); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) @@ -102,10 +105,9 @@ final class ManiphestTaskDetailController extends ManiphestController { )) ->appendChild( array( - $object_box, - $timeline, - $comment_view, - )); + $view, + )); + } private function buildHeaderView(ManiphestTask $task) { @@ -114,11 +116,34 @@ final class ManiphestTaskDetailController extends ManiphestController { ->setUser($this->getRequest()->getUser()) ->setPolicyObject($task); - $status = $task->getStatus(); - $status_name = ManiphestTaskStatus::renderFullDescription($status); + $priority_name = ManiphestTaskPriority::getTaskPriorityName( + $task->getPriority()); + $priority_color = ManiphestTaskPriority::getTaskPriorityColor( + $task->getPriority()); + $status = $task->getStatus(); + $status_name = ManiphestTaskStatus::renderFullDescription( + $status, $priority_name, $priority_color); $view->addProperty(PHUIHeaderView::PROPERTY_STATUS, $status_name); + $view->setHeaderIcon(ManiphestTaskStatus::getStatusIcon( + $task->getStatus()).' '.$priority_color); + + if (ManiphestTaskPoints::getIsEnabled()) { + $points = $task->getPoints(); + if ($points !== null) { + $points_name = pht('%s %s', + $task->getPoints(), + ManiphestTaskPoints::getPointsLabel()); + $tag = id(new PHUITagView()) + ->setName($points_name) + ->setShade('blue') + ->setType(PHUITagView::TYPE_SHADE); + + $view->addTag($tag); + } + } + return $view; } @@ -198,45 +223,11 @@ final class ManiphestTaskDetailController extends ManiphestController { ManiphestTask $task, PhabricatorCustomFieldList $field_list, array $edges, - PhabricatorActionListView $actions, $handles) { $viewer = $this->getRequest()->getUser(); - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($task) - ->setActionList($actions); - - $owner_phid = $task->getOwnerPHID(); - if ($owner_phid) { - $assigned_to = $handles - ->renderHandle($owner_phid) - ->setShowHovercard(true); - } else { - $assigned_to = phutil_tag('em', array(), pht('None')); - } - - $view->addProperty(pht('Assigned To'), $assigned_to); - - $view->addProperty( - pht('Priority'), - ManiphestTaskPriority::getTaskPriorityName($task->getPriority())); - - $author = $handles - ->renderHandle($task->getAuthorPHID()) - ->setShowHovercard(true); - - $view->addProperty(pht('Author'), $author); - - if (ManiphestTaskPoints::getIsEnabled()) { - $points = $task->getPoints(); - if ($points !== null) { - $view->addProperty( - ManiphestTaskPoints::getPointsLabel(), - $task->getPoints()); - } - } + ->setUser($viewer); $source = $task->getOriginalEmailSource(); if ($source) { @@ -304,13 +295,46 @@ final class ManiphestTaskDetailController extends ManiphestController { phutil_implode_html(phutil_tag('br'), $revisions_commits)); } - $view->invokeWillRenderEvent(); - $field_list->appendFieldsToPropertyList( $task, $viewer, $view); + if ($view->hasAnyProperties()) { + return $view; + } + + return null; + } + + private function buildPropertyListView(ManiphestTask $task, $handles) { + $viewer = $this->getRequest()->getUser(); + $view = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setObject($task); + + $view->invokeWillRenderEvent(); + + $owner_phid = $task->getOwnerPHID(); + if ($owner_phid) { + $assigned_to = $handles + ->renderHandle($owner_phid) + ->setShowHovercard(true); + } else { + $assigned_to = phutil_tag('em', array(), pht('None')); + } + + $view->addProperty(pht('Assigned To'), $assigned_to); + + $author_phid = $task->getAuthorPHID(); + $author = $handles + ->renderHandle($author_phid) + ->setShowHovercard(true); + + $date = phabricator_datetime($task->getDateCreated(), $viewer); + + $view->addProperty(pht('Author'), $author); + return $view; } @@ -321,9 +345,6 @@ final class ManiphestTaskDetailController extends ManiphestController { $section = null; if (strlen($task->getDescription())) { $section = new PHUIPropertyListView(); - $section->addSectionHeader( - pht('Description'), - PHUIPropertyListView::ICON_SUMMARY); $section->addTextContent( phutil_tag( 'div', diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php index 0e12bc9abe..0e76aafcd7 100644 --- a/src/view/phui/PHUIHeaderView.php +++ b/src/view/phui/PHUIHeaderView.php @@ -116,25 +116,17 @@ final class PHUIHeaderView extends AphrontTagView { } public function setStatus($icon, $color, $name) { - $header_class = 'phui-header-status'; - if ($color) { - $icon = $icon.' '.$color; - $header_class = $header_class.'-'.$color; + // TODO: Normalize "closed/archived" to constants. + if ($color == 'dark') { + $color = PHUITagView::COLOR_INDIGO; } - $img = id(new PHUIIconView()) - ->setIcon($icon); - - $tag = phutil_tag( - 'span', - array( - 'class' => "phui-header-status {$header_class}", - ), - array( - $img, - $name, - )); + $tag = id(new PHUITagView()) + ->setName($name) + ->setIcon($icon) + ->setShade($color) + ->setType(PHUITagView::TYPE_SHADE); return $this->addProperty(self::PROPERTY_STATUS, $tag); } @@ -285,7 +277,7 @@ final class PHUIHeaderView extends AphrontTagView { $this->buttonBar); } - if ($this->actionIcons || $this->tags) { + if ($this->actionIcons) { $action_list = array(); if ($this->actionIcons) { foreach ($this->actionIcons as $icon) { @@ -297,14 +289,6 @@ final class PHUIHeaderView extends AphrontTagView { $icon); } } - if ($this->tags) { - $action_list[] = phutil_tag( - 'li', - array( - 'class' => 'phui-header-action-tag', - ), - array_interleave(' ', $this->tags)); - } $right[] = phutil_tag( 'ul', array( @@ -379,6 +363,10 @@ final class PHUIHeaderView extends AphrontTagView { $property_list[] = $this->renderPolicyProperty($this->policyObject); } + if ($this->tags) { + $property_list[] = $this->tags; + } + $left[] = phutil_tag( 'div', array( diff --git a/webroot/rsrc/css/font/phui-font-icon-base.css b/webroot/rsrc/css/font/phui-font-icon-base.css index 2ddd423d3c..23194b3bda 100644 --- a/webroot/rsrc/css/font/phui-font-icon-base.css +++ b/webroot/rsrc/css/font/phui-font-icon-base.css @@ -148,7 +148,7 @@ } .phui-icon-view.lightgreytext, .phui-icon-view.grey { - color: {$lightgreytext}; + color: rgba({$alphagrey},0.3); } /* Hovers */ diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index cd4b7b1238..ddfda01173 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -175,7 +175,8 @@ body .phui-header-shell.phui-bleed-header margin-top: 8px; } -.phui-header-subheader .phui-icon-view { +.phui-header-subheader .phui-tag-view .phui-icon-view, +.phui-header-subheader .policy-header-callout .phui-icon-view { display: inline-block; margin: -2px 4px -2px 0; font-size: 15px; @@ -187,11 +188,18 @@ body .phui-header-shell.phui-bleed-header } .policy-header-callout, -.phui-header-subheader .phui-header-status { +.phui-header-subheader .phui-tag-core { padding: 3px 9px; border-radius: 3px; - background: rgba({$alphablue}, 0.08); + background: rgba({$alphablue}, 0.1); margin-right: 8px; + -webkit-font-smoothing: auto; + border-color: transparent; +} + +.phui-header-subheader .phui-tag-type-shade .phui-tag-core { + border: none; + -webkit-font-smoothing: auto; } .policy-header-callout.policy-adjusted-weaker { @@ -221,26 +229,6 @@ body .phui-header-shell.phui-bleed-header color: {$sh-orangetext}; } -.phui-header-subheader .phui-header-status-dark { - color: {$sh-indigotext}; - background: {$sh-indigobackground}; - margin-right: 8px; -} - -.phui-header-subheader .phui-header-status-dark .phui-icon-view { - color: {$sh-indigotext}; -} - -.phui-header-subheader .phui-header-status-red { - color: {$sh-redtext}; - background: {$sh-redbackground}; -} - -.phui-header-subheader .phui-header-status-green { - color: {$sh-greentext}; - background: {$sh-greenbackground}; -} - .phui-header-action-links .phui-mobile-menu { display: none; } diff --git a/webroot/rsrc/css/phui/phui-tag-view.css b/webroot/rsrc/css/phui/phui-tag-view.css index a68ed19484..729c4719e5 100644 --- a/webroot/rsrc/css/phui/phui-tag-view.css +++ b/webroot/rsrc/css/phui/phui-tag-view.css @@ -257,6 +257,26 @@ a.phui-tag-view:hover.phui-tag-shade-blue .phui-tag-core, border-color: {$sh-blueborder}; } +/* - Sky ------------------------------------------------------------------- */ + +.phui-tag-shade-sky .phui-tag-core, +.jx-tokenizer-token.sky { + background: #E0F0FA; + border-color: {$sh-lightblueborder}; + color: {$sh-bluetext}; +} + +.phui-tag-shade-sky .phui-icon-view, +.jx-tokenizer-token.sky .phui-icon-view, +.jx-tokenizer-token.sky .jx-tokenizer-x { + color: {$sh-blueicon}; +} + +a.phui-tag-view:hover.phui-tag-shade-sky .phui-tag-core, +.jx-tokenizer-token.sky:hover { + border-color: {$sh-blueborder}; +} + /* - Indigo ----------------------------------------------------------------- */ .phui-tag-shade-indigo .phui-tag-core, diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index 6c36aa8bd1..bf1f9a76e4 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -8,6 +8,10 @@ margin-bottom: 24px; } +.device .phui-two-column-view .phui-two-column-header { + margin-bottom: 12px; +} + .phui-two-column-view.with-subheader .phui-two-column-header { margin-bottom: 0; } @@ -99,6 +103,10 @@ margin: 0; } +.phui-main-column > .phui-timeline-view { + border-top: 1px solid {$thinblueborder}; +} + /* Main Column Properties */ .device-desktop .phui-main-column .phui-property-list-key { @@ -144,7 +152,13 @@ padding: 0 4px; } -.device-desktop .phui-two-column-properties .phui-property-list-container { +.device .phui-two-column-properties .phui-property-list-stacked + .phui-property-list-properties .phui-property-list-value { + margin-bottom: 12px; +} + +.device-desktop .phui-two-column-properties .phui-property-list-container, +.device .phui-two-column-properties .phui-property-list-container { padding: 0; } @@ -155,10 +169,6 @@ 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; } From fec1a154d54c6af72af0b76c386863e8da6c4826 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Fri, 4 Mar 2016 18:34:37 -0800 Subject: [PATCH 02/57] Expand scope of addActionItem in PHUIHeaderView Summary: Gives a bit more flexibility to add anything to the right side of PHUIHeaderView. Test Plan: Test Maniphest, Workboards, Project Home, Differential. Grep for `addActionIcon` use. Fixes T10518 Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T10518 Differential Revision: https://secure.phabricator.com/D15402 --- resources/celerity/map.php | 10 +++++----- .../controller/ConpherenceWidgetController.php | 2 +- ...habricatorDashboardPanelRenderingEngine.php | 4 ++-- .../PhabricatorDashboardQueryPanelType.php | 2 +- .../PhabricatorHomeMainController.php | 2 +- src/view/phui/PHUIHeaderView.php | 18 +++++++++--------- src/view/phui/PHUIWorkpanelView.php | 10 +++++----- webroot/rsrc/css/phui/phui-header-view.css | 8 ++++---- webroot/rsrc/css/phui/phui-two-column-view.css | 2 +- 9 files changed, 29 insertions(+), 29 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 927c5d1eff..2b812d0ddf 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'db1cd0bd', + 'core.pkg.css' => 'c0f196d2', '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' => 'bfb9fed3', + 'rsrc/css/phui/phui-header-view.css' => '32e71367', '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' => '37309046', 'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2', 'rsrc/css/phui/phui-timeline-view.css' => '2efceff8', - 'rsrc/css/phui/phui-two-column-view.css' => '097630a3', + 'rsrc/css/phui/phui-two-column-view.css' => 'ecd7ec62', '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', @@ -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' => 'bfb9fed3', + 'phui-header-view-css' => '32e71367', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'de1a2119', 'phui-icon-set-selector-css' => '1ab67aad', @@ -843,7 +843,7 @@ return array( 'phui-tag-view-css' => '6bbd83e2', 'phui-theme-css' => '027ba77e', 'phui-timeline-view-css' => '2efceff8', - 'phui-two-column-view-css' => '097630a3', + 'phui-two-column-view-css' => 'ecd7ec62', 'phui-workboard-color-css' => 'ac6fe6a7', 'phui-workboard-view-css' => 'e6d89647', 'phui-workcard-view-css' => '3646fb96', diff --git a/src/applications/conpherence/controller/ConpherenceWidgetController.php b/src/applications/conpherence/controller/ConpherenceWidgetController.php index e6707577e9..af9d799519 100644 --- a/src/applications/conpherence/controller/ConpherenceWidgetController.php +++ b/src/applications/conpherence/controller/ConpherenceWidgetController.php @@ -76,7 +76,7 @@ final class ConpherenceWidgetController extends ConpherenceController { ), id(new PHUIHeaderView()) ->setHeader($header) - ->addActionIcon($new_icon)); + ->addActionItem($new_icon)); $user = $this->getRequest()->getUser(); // now the widget bodies $widgets[] = javelin_tag( diff --git a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php index 57877783bb..22ab5b20bf 100644 --- a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php +++ b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php @@ -253,7 +253,7 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { ->setIcon('fa-pencil') ->setWorkflow(true) ->setHref((string)$edit_uri); - $header->addActionIcon($action_edit); + $header->addActionItem($action_edit); if ($dashboard_id) { $uri = id(new PhutilURI( @@ -263,7 +263,7 @@ final class PhabricatorDashboardPanelRenderingEngine extends Phobject { ->setIcon('fa-trash-o') ->setHref((string)$uri) ->setWorkflow(true); - $header->addActionIcon($action_remove); + $header->addActionItem($action_remove); } return $header; } diff --git a/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php b/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php index 92195aa8f5..76e6e8432b 100644 --- a/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php +++ b/src/applications/dashboard/paneltype/PhabricatorDashboardQueryPanelType.php @@ -119,7 +119,7 @@ final class PhabricatorDashboardQueryPanelType $icon = id(new PHUIIconView()) ->setIcon('fa-search') ->setHref($href); - $header->addActionIcon($icon); + $header->addActionItem($icon); return $header; } diff --git a/src/applications/home/controller/PhabricatorHomeMainController.php b/src/applications/home/controller/PhabricatorHomeMainController.php index 1b8557fc7f..e53c4fac8d 100644 --- a/src/applications/home/controller/PhabricatorHomeMainController.php +++ b/src/applications/home/controller/PhabricatorHomeMainController.php @@ -322,7 +322,7 @@ final class PhabricatorHomeMainController extends PhabricatorHomeController { ->setHref($href); $header = id(new PHUIHeaderView()) ->setHeader($title) - ->addActionIcon($icon); + ->addActionItem($icon); return $header; } diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php index 0e76aafcd7..8111944c4d 100644 --- a/src/view/phui/PHUIHeaderView.php +++ b/src/view/phui/PHUIHeaderView.php @@ -20,7 +20,7 @@ final class PHUIHeaderView extends AphrontTagView { private $buttonBar = null; private $policyObject; private $epoch; - private $actionIcons = array(); + private $actionItems = array(); private $badges = array(); private $href; private $actionList; @@ -105,8 +105,8 @@ final class PHUIHeaderView extends AphrontTagView { return $this; } - public function addActionIcon(PHUIIconView $action) { - $this->actionIcons[] = $action; + public function addActionItem($action) { + $this->actionItems[] = $action; return $this; } @@ -277,16 +277,16 @@ final class PHUIHeaderView extends AphrontTagView { $this->buttonBar); } - if ($this->actionIcons) { + if ($this->actionItems) { $action_list = array(); - if ($this->actionIcons) { - foreach ($this->actionIcons as $icon) { + if ($this->actionItems) { + foreach ($this->actionItems as $item) { $action_list[] = phutil_tag( 'li', array( - 'class' => 'phui-header-action-icon', + 'class' => 'phui-header-action-item', ), - $icon); + $item); } } $right[] = phutil_tag( @@ -346,7 +346,7 @@ final class PHUIHeaderView extends AphrontTagView { )); } - if ($this->properties || $this->policyObject) { + if ($this->properties || $this->policyObject || $this->tags) { $property_list = array(); foreach ($this->properties as $type => $property) { switch ($type) { diff --git a/src/view/phui/PHUIWorkpanelView.php b/src/view/phui/PHUIWorkpanelView.php index b94b423ced..911d38c2e3 100644 --- a/src/view/phui/PHUIWorkpanelView.php +++ b/src/view/phui/PHUIWorkpanelView.php @@ -83,16 +83,16 @@ final class PHUIWorkpanelView extends AphrontTagView { ->setHeader($this->header) ->setSubheader($this->subheader); - if ($this->headerIcon) { - $header->setHeaderIcon($this->headerIcon); + foreach ($this->headerActions as $action) { + $header->addActionItem($action); } if ($this->headerTag) { - $header->addTag($this->headerTag); + $header->addActionItem($this->headerTag); } - foreach ($this->headerActions as $action) { - $header->addActionIcon($action); + if ($this->headerIcon) { + $header->setHeaderIcon($this->headerIcon); } $href = $this->getHref(); diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index ddfda01173..b75715a7ee 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -197,8 +197,11 @@ body .phui-header-shell.phui-bleed-header border-color: transparent; } + +.phui-header-subheader .phui-tag-view, .phui-header-subheader .phui-tag-type-shade .phui-tag-core { border: none; + font-weight: normal; -webkit-font-smoothing: auto; } @@ -246,12 +249,9 @@ body .phui-header-shell.phui-bleed-header float: right; } -.phui-header-action-list li.phui-header-action-icon { +.phui-header-action-list .phui-header-action-item .phui-icon-view { height: 18px; width: 16px; -} - -.phui-header-action-list .phui-header-action-icon .phui-icon-view { font-size: 16px; line-height: 20px; display: block; diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index bf1f9a76e4..769eaf84e4 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -103,7 +103,7 @@ margin: 0; } -.phui-main-column > .phui-timeline-view { +.phui-main-column > .phui-timeline-view:first-child { border-top: 1px solid {$thinblueborder}; } From 5a43e2040eeb1fa7c9f5006c57fad50aa72a1c23 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 5 Mar 2016 15:25:06 +0000 Subject: [PATCH 03/57] Fix header tag on Hovercards Summary: Switch to new method. Test Plan: Hover over task, see tag in correct place. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15403 --- resources/celerity/map.php | 6 +++--- .../controller/PassphraseCredentialViewController.php | 3 ++- .../paste/controller/PhabricatorPasteViewController.php | 3 ++- src/view/phui/PHUIHovercardView.php | 2 +- webroot/rsrc/css/phui/phui-header-view.css | 4 ++++ 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 2b812d0ddf..d294ca82a1 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'c0f196d2', + 'core.pkg.css' => '3d58b25a', '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' => '32e71367', + 'rsrc/css/phui/phui-header-view.css' => '26cffd3d', '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' => '32e71367', + 'phui-header-view-css' => '26cffd3d', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'de1a2119', 'phui-icon-set-selector-css' => '1ab67aad', diff --git a/src/applications/passphrase/controller/PassphraseCredentialViewController.php b/src/applications/passphrase/controller/PassphraseCredentialViewController.php index 5ec2fe7c07..fa52681d82 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialViewController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialViewController.php @@ -59,7 +59,8 @@ final class PassphraseCredentialViewController extends PassphraseController { $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($credential->getName()) - ->setPolicyObject($credential); + ->setPolicyObject($credential) + ->setHeaderIcon('fa-user-secret'); if ($credential->getIsDestroyed()) { $header->setStatus('fa-ban', 'red', pht('Destroyed')); diff --git a/src/applications/paste/controller/PhabricatorPasteViewController.php b/src/applications/paste/controller/PhabricatorPasteViewController.php index f259cdc6af..18d3ad1fb6 100644 --- a/src/applications/paste/controller/PhabricatorPasteViewController.php +++ b/src/applications/paste/controller/PhabricatorPasteViewController.php @@ -110,7 +110,8 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { ->setHeader($title) ->setUser($this->getRequest()->getUser()) ->setStatus($header_icon, $header_color, $header_name) - ->setPolicyObject($paste); + ->setPolicyObject($paste) + ->setHeaderIcon('fa-clipboard'); return $header; } diff --git a/src/view/phui/PHUIHovercardView.php b/src/view/phui/PHUIHovercardView.php index e5c89bb8fd..115964f191 100644 --- a/src/view/phui/PHUIHovercardView.php +++ b/src/view/phui/PHUIHovercardView.php @@ -106,7 +106,7 @@ final class PHUIHovercardView extends AphrontTagView { $header->setHeader($title); if ($this->tags) { foreach ($this->tags as $tag) { - $header->addTag($tag); + $header->addActionItem($tag); } } diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index b75715a7ee..a61a73fc88 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -175,6 +175,10 @@ body .phui-header-shell.phui-bleed-header margin-top: 8px; } +.phui-header-subheader .phui-icon-view { + margin-right: 4px; +} + .phui-header-subheader .phui-tag-view .phui-icon-view, .phui-header-subheader .policy-header-callout .phui-icon-view { display: inline-block; From f6127f5835dae398d691216eec6919049fef8364 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 5 Mar 2016 08:23:40 -0800 Subject: [PATCH 04/57] Convert Almanac Binding View to two columns Summary: Moves over to the new layout. Fixes T10521 Test Plan: Make a binding, view page, add some properties. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T10521 Differential Revision: https://secure.phabricator.com/D15404 --- .../AlmanacBindingViewController.php | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/applications/almanac/controller/AlmanacBindingViewController.php b/src/applications/almanac/controller/AlmanacBindingViewController.php index b75565525c..32915593cf 100644 --- a/src/applications/almanac/controller/AlmanacBindingViewController.php +++ b/src/applications/almanac/controller/AlmanacBindingViewController.php @@ -26,26 +26,23 @@ final class AlmanacBindingViewController $title = pht('Binding %s', $binding->getID()); - $property_list = $this->buildPropertyList($binding); - $action_list = $this->buildActionList($binding); - $property_list->setActionList($action_list); + $properties = $this->buildPropertyList($binding); + $details = $this->buildPropertySection($binding); + $actions = $this->buildActionList($binding); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($title) - ->setPolicyObject($binding); + ->setPolicyObject($binding) + ->setHeaderIcon('fa-object-group'); if ($binding->getIsDisabled()) { $header->setStatus('fa-ban', 'red', pht('Disabled')); } - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($property_list); - + $issue = null; if ($binding->getService()->isClusterService()) { - $this->addClusterMessage( - $box, + $issue = $this->addClusterMessage( pht('The service for this binding is a cluster service.'), pht( 'The service for this binding is a cluster service. You do not '. @@ -56,24 +53,34 @@ final class AlmanacBindingViewController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($service->getName(), $service_uri); $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); $timeline = $this->buildTransactionTimeline( $binding, new AlmanacBindingTransactionQuery()); $timeline->setShouldTerminate(true); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn(array( + $issue, + $this->buildAlmanacPropertiesTable($binding), + $timeline, + )) + ->setPropertyList($properties) + ->addPropertySection(pht('DETAILS'), $details) + ->setActionList($actions); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $box, - $this->buildAlmanacPropertiesTable($binding), - $timeline, + $view, )); } - private function buildPropertyList(AlmanacBinding $binding) { + private function buildPropertySection(AlmanacBinding $binding) { $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) @@ -98,6 +105,17 @@ final class AlmanacBindingViewController return $properties; } + private function buildPropertyList(AlmanacBinding $binding) { + $viewer = $this->getViewer(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setObject($binding); + $properties->invokeWillRenderEvent(); + + return $properties; + } + private function buildActionList(AlmanacBinding $binding) { $viewer = $this->getViewer(); $id = $binding->getID(); From 010b7811b44fc935ff7bd27a05cf2975caae0ef8 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 5 Mar 2016 20:34:56 +0000 Subject: [PATCH 05/57] Convert Applications to two column view Summary: Converts the meta applications application view layout to two column Test Plan: click through "Configure" on each application, set up some emails. uninstall Phrequent Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15405 --- ...ricatorApplicationDetailViewController.php | 70 ++++++++++++------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php index 19aad0e6a5..07dea613db 100644 --- a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php +++ b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php @@ -24,11 +24,13 @@ final class PhabricatorApplicationDetailViewController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($selected->getName()); + $crumbs->setBorder(true); $header = id(new PHUIHeaderView()) ->setHeader($title) ->setUser($viewer) - ->setPolicyObject($selected); + ->setPolicyObject($selected) + ->setHeaderIcon($selected->getIcon()); if ($selected->isInstalled()) { $header->setStatus('fa-check', 'bluegrey', pht('Installed')); @@ -37,11 +39,8 @@ final class PhabricatorApplicationDetailViewController } $actions = $this->buildActionView($viewer, $selected); - $properties = $this->buildPropertyView($selected, $actions); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $details = $this->buildPropertySectionView($selected); + $policies = $this->buildPolicyView($selected); $configs = PhabricatorApplicationConfigurationPanel::loadAllPanelsForApplication( @@ -51,29 +50,35 @@ final class PhabricatorApplicationDetailViewController foreach ($configs as $config) { $config->setViewer($viewer); $config->setApplication($selected); + $panel = $config->buildConfigurationPagePanel(); + $panel->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); + $panels[] = $panel; - $panels[] = $config->buildConfigurationPagePanel(); } - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - $panels, - ), - array( - 'title' => $title, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn(array( + $policies, + $panels, + )) + ->addPropertySection(pht('DETAILS'), $details) + ->setActionList($actions); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, )); } - private function buildPropertyView( - PhabricatorApplication $application, - PhabricatorActionListView $actions) { - - $viewer = $this->getRequest()->getUser(); + private function buildPropertySectionView( + PhabricatorApplication $application) { + $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()); - $properties->setActionList($actions); $properties->addProperty( pht('Description'), @@ -111,20 +116,35 @@ final class PhabricatorApplicationDetailViewController $properties->addTextContent($overview); } + return $properties; + } + + private function buildPolicyView( + PhabricatorApplication $application) { + + $viewer = $this->getViewer(); + $properties = id(new PHUIPropertyListView()) + ->setStacked(true); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('POLICIES')) + ->setHeaderIcon('fa-lock'); + $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( $viewer, $application); - $properties->addSectionHeader( - pht('Policies'), 'fa-lock'); - foreach ($application->getCapabilities() as $capability) { $properties->addProperty( $application->getCapabilityLabel($capability), idx($descriptions, $capability)); } - return $properties; + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($properties); + } private function buildActionView( From 090252a89e2bb1af9b555d13c72867c44f462f2b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 5 Mar 2016 12:28:56 -0800 Subject: [PATCH 06/57] Clean up Macro view page Summary: Sets a header icon, makes "Details" not show if empty, simplifies title. Test Plan: Review a few Macro pages for changes with and without audio. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15406 --- .../macro/controller/PhabricatorMacroViewController.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/applications/macro/controller/PhabricatorMacroViewController.php b/src/applications/macro/controller/PhabricatorMacroViewController.php index 4e43c796cf..9ee70eecdc 100644 --- a/src/applications/macro/controller/PhabricatorMacroViewController.php +++ b/src/applications/macro/controller/PhabricatorMacroViewController.php @@ -40,7 +40,8 @@ final class PhabricatorMacroViewController $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setPolicyObject($macro) - ->setHeader($title_long); + ->setHeader($macro->getName()) + ->setHeaderIcon('fa-file-image-o'); if (!$macro->getIsDisabled()) { $header->setStatus('fa-check', 'bluegrey', pht('Active')); @@ -177,7 +178,11 @@ final class PhabricatorMacroViewController $viewer->renderHandle($audio_phid)); } - return $view; + if ($view->hasAnyProperties()) { + return $view; + } + + return null; } private function buildFileView( From 0569919eab545806c1632a1ab49cde4f91266950 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 5 Mar 2016 13:49:46 -0800 Subject: [PATCH 07/57] Fix some visibility issues with inline comments in Diffusion Summary: Fixes T10519. Two issues: First, the acting user wasn't explicitly included in the mail. This usually didn't matter, but could matter if you unsubscribed and then interacted. Second, we had some logic which tried to hide redundant "added inline comment" transactions, but could hide them inappropriately. In particular, if another action (like a subscribe) was present in the same group, we could hide the inlines because of that other transaction, then //also// hide the subscribe. This particular issue is likely an unintended consequence of hiding self-subscribes. Instead of hiding inlines if //anything else// happened, hide them only if: - there is another "added a comment" transaction; or - there is another "added an inline comment" transaction. This prevents the root issue in T10519 (incorrectly hiding every transaction, and thus not sending the mail) and should generally make behavior a little more consistent and future-proof. Test Plan: - Submitted //only// an inline comment on a commit I had not previously interacted with. - Before patch: no mail was generated (entire mail was improperly hidden). - After patch: got some mail with my comment. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10519 Differential Revision: https://secure.phabricator.com/D15407 --- .../audit/editor/PhabricatorAuditEditor.php | 2 ++ .../PhabricatorApplicationTransaction.php | 29 ++++++++++--------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/applications/audit/editor/PhabricatorAuditEditor.php b/src/applications/audit/editor/PhabricatorAuditEditor.php index 51efdf175b..3df9013301 100644 --- a/src/applications/audit/editor/PhabricatorAuditEditor.php +++ b/src/applications/audit/editor/PhabricatorAuditEditor.php @@ -636,6 +636,8 @@ final class PhabricatorAuditEditor } } + $phids[] = $this->getActingAsPHID(); + return $phids; } diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index 8084ea302f..ee8a7a47a3 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -605,24 +605,27 @@ abstract class PhabricatorApplicationTransaction break; } - // If a transaction publishes an inline comment: - // - // - Don't show it if there are other kinds of transactions. The - // rationale here is that application mail will make the presence - // of inline comments obvious enough by including them prominently - // in the body. We could change this in the future if the obviousness - // needs to be increased. - // - If there are only inline transactions, only show the first - // transaction. The rationale is that seeing multiple "added an inline - // comment" transactions is not useful. - if ($this->isInlineCommentTransaction()) { + $inlines = array(); + + // If there's a normal comment, we don't need to publish the inline + // transaction, since the normal comment covers things. foreach ($xactions as $xaction) { - if (!$xaction->isInlineCommentTransaction()) { + if ($xaction->isInlineCommentTransaction()) { + $inlines[] = $xaction; + continue; + } + + // We found a normal comment, so hide this inline transaction. + if ($xaction->hasComment()) { return true; } } - return ($this !== head($xactions)); + + // If there are several inline comments, only publish the first one. + if ($this !== head($inlines)) { + return true; + } } return $this->shouldHide(); From 6084b7a20160822eaed807e0fc3c13c02cbd61ab Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 2 Mar 2016 20:32:07 -0800 Subject: [PATCH 08/57] Update Owners for PHUITwoColumn Summary: Clean up owners a bit, move to two columns. Test Plan: Review a package, edit paths, remove all paths. Archive. {F1139351} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15388 --- resources/celerity/map.php | 6 +- .../PhabricatorOwnersDetailController.php | 80 ++++++++++++------- webroot/rsrc/css/phui/phui-box.css | 8 ++ 3 files changed, 63 insertions(+), 31 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index d294ca82a1..4ab5e3cd3a 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '3d58b25a', + 'core.pkg.css' => 'd3b3a609', 'core.pkg.js' => '7d8faf57', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', @@ -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' => 'c9e01148', + 'rsrc/css/phui/phui-box.css' => '3830ab21', 'rsrc/css/phui/phui-button.css' => 'a64a8de6', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-crumbs-view.css' => '79d536e5', @@ -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' => 'c9e01148', + 'phui-box-css' => '3830ab21', 'phui-button-css' => 'a64a8de6', 'phui-calendar-css' => 'ccabe893', 'phui-calendar-day-css' => 'd1cf6f93', diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index fc09508bfe..9b2da6c3c1 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -45,7 +45,7 @@ final class PhabricatorOwnersDetailController $actions = $this->buildPackageActionView($package); $properties = $this->buildPackagePropertyView($package, $field_list); - $properties->setActionList($actions); + $details = $this->buildPackageDetailView($package, $field_list); if ($package->isArchived()) { $header_icon = 'fa-ban'; @@ -61,11 +61,8 @@ final class PhabricatorOwnersDetailController ->setUser($viewer) ->setHeader($package->getName()) ->setStatus($header_icon, $header_color, $header_name) - ->setPolicyObject($package); - - $panel = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + ->setPolicyObject($package) + ->setHeaderIcon('fa-gift'); $commit_views = array(); @@ -91,11 +88,13 @@ final class PhabricatorOwnersDetailController $commit_views[] = array( 'view' => $view, - 'header' => pht('Commits in this Package that Need Attention'), + 'header' => pht('Needs Attention'), + 'icon' => 'fa-warning', 'button' => id(new PHUIButtonView()) ->setTag('a') ->setHref($commit_uri->alter('status', $status_concern)) - ->setText(pht('View All Problem Commits')), + ->setIcon('fa-list-ul') + ->setText(pht('View All')), ); $all_commits = id(new DiffusionCommitQuery()) @@ -112,11 +111,13 @@ final class PhabricatorOwnersDetailController $commit_views[] = array( 'view' => $view, - 'header' => pht('Recent Commits in Package'), + 'header' => pht('Recent Commits'), + 'icon' => 'fa-code', 'button' => id(new PHUIButtonView()) ->setTag('a') ->setHref($commit_uri) - ->setText(pht('View All Package Commits')), + ->setIcon('fa-list-ul') + ->setText(pht('View All')), ); $phids = array(); @@ -128,14 +129,16 @@ final class PhabricatorOwnersDetailController $commit_panels = array(); foreach ($commit_views as $commit_view) { - $commit_panel = new PHUIObjectBoxView(); - $header = new PHUIHeaderView(); - $header->setHeader($commit_view['header']); + $commit_panel = id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); + $commit_header = id(new PHUIHeaderView()) + ->setHeader($commit_view['header']) + ->setHeaderIcon($commit_view['icon']); if (isset($commit_view['button'])) { - $header->addActionLink($commit_view['button']); + $commit_header->addActionLink($commit_view['button']); } $commit_view['view']->setHandles($handles); - $commit_panel->setHeader($header); + $commit_panel->setHeader($commit_header); $commit_panel->appendChild($commit_view['view']); $commit_panels[] = $commit_panel; @@ -143,32 +146,51 @@ final class PhabricatorOwnersDetailController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($package->getName()); + $crumbs->setBorder(true); $timeline = $this->buildTransactionTimeline( $package, new PhabricatorOwnersPackageTransactionQuery()); $timeline->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, - $panel, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn(array( $this->renderPathsTable($paths, $repositories), $commit_panels, $timeline, - ), - array( - 'title' => $package->getName(), + )) + ->addPropertySection(pht('Details'), $details) + ->setPropertyList($properties) + ->setActionList($actions); + + return $this->newPage() + ->setTitle($package->getName()) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, )); } - private function buildPackagePropertyView( PhabricatorOwnersPackage $package, PhabricatorCustomFieldList $field_list) { $viewer = $this->getViewer(); + $view = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setObject($package); + $view->invokeWillRenderEvent(); + return $view; + } + + private function buildPackageDetailView( + PhabricatorOwnersPackage $package, + PhabricatorCustomFieldList $field_list) { + + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer); @@ -190,13 +212,10 @@ final class PhabricatorOwnersDetailController $description = $package->getDescription(); if (strlen($description)) { $description = new PHUIRemarkupView($viewer, $description); - $view->addSectionHeader( - pht('Description'), PHUIPropertyListView::ICON_SUMMARY); + $view->addSectionHeader(pht('Description')); $view->addTextContent($description); } - $view->invokeWillRenderEvent(); - $field_list->appendFieldsToPropertyList( $package, $viewer, @@ -314,8 +333,13 @@ final class PhabricatorOwnersDetailController 'wide', )); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Paths')) + ->setHeaderIcon('fa-folder-open'); + $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Paths')) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setTable($table); if ($info) { diff --git a/webroot/rsrc/css/phui/phui-box.css b/webroot/rsrc/css/phui/phui-box.css index 7eaca8b741..1d42cde8fa 100644 --- a/webroot/rsrc/css/phui/phui-box.css +++ b/webroot/rsrc/css/phui/phui-box.css @@ -66,6 +66,14 @@ padding: 0; } +.phui-box.phui-box-blue-property .phui-header-header { + text-transform: uppercase; +} + +.phui-box.phui-box-blue-property .phui-header-header .phui-header-icon { + margin-right: 6px; +} + .phui-box.phui-box-blue-property .phui-header-action-link { margin-top: 0; margin-bottom: 0; From 5a28bff9873930cade73a2498d0356e19c5d64fa Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 5 Mar 2016 18:27:49 -0800 Subject: [PATCH 09/57] Fix variable in StandardPageView Summary: Found this grepping for `contne` Test Plan: render any standard page Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15411 --- src/view/page/PhabricatorStandardPageView.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php index e85abb2140..f38bf3c18b 100644 --- a/src/view/page/PhabricatorStandardPageView.php +++ b/src/view/page/PhabricatorStandardPageView.php @@ -490,7 +490,7 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView $nav->appendFooter($footer); $content = phutil_implode_html('', array($nav->render())); } else { - $contnet = array(); + $content = array(); $crumbs = $this->getCrumbs(); if ($crumbs) { From 220230e08c8d2416ffe560ade66c4da06724e4de Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sat, 5 Mar 2016 18:23:42 -0800 Subject: [PATCH 10/57] Fix typo in Phriction contne Summary: Fix typo in infoview. Fixes T10524 Test Plan: Read. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T10524 Differential Revision: https://secure.phabricator.com/D15410 --- .../phriction/controller/PhrictionDocumentController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/phriction/controller/PhrictionDocumentController.php b/src/applications/phriction/controller/PhrictionDocumentController.php index 360c0fbf3f..85bfa1a660 100644 --- a/src/applications/phriction/controller/PhrictionDocumentController.php +++ b/src/applications/phriction/controller/PhrictionDocumentController.php @@ -151,7 +151,7 @@ final class PhrictionDocumentController array(), pht( 'This document has been moved. You can edit it to put new '. - 'contne here, or use history to revert to an earlier '. + 'content here, or use history to revert to an earlier '. 'version.'))); } From abb4c03b476d4159ebff62ed44009c0e4fec3be1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 5 Mar 2016 14:53:44 -0800 Subject: [PATCH 11/57] Remove shouldShowSubscribersProperty() from SubscribableInterface Summary: Every caller returns `true`. This was added a long time ago for Projects, but projects are no longer subscribable. I don't anticipate needing this in the future. Test Plan: Grepped for this method. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D15409 --- .../badges/storage/PhabricatorBadgesBadge.php | 4 ---- .../calendar/storage/PhabricatorCalendarEvent.php | 4 ---- .../countdown/storage/PhabricatorCountdown.php | 3 --- .../differential/storage/DifferentialRevision.php | 4 ---- src/applications/files/storage/PhabricatorFile.php | 4 ---- src/applications/fund/storage/FundInitiative.php | 4 ---- .../storage/configuration/HarbormasterBuildPlan.php | 4 ---- src/applications/herald/storage/HeraldRule.php | 4 ---- .../legalpad/storage/LegalpadDocument.php | 4 ---- .../macro/storage/PhabricatorFileImageMacro.php | 4 ---- .../maniphest/storage/ManiphestTask.php | 4 ---- .../passphrase/storage/PassphraseCredential.php | 4 ---- src/applications/paste/storage/PhabricatorPaste.php | 4 ---- src/applications/phame/storage/PhameBlog.php | 4 ---- src/applications/phame/storage/PhamePost.php | 4 ---- src/applications/pholio/storage/PholioMock.php | 4 ---- .../phriction/storage/PhrictionDocument.php | 3 --- .../phurl/storage/PhabricatorPhurlURL.php | 3 --- src/applications/ponder/storage/PonderAnswer.php | 4 ---- src/applications/ponder/storage/PonderQuestion.php | 4 ---- .../storage/PhabricatorRepositoryCommit.php | 4 ---- .../slowvote/storage/PhabricatorSlowvotePoll.php | 4 ---- .../PhabricatorSubscriptionsUIEventListener.php | 5 ----- .../interface/PhabricatorSubscribableInterface.php | 13 ------------- .../workers/storage/PhabricatorWorkerBulkJob.php | 4 ---- 25 files changed, 107 deletions(-) diff --git a/src/applications/badges/storage/PhabricatorBadgesBadge.php b/src/applications/badges/storage/PhabricatorBadgesBadge.php index 254b02a6eb..d32a46a803 100644 --- a/src/applications/badges/storage/PhabricatorBadgesBadge.php +++ b/src/applications/badges/storage/PhabricatorBadgesBadge.php @@ -181,10 +181,6 @@ final class PhabricatorBadgesBadge extends PhabricatorBadgesDAO return ($this->creatorPHID == $phid); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 36aa905b3e..8432a157e2 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -531,10 +531,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO return ($phid == $this->getUserPHID()); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ diff --git a/src/applications/countdown/storage/PhabricatorCountdown.php b/src/applications/countdown/storage/PhabricatorCountdown.php index 1a864907eb..52a395f0c2 100644 --- a/src/applications/countdown/storage/PhabricatorCountdown.php +++ b/src/applications/countdown/storage/PhabricatorCountdown.php @@ -70,9 +70,6 @@ final class PhabricatorCountdown extends PhabricatorCountdownDAO return ($phid == $this->getAuthorPHID()); } - public function shouldShowSubscribersProperty() { - return true; - } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index e67208c53a..6ca586c747 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -485,10 +485,6 @@ final class DifferentialRevision extends DifferentialDAO return false; } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorCustomFieldInterface )------------------------------------ */ diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php index 7d2780778f..28328796ce 100644 --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -1348,10 +1348,6 @@ final class PhabricatorFile extends PhabricatorFileDAO return ($this->authorPHID == $phid); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ diff --git a/src/applications/fund/storage/FundInitiative.php b/src/applications/fund/storage/FundInitiative.php index 3375551443..517c377c44 100644 --- a/src/applications/fund/storage/FundInitiative.php +++ b/src/applications/fund/storage/FundInitiative.php @@ -178,10 +178,6 @@ final class FundInitiative extends FundDAO return ($phid == $this->getOwnerPHID()); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorTokenRecevierInterface )---------------------------------- */ diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php index c5b2142ac5..8d706657ef 100644 --- a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php +++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php @@ -127,10 +127,6 @@ final class HarbormasterBuildPlan extends HarbormasterDAO return false; } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/herald/storage/HeraldRule.php b/src/applications/herald/storage/HeraldRule.php index 8d585263c0..9902e952ef 100644 --- a/src/applications/herald/storage/HeraldRule.php +++ b/src/applications/herald/storage/HeraldRule.php @@ -328,10 +328,6 @@ final class HeraldRule extends HeraldDAO return $this->isPersonalRule() && $phid == $this->getAuthorPHID(); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorDestructibleInterface )----------------------------------- */ diff --git a/src/applications/legalpad/storage/LegalpadDocument.php b/src/applications/legalpad/storage/LegalpadDocument.php index 51f158a978..fcecac991f 100644 --- a/src/applications/legalpad/storage/LegalpadDocument.php +++ b/src/applications/legalpad/storage/LegalpadDocument.php @@ -163,10 +163,6 @@ final class LegalpadDocument extends LegalpadDAO return ($this->creatorPHID == $phid); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/macro/storage/PhabricatorFileImageMacro.php b/src/applications/macro/storage/PhabricatorFileImageMacro.php index 5cf23133a3..37f0d5c06b 100644 --- a/src/applications/macro/storage/PhabricatorFileImageMacro.php +++ b/src/applications/macro/storage/PhabricatorFileImageMacro.php @@ -111,10 +111,6 @@ final class PhabricatorFileImageMacro extends PhabricatorFileDAO return false; } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorTokenRecevierInterface )---------------------------------- */ diff --git a/src/applications/maniphest/storage/ManiphestTask.php b/src/applications/maniphest/storage/ManiphestTask.php index 0c1a1bc787..7718bdfcf8 100644 --- a/src/applications/maniphest/storage/ManiphestTask.php +++ b/src/applications/maniphest/storage/ManiphestTask.php @@ -269,10 +269,6 @@ final class ManiphestTask extends ManiphestDAO return ($phid == $this->getOwnerPHID()); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( Markup Interface )--------------------------------------------------- */ diff --git a/src/applications/passphrase/storage/PassphraseCredential.php b/src/applications/passphrase/storage/PassphraseCredential.php index f263523b49..8384f5bd52 100644 --- a/src/applications/passphrase/storage/PassphraseCredential.php +++ b/src/applications/passphrase/storage/PassphraseCredential.php @@ -157,10 +157,6 @@ final class PassphraseCredential extends PassphraseDAO return false; } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorDestructibleInterface )----------------------------------- */ diff --git a/src/applications/paste/storage/PhabricatorPaste.php b/src/applications/paste/storage/PhabricatorPaste.php index 624c70c410..bc0909cd45 100644 --- a/src/applications/paste/storage/PhabricatorPaste.php +++ b/src/applications/paste/storage/PhabricatorPaste.php @@ -155,10 +155,6 @@ final class PhabricatorPaste extends PhabricatorPasteDAO return ($this->authorPHID == $phid); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ diff --git a/src/applications/phame/storage/PhameBlog.php b/src/applications/phame/storage/PhameBlog.php index d9b9bdb41d..861def127b 100644 --- a/src/applications/phame/storage/PhameBlog.php +++ b/src/applications/phame/storage/PhameBlog.php @@ -336,10 +336,6 @@ final class PhameBlog extends PhameDAO return ($this->creatorPHID == $phid); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorConduitResultInterface )---------------------------------- */ diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php index 01dd71e88d..fb2e8058dc 100644 --- a/src/applications/phame/storage/PhamePost.php +++ b/src/applications/phame/storage/PhamePost.php @@ -282,10 +282,6 @@ final class PhamePost extends PhameDAO return ($this->bloggerPHID == $phid); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorConduitResultInterface )---------------------------------- */ diff --git a/src/applications/pholio/storage/PholioMock.php b/src/applications/pholio/storage/PholioMock.php index c817b7f32b..49cf2ace5d 100644 --- a/src/applications/pholio/storage/PholioMock.php +++ b/src/applications/pholio/storage/PholioMock.php @@ -184,10 +184,6 @@ final class PholioMock extends PholioDAO return ($this->authorPHID == $phid); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ diff --git a/src/applications/phriction/storage/PhrictionDocument.php b/src/applications/phriction/storage/PhrictionDocument.php index 915cfed1eb..f931ac8dbe 100644 --- a/src/applications/phriction/storage/PhrictionDocument.php +++ b/src/applications/phriction/storage/PhrictionDocument.php @@ -194,9 +194,6 @@ final class PhrictionDocument extends PhrictionDAO return false; } - public function shouldShowSubscribersProperty() { - return true; - } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/phurl/storage/PhabricatorPhurlURL.php b/src/applications/phurl/storage/PhabricatorPhurlURL.php index 30a3b0b93b..19d5968b0c 100644 --- a/src/applications/phurl/storage/PhabricatorPhurlURL.php +++ b/src/applications/phurl/storage/PhabricatorPhurlURL.php @@ -169,9 +169,6 @@ final class PhabricatorPhurlURL extends PhabricatorPhurlDAO return ($phid == $this->getAuthorPHID()); } - public function shouldShowSubscribersProperty() { - return true; - } /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ diff --git a/src/applications/ponder/storage/PonderAnswer.php b/src/applications/ponder/storage/PonderAnswer.php index 025c020637..76c9057497 100644 --- a/src/applications/ponder/storage/PonderAnswer.php +++ b/src/applications/ponder/storage/PonderAnswer.php @@ -219,10 +219,6 @@ final class PonderAnswer extends PonderDAO return ($phid == $this->getAuthorPHID()); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorDestructibleInterface )----------------------------------- */ diff --git a/src/applications/ponder/storage/PonderQuestion.php b/src/applications/ponder/storage/PonderQuestion.php index 5ab719e3ac..99ce64c9f7 100644 --- a/src/applications/ponder/storage/PonderQuestion.php +++ b/src/applications/ponder/storage/PonderQuestion.php @@ -248,10 +248,6 @@ final class PonderQuestion extends PonderDAO return ($phid == $this->getAuthorPHID()); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index 9fd218db9a..18732e5e4c 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -443,10 +443,6 @@ final class PhabricatorRepositoryCommit return ($phid == $this->getAuthorPHID()); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php index 2240c510aa..6896f7ba2f 100644 --- a/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php +++ b/src/applications/slowvote/storage/PhabricatorSlowvotePoll.php @@ -179,10 +179,6 @@ final class PhabricatorSlowvotePoll extends PhabricatorSlowvoteDAO return ($phid == $this->getAuthorPHID()); } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ diff --git a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php index b69ce4243f..acbb978238 100644 --- a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php +++ b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php @@ -96,11 +96,6 @@ final class PhabricatorSubscriptionsUIEventListener return; } - if (!$object->shouldShowSubscribersProperty()) { - // This object doesn't render subscribers in its property list. - return; - } - $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID( $object->getPHID()); if ($subscribers) { diff --git a/src/applications/subscriptions/interface/PhabricatorSubscribableInterface.php b/src/applications/subscriptions/interface/PhabricatorSubscribableInterface.php index e54e2cd42e..1af9e7107f 100644 --- a/src/applications/subscriptions/interface/PhabricatorSubscribableInterface.php +++ b/src/applications/subscriptions/interface/PhabricatorSubscribableInterface.php @@ -13,15 +13,6 @@ interface PhabricatorSubscribableInterface { */ public function isAutomaticallySubscribed($phid); - - /** - * Return `true` to indicate that "Subscribers:" should be shown when - * rendering property lists for this object, or `false` to omit the property. - * - * @return bool True to show the "Subscribers:" property. - */ - public function shouldShowSubscribersProperty(); - } // TEMPLATE IMPLEMENTATION ///////////////////////////////////////////////////// @@ -33,8 +24,4 @@ interface PhabricatorSubscribableInterface { return false; } - public function shouldShowSubscribersProperty() { - return true; - } - */ diff --git a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJob.php b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJob.php index 0b70268cb4..ca7508d2b2 100644 --- a/src/infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJob.php +++ b/src/infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJob.php @@ -218,10 +218,6 @@ final class PhabricatorWorkerBulkJob return false; } - public function shouldShowSubscribersProperty() { - return true; - } - /* -( PhabricatorApplicationTransactionInterface )------------------------- */ From aaab1011e5a464ea94c182001a7fe867b19bae5a Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 6 Mar 2016 06:26:34 -0800 Subject: [PATCH 12/57] Give AphrontTagView a `getViewer()`, deprecate `getUser()` Summary: Two minor changes here: - Replace `get/setUser()` with `get/setViewer()` for consistency with everything else. - `getViewer()` now throws if no viewer is set. We had a lot of code that either "should" check this but didn't, or did check it in an identical way, duplicating work. In contrast, very little code checks for a viewer but works if one is not present. Test Plan: - Grepped for `->user`. - Attempted to fix all callsites inside `*View` classes. - Browsed around a bunch of applications, particularly Calendar, Differential and Diffusion, which seemed most heavily affected. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D15412 --- .../PhabricatorBadgesRecipientsListView.php | 3 +- .../view/PhabricatorDaemonLogEventsView.php | 9 +-- .../view/PhabricatorDaemonLogListView.php | 8 +-- .../view/DifferentialAddCommentView.php | 5 +- .../view/DifferentialChangesetListView.php | 14 +++-- .../view/DifferentialLocalCommitsView.php | 7 +-- .../view/DifferentialRevisionListView.php | 11 ++-- .../diffusion/view/DiffusionTagListView.php | 2 +- .../phame/view/PhameBlogListView.php | 6 -- .../phame/view/PhameDraftListView.php | 6 -- .../phame/view/PhamePostListView.php | 8 +-- .../ponder/view/PonderAddAnswerView.php | 6 +- .../view/PhabricatorProjectUserListView.php | 2 +- .../view/PHUIDiffInlineCommentEditView.php | 7 +-- src/view/AphrontDialogView.php | 8 +-- src/view/AphrontView.php | 56 +++++++++++++++++-- src/view/form/AphrontFormView.php | 8 +-- .../form/control/AphrontFormDateControl.php | 15 ++--- .../control/AphrontFormTokenizerControl.php | 4 +- src/view/layout/AphrontSideNavFilterView.php | 3 - src/view/layout/PhabricatorActionListView.php | 8 +-- src/view/layout/PhabricatorActionView.php | 6 +- .../menu/PhabricatorMainMenuSearchView.php | 6 +- .../page/menu/PhabricatorMainMenuView.php | 28 +++++----- src/view/phui/PHUIFeedStoryView.php | 4 +- .../phui/calendar/PHUICalendarDayView.php | 6 +- .../phui/calendar/PHUICalendarMonthView.php | 14 ++--- 27 files changed, 134 insertions(+), 126 deletions(-) diff --git a/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php b/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php index 6b633bed5b..68633a6a29 100644 --- a/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php +++ b/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php @@ -16,8 +16,7 @@ final class PhabricatorBadgesRecipientsListView extends AphrontView { } public function render() { - - $viewer = $this->user; + $viewer = $this->getViewer(); $badge = $this->badge; $handles = $this->handles; diff --git a/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php b/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php index 4c0caa6fd7..039906e718 100644 --- a/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php +++ b/src/applications/daemon/view/PhabricatorDaemonLogEventsView.php @@ -24,12 +24,9 @@ final class PhabricatorDaemonLogEventsView extends AphrontView { } public function render() { + $viewer = $this->getViewer(); $rows = array(); - if (!$this->user) { - throw new PhutilInvalidStateException('setUser'); - } - foreach ($this->events as $event) { // Limit display log size. If a daemon gets stuck in an output loop this @@ -83,8 +80,8 @@ final class PhabricatorDaemonLogEventsView extends AphrontView { $row = array( $event->getLogType(), - phabricator_date($event->getEpoch(), $this->user), - phabricator_time($event->getEpoch(), $this->user), + phabricator_date($event->getEpoch(), $viewer), + phabricator_time($event->getEpoch(), $viewer), array( $message, $more, diff --git a/src/applications/daemon/view/PhabricatorDaemonLogListView.php b/src/applications/daemon/view/PhabricatorDaemonLogListView.php index 6c96509505..046d1a29f5 100644 --- a/src/applications/daemon/view/PhabricatorDaemonLogListView.php +++ b/src/applications/daemon/view/PhabricatorDaemonLogListView.php @@ -11,11 +11,9 @@ final class PhabricatorDaemonLogListView extends AphrontView { } public function render() { - $rows = array(); + $viewer = $this->getViewer(); - if (!$this->user) { - throw new PhutilInvalidStateException('setUser'); - } + $rows = array(); $list = new PHUIObjectItemListView(); $list->setFlush(true); @@ -27,7 +25,7 @@ final class PhabricatorDaemonLogListView extends AphrontView { ->setObjectName(pht('Daemon %s', $id)) ->setHeader($log->getDaemon()) ->setHref("/daemon/log/{$id}/") - ->addIcon('none', phabricator_datetime($epoch, $this->user)); + ->addIcon('none', phabricator_datetime($epoch, $viewer)); $status = $log->getStatus(); switch ($status) { diff --git a/src/applications/differential/view/DifferentialAddCommentView.php b/src/applications/differential/view/DifferentialAddCommentView.php index 5524869a77..2d586e7be4 100644 --- a/src/applications/differential/view/DifferentialAddCommentView.php +++ b/src/applications/differential/view/DifferentialAddCommentView.php @@ -50,6 +50,7 @@ final class DifferentialAddCommentView extends AphrontView { } public function render() { + $viewer = $this->getViewer(); $this->requireResource('differential-revision-add-comment-css'); $revision = $this->revision; @@ -73,7 +74,7 @@ final class DifferentialAddCommentView extends AphrontView { $form = new AphrontFormView(); $form ->setWorkflow(true) - ->setUser($this->user) + ->setViewer($viewer) ->setAction($this->actionURI) ->addHiddenInput('revision_id', $revision->getID()) ->appendChild( @@ -108,7 +109,7 @@ final class DifferentialAddCommentView extends AphrontView { ->setID('comment-content') ->setLabel(pht('Comment')) ->setValue($this->draft ? $this->draft->getDraft() : null) - ->setUser($this->user)) + ->setViewer($viewer)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Submit'))); diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index ec509d62dd..0cd2923018 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -113,6 +113,8 @@ final class DifferentialChangesetListView extends AphrontView { } public function render() { + $viewer = $this->getViewer(); + $this->requireResource('differential-changeset-view-css'); $changesets = $this->changesets; @@ -148,7 +150,7 @@ final class DifferentialChangesetListView extends AphrontView { )); $renderer = DifferentialChangesetParser::getDefaultRendererForViewer( - $this->getUser()); + $viewer); $output = array(); $ids = array(); @@ -163,7 +165,7 @@ final class DifferentialChangesetListView extends AphrontView { $ref = $this->references[$key]; $detail = id(new DifferentialChangesetDetailView()) - ->setUser($this->getUser()); + ->setUser($viewer); $uniq_id = 'diff-'.$changeset->getAnchorName(); $detail->setID($uniq_id); @@ -261,6 +263,7 @@ final class DifferentialChangesetListView extends AphrontView { DifferentialChangesetDetailView $detail, $ref, DifferentialChangeset $changeset) { + $viewer = $this->getViewer(); $meta = array(); @@ -280,7 +283,7 @@ final class DifferentialChangesetListView extends AphrontView { try { $meta['diffusionURI'] = (string)$repository->getDiffusionBrowseURIForPath( - $this->user, + $viewer, $changeset->getAbsoluteRepositoryPath($repository, $this->diff), idx($changeset->getMetadata(), 'line:first'), $this->getBranch()); @@ -308,13 +311,12 @@ final class DifferentialChangesetListView extends AphrontView { } } - $user = $this->user; - if ($user && $repository) { + if ($viewer && $repository) { $path = ltrim( $changeset->getAbsoluteRepositoryPath($repository, $this->diff), '/'); $line = idx($changeset->getMetadata(), 'line:first', 1); - $editor_link = $user->loadEditorLink($path, $line, $repository); + $editor_link = $viewer->loadEditorLink($path, $line, $repository); if ($editor_link) { $meta['editor'] = $editor_link; } else { diff --git a/src/applications/differential/view/DifferentialLocalCommitsView.php b/src/applications/differential/view/DifferentialLocalCommitsView.php index 4da850539d..ebacabc3f3 100644 --- a/src/applications/differential/view/DifferentialLocalCommitsView.php +++ b/src/applications/differential/view/DifferentialLocalCommitsView.php @@ -17,10 +17,7 @@ final class DifferentialLocalCommitsView extends AphrontView { } public function render() { - $user = $this->user; - if (!$user) { - throw new PhutilInvalidStateException('setUser'); - } + $viewer = $this->getViewer(); $local = $this->localCommits; if (!$local) { @@ -94,7 +91,7 @@ final class DifferentialLocalCommitsView extends AphrontView { idx($commit, 'date'), idx($commit, 'time')); if ($date) { - $date = phabricator_datetime($date, $user); + $date = phabricator_datetime($date, $viewer); } $row[] = $date; diff --git a/src/applications/differential/view/DifferentialRevisionListView.php b/src/applications/differential/view/DifferentialRevisionListView.php index 2c14bbc4a5..92394fcb3e 100644 --- a/src/applications/differential/view/DifferentialRevisionListView.php +++ b/src/applications/differential/view/DifferentialRevisionListView.php @@ -57,10 +57,7 @@ final class DifferentialRevisionListView extends AphrontView { } public function render() { - $user = $this->user; - if (!$user) { - throw new PhutilInvalidStateException('setUser'); - } + $viewer = $this->getViewer(); $fresh = PhabricatorEnv::getEnvConfig('differential.days-fresh'); if ($fresh) { @@ -83,12 +80,12 @@ final class DifferentialRevisionListView extends AphrontView { foreach ($this->revisions as $revision) { $item = id(new PHUIObjectItemView()) - ->setUser($user); + ->setUser($viewer); $icons = array(); $phid = $revision->getPHID(); - $flag = $revision->getFlag($user); + $flag = $revision->getFlag($viewer); if ($flag) { $flag_class = PhabricatorFlagColor::getCSSClass($flag->getColor()); $icons['flag'] = phutil_tag( @@ -99,7 +96,7 @@ final class DifferentialRevisionListView extends AphrontView { ''); } - if ($revision->getDrafts($user)) { + if ($revision->getDrafts($viewer)) { $icons['draft'] = true; } diff --git a/src/applications/diffusion/view/DiffusionTagListView.php b/src/applications/diffusion/view/DiffusionTagListView.php index 3b27284f5e..923fa30fc5 100644 --- a/src/applications/diffusion/view/DiffusionTagListView.php +++ b/src/applications/diffusion/view/DiffusionTagListView.php @@ -100,7 +100,7 @@ final class DiffusionTagListView extends DiffusionView { $build, $author, $description, - phabricator_datetime($tag->getEpoch(), $this->user), + phabricator_datetime($tag->getEpoch(), $this->getViewer()), ); } diff --git a/src/applications/phame/view/PhameBlogListView.php b/src/applications/phame/view/PhameBlogListView.php index 0d897b730a..5aa58ee8f9 100644 --- a/src/applications/phame/view/PhameBlogListView.php +++ b/src/applications/phame/view/PhameBlogListView.php @@ -3,7 +3,6 @@ final class PhameBlogListView extends AphrontTagView { private $blogs; - private $viewer; public function setBlogs($blogs) { assert_instances_of($blogs, 'PhameBlog'); @@ -11,11 +10,6 @@ final class PhameBlogListView extends AphrontTagView { return $this; } - public function setViewer($viewer) { - $this->viewer = $viewer; - return $this; - } - protected function getTagAttributes() { $classes = array(); $classes[] = 'phame-blog-list'; diff --git a/src/applications/phame/view/PhameDraftListView.php b/src/applications/phame/view/PhameDraftListView.php index 87c5a6d7b5..294eeb11bc 100644 --- a/src/applications/phame/view/PhameDraftListView.php +++ b/src/applications/phame/view/PhameDraftListView.php @@ -4,7 +4,6 @@ final class PhameDraftListView extends AphrontTagView { private $posts; private $blogs; - private $viewer; public function setPosts($posts) { assert_instances_of($posts, 'PhamePost'); @@ -18,11 +17,6 @@ final class PhameDraftListView extends AphrontTagView { return $this; } - public function setViewer($viewer) { - $this->viewer = $viewer; - return $this; - } - protected function getTagAttributes() { $classes = array(); $classes[] = 'phame-blog-list'; diff --git a/src/applications/phame/view/PhamePostListView.php b/src/applications/phame/view/PhamePostListView.php index dbe77d4aa6..fe6da36541 100644 --- a/src/applications/phame/view/PhamePostListView.php +++ b/src/applications/phame/view/PhamePostListView.php @@ -4,7 +4,6 @@ final class PhamePostListView extends AphrontTagView { private $posts; private $nodata; - private $viewer; private $showBlog = false; private $isExternal; private $isLive; @@ -25,11 +24,6 @@ final class PhamePostListView extends AphrontTagView { return $this; } - public function setViewer($viewer) { - $this->viewer = $viewer; - return $this; - } - public function setIsExternal($is_external) { $this->isExternal = $is_external; return $this; @@ -53,7 +47,7 @@ final class PhamePostListView extends AphrontTagView { } protected function getTagContent() { - $viewer = $this->viewer; + $viewer = $this->getViewer(); $posts = $this->posts; $nodata = $this->nodata; diff --git a/src/applications/ponder/view/PonderAddAnswerView.php b/src/applications/ponder/view/PonderAddAnswerView.php index 989837aa94..d958e9843e 100644 --- a/src/applications/ponder/view/PonderAddAnswerView.php +++ b/src/applications/ponder/view/PonderAddAnswerView.php @@ -18,7 +18,7 @@ final class PonderAddAnswerView extends AphrontView { public function render() { $question = $this->question; - $viewer = $this->user; + $viewer = $this->getViewer(); $authors = mpull($question->getAnswers(), null, 'getAuthorPHID'); if (isset($authors[$viewer->getPHID()])) { @@ -49,7 +49,7 @@ final class PonderAddAnswerView extends AphrontView { $form = new AphrontFormView(); $form - ->setUser($this->user) + ->setViewer($viewer) ->setAction($this->actionURI) ->setWorkflow(true) ->addHiddenInput('question_id', $question->getID()) @@ -59,7 +59,7 @@ final class PonderAddAnswerView extends AphrontView { ->setLabel(pht('Answer')) ->setError(true) ->setID('answer-content') - ->setUser($this->user)) + ->setViewer($viewer)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Add Answer'))); diff --git a/src/applications/project/view/PhabricatorProjectUserListView.php b/src/applications/project/view/PhabricatorProjectUserListView.php index e7f6631bfb..d590cbb559 100644 --- a/src/applications/project/view/PhabricatorProjectUserListView.php +++ b/src/applications/project/view/PhabricatorProjectUserListView.php @@ -45,7 +45,7 @@ abstract class PhabricatorProjectUserListView extends AphrontView { abstract protected function getHeaderText(); public function render() { - $viewer = $this->getUser(); + $viewer = $this->getViewer(); $project = $this->getProject(); $user_phids = $this->getUserPHIDs(); diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php index 3a9bc616d9..a7c10b5b48 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentEditView.php @@ -78,12 +78,11 @@ final class PHUIDiffInlineCommentEditView if (!$this->uri) { throw new PhutilInvalidStateException('setSubmitURI'); } - if (!$this->user) { - throw new PhutilInvalidStateException('setUser'); - } + + $viewer = $this->getViewer(); $content = phabricator_form( - $this->user, + $viewer, array( 'action' => $this->uri, 'method' => 'POST', diff --git a/src/view/AphrontDialogView.php b/src/view/AphrontDialogView.php index f8bc3fa060..26dee0c398 100644 --- a/src/view/AphrontDialogView.php +++ b/src/view/AphrontDialogView.php @@ -236,11 +236,11 @@ final class AphrontDialogView $this->cancelText); } - if (!$this->user) { + if (!$this->hasViewer()) { throw new Exception( pht( 'You must call %s when rendering an %s.', - 'setUser()', + 'setViewer()', __CLASS__)); } @@ -308,7 +308,7 @@ final class AphrontDialogView if (!$this->renderAsForm) { $buttons = array( phabricator_form( - $this->user, + $this->getViewer(), $form_attributes, array_merge($hidden_inputs, $buttons)), ); @@ -376,7 +376,7 @@ final class AphrontDialogView if ($this->renderAsForm) { return phabricator_form( - $this->user, + $this->getViewer(), $form_attributes + $attributes, array($hidden_inputs, $content)); } else { diff --git a/src/view/AphrontView.php b/src/view/AphrontView.php index 91565ca6bd..c014d7d50c 100644 --- a/src/view/AphrontView.php +++ b/src/view/AphrontView.php @@ -6,7 +6,7 @@ abstract class AphrontView extends Phobject implements PhutilSafeHTMLProducerInterface { - protected $user; + private $viewer; protected $children = array(); @@ -14,19 +14,65 @@ abstract class AphrontView extends Phobject /** - * @task config + * Set the user viewing this element. + * + * @param PhabricatorUser Viewing user. + * @return this */ - public function setUser(PhabricatorUser $user) { - $this->user = $user; + public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; return $this; } /** + * Get the user viewing this element. + * + * Throws an exception if no viewer has been set. + * + * @return PhabricatorUser Viewing user. + */ + public function getViewer() { + if (!$this->viewer) { + throw new PhutilInvalidStateException('setViewer'); + } + + return $this->viewer; + } + + + /** + * Test if a viewer has been set on this elmeent. + * + * @return bool True if a viewer is available. + */ + public function hasViewer() { + return (bool)$this->viewer; + } + + + /** + * Deprecated, use @{method:setViewer}. + * * @task config + * @deprecated + */ + public function setUser(PhabricatorUser $user) { + return $this->setViewer($user); + } + + + /** + * Deprecated, use @{method:getViewer}. + * + * @task config + * @deprecated */ protected function getUser() { - return $this->user; + if (!$this->hasViewer()) { + return null; + } + return $this->getViewer(); } diff --git a/src/view/form/AphrontFormView.php b/src/view/form/AphrontFormView.php index 810208ffae..ecd4c1206e 100644 --- a/src/view/form/AphrontFormView.php +++ b/src/view/form/AphrontFormView.php @@ -85,13 +85,13 @@ final class AphrontFormView extends AphrontView { public function appendRemarkupInstructions($remarkup) { return $this->appendInstructions( - new PHUIRemarkupView($this->getUser(), $remarkup)); + new PHUIRemarkupView($this->getViewer(), $remarkup)); } public function buildLayoutView() { foreach ($this->controls as $control) { - $control->setUser($this->getUser()); + $control->setViewer($this->getViewer()); $control->willRender(); } @@ -123,7 +123,7 @@ final class AphrontFormView extends AphrontView { $layout = $this->buildLayoutView(); - if (!$this->user) { + if (!$this->hasViewer()) { throw new Exception( pht( 'You must pass the user to %s.', @@ -136,7 +136,7 @@ final class AphrontFormView extends AphrontView { } return phabricator_form( - $this->user, + $this->getViewer(), array( 'class' => $this->shaded ? 'phui-form-shaded' : null, 'action' => $this->action, diff --git a/src/view/form/control/AphrontFormDateControl.php b/src/view/form/control/AphrontFormDateControl.php index 75398d688a..25dec8eef0 100644 --- a/src/view/form/control/AphrontFormDateControl.php +++ b/src/view/form/control/AphrontFormDateControl.php @@ -137,12 +137,12 @@ final class AphrontFormDateControl extends AphrontFormControl { } private function getTimeFormat() { - return $this->getUser() + return $this->getViewer() ->getPreference(PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT); } private function getDateFormat() { - return $this->getUser() + return $this->getViewer() ->getPreference(PhabricatorUserPreferences::PREFERENCE_DATE_FORMAT); } @@ -153,7 +153,7 @@ final class AphrontFormDateControl extends AphrontFormControl { private function formatTime($epoch, $fmt) { return phabricator_format_local_time( $epoch, - $this->user, + $this->getViewer(), $fmt); } @@ -259,7 +259,7 @@ final class AphrontFormDateControl extends AphrontFormControl { ), $time_sel); - $preferences = $this->user->loadPreferences(); + $preferences = $this->getViewer()->loadPreferences(); $pref_week_start = PhabricatorUserPreferences::PREFERENCE_WEEK_START_DAY; $week_start = $preferences->getPreference($pref_week_start, 0); @@ -300,12 +300,9 @@ final class AphrontFormDateControl extends AphrontFormControl { return $this->zone; } - $user = $this->getUser(); - if (!$this->getUser()) { - throw new PhutilInvalidStateException('setUser'); - } + $viewer = $this->getViewer(); - $user_zone = $user->getTimezoneIdentifier(); + $user_zone = $viewer->getTimezoneIdentifier(); $this->zone = new DateTimeZone($user_zone); return $this->zone; } diff --git a/src/view/form/control/AphrontFormTokenizerControl.php b/src/view/form/control/AphrontFormTokenizerControl.php index 3f24dd1348..3d65c4e525 100644 --- a/src/view/form/control/AphrontFormTokenizerControl.php +++ b/src/view/form/control/AphrontFormTokenizerControl.php @@ -90,8 +90,8 @@ final class AphrontFormTokenizerControl extends AphrontFormControl { } $username = null; - if ($this->user) { - $username = $this->user->getUsername(); + if ($this->hasViewer()) { + $username = $this->getViewer()->getUsername(); } $datasource_uri = $datasource->getDatasourceURI(); diff --git a/src/view/layout/AphrontSideNavFilterView.php b/src/view/layout/AphrontSideNavFilterView.php index 3a267510f0..d59bf5ccac 100644 --- a/src/view/layout/AphrontSideNavFilterView.php +++ b/src/view/layout/AphrontSideNavFilterView.php @@ -199,9 +199,6 @@ final class AphrontSideNavFilterView extends AphrontView { } private function renderFlexNav() { - - $user = $this->user; - require_celerity_resource('phabricator-nav-view-css'); $nav_classes = array(); diff --git a/src/view/layout/PhabricatorActionListView.php b/src/view/layout/PhabricatorActionListView.php index 6c343af4fe..4965f02793 100644 --- a/src/view/layout/PhabricatorActionListView.php +++ b/src/view/layout/PhabricatorActionListView.php @@ -22,9 +22,7 @@ final class PhabricatorActionListView extends AphrontView { } public function render() { - if (!$this->user) { - throw new PhutilInvalidStateException('setUser'); - } + $viewer = $this->getViewer(); $event = new PhabricatorEvent( PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS, @@ -32,7 +30,7 @@ final class PhabricatorActionListView extends AphrontView { 'object' => $this->object, 'actions' => $this->actions, )); - $event->setUser($this->user); + $event->setUser($viewer); PhutilEventEngine::dispatchEvent($event); $actions = $event->getValue('actions'); @@ -41,7 +39,7 @@ final class PhabricatorActionListView extends AphrontView { } foreach ($actions as $action) { - $action->setUser($this->user); + $action->setViewer($viewer); } require_celerity_resource('phabricator-action-list-view-css'); diff --git a/src/view/layout/PhabricatorActionView.php b/src/view/layout/PhabricatorActionView.php index b89c00daf5..3efa32071d 100644 --- a/src/view/layout/PhabricatorActionView.php +++ b/src/view/layout/PhabricatorActionView.php @@ -125,11 +125,11 @@ final class PhabricatorActionView extends AphrontView { $sigils = $sigils ? implode(' ', $sigils) : null; if ($this->renderAsForm) { - if (!$this->user) { + if (!$this->hasViewer()) { throw new Exception( pht( 'Call %s when rendering an action as a form.', - 'setUser()')); + 'setViewer()')); } $item = javelin_tag( @@ -140,7 +140,7 @@ final class PhabricatorActionView extends AphrontView { array($icon, $this->name)); $item = phabricator_form( - $this->user, + $this->getViewer(), array( 'action' => $this->getHref(), 'method' => 'POST', diff --git a/src/view/page/menu/PhabricatorMainMenuSearchView.php b/src/view/page/menu/PhabricatorMainMenuSearchView.php index a5c5653cc3..d3c7319f7d 100644 --- a/src/view/page/menu/PhabricatorMainMenuSearchView.php +++ b/src/view/page/menu/PhabricatorMainMenuSearchView.php @@ -24,7 +24,7 @@ final class PhabricatorMainMenuSearchView extends AphrontView { } public function render() { - $user = $this->user; + $viewer = $this->getViewer(); $target_id = celerity_generate_unique_node_id(); $search_id = $this->getID(); @@ -86,7 +86,7 @@ final class PhabricatorMainMenuSearchView extends AphrontView { $selector = $this->buildModeSelector($selector_id, $application_id); $form = phabricator_form( - $user, + $viewer, array( 'action' => '/search/', 'method' => 'POST', @@ -109,7 +109,7 @@ final class PhabricatorMainMenuSearchView extends AphrontView { } private function buildModeSelector($selector_id, $application_id) { - $viewer = $this->getUser(); + $viewer = $this->getViewer(); $items = array(); $items[] = array( diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index 5ce25fb3da..e804573ff0 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -24,7 +24,7 @@ final class PhabricatorMainMenuView extends AphrontView { } public function render() { - $user = $this->user; + $viewer = $this->getViewer(); require_celerity_resource('phabricator-main-menu-view'); @@ -35,7 +35,7 @@ final class PhabricatorMainMenuView extends AphrontView { $app_button = ''; $aural = null; - if ($user->isLoggedIn() && $user->isUserActivated()) { + if ($viewer->isLoggedIn() && $viewer->isUserActivated()) { list($menu, $dropdowns, $aural) = $this->renderNotificationMenu(); if (array_filter($menu)) { $alerts[] = $menu; @@ -77,10 +77,10 @@ final class PhabricatorMainMenuView extends AphrontView { $controller = $this->getController(); foreach ($applications as $application) { $app_actions = $application->buildMainMenuItems( - $user, + $viewer, $controller); $app_extra = $application->buildMainMenuExtraNodes( - $user, + $viewer, $controller); foreach ($app_actions as $action) { @@ -97,7 +97,7 @@ final class PhabricatorMainMenuView extends AphrontView { $extensions = PhabricatorMainMenuBarExtension::getAllEnabledExtensions(); foreach ($extensions as $extension) { - $extension->setViewer($user); + $extension->setViewer($viewer); $controller = $this->getController(); if ($controller) { @@ -158,7 +158,7 @@ final class PhabricatorMainMenuView extends AphrontView { } private function renderSearch() { - $user = $this->user; + $viewer = $this->getViewer(); $result = null; @@ -166,15 +166,15 @@ final class PhabricatorMainMenuView extends AphrontView { 'helpURI' => '/help/keyboardshortcut/', ); - if ($user->isLoggedIn()) { - $show_search = $user->isUserActivated(); + if ($viewer->isLoggedIn()) { + $show_search = $viewer->isUserActivated(); } else { $show_search = PhabricatorEnv::getEnvConfig('policy.allow-public'); } if ($show_search) { $search = new PhabricatorMainMenuSearchView(); - $search->setUser($user); + $search->setViewer($viewer); $application = null; $controller = $this->getController(); @@ -188,7 +188,7 @@ final class PhabricatorMainMenuView extends AphrontView { $result = $search; $pref_shortcut = PhabricatorUserPreferences::PREFERENCE_SEARCH_SHORTCUT; - if ($user->loadPreferences()->getPreference($pref_shortcut, true)) { + if ($viewer->loadPreferences()->getPreference($pref_shortcut, true)) { $keyboard_config['searchID'] = $search->getID(); } } @@ -230,7 +230,7 @@ final class PhabricatorMainMenuView extends AphrontView { } private function renderApplicationMenu(array $bar_items) { - $user = $this->getUser(); + $viewer = $this->getViewer(); $view = $this->getApplicationMenu(); @@ -302,7 +302,7 @@ final class PhabricatorMainMenuView extends AphrontView { $logo_uri = $cache->getKey($cache_key_logo); if (!$logo_uri) { $file = id(new PhabricatorFileQuery()) - ->setViewer($this->getUser()) + ->setViewer($this->getViewer()) ->withPHIDs(array($custom_header)) ->executeOne(); if ($file) { @@ -355,7 +355,7 @@ final class PhabricatorMainMenuView extends AphrontView { } private function renderNotificationMenu() { - $user = $this->user; + $viewer = $this->getViewer(); require_celerity_resource('phabricator-notification-css'); require_celerity_resource('phabricator-notification-menu-css'); @@ -364,7 +364,7 @@ final class PhabricatorMainMenuView extends AphrontView { $aural = array(); $dropdown_query = id(new AphlictDropdownDataQuery()) - ->setViewer($user); + ->setViewer($viewer); $dropdown_data = $dropdown_query->execute(); $message_tag = ''; diff --git a/src/view/phui/PHUIFeedStoryView.php b/src/view/phui/PHUIFeedStoryView.php index bacd266089..978e25062f 100644 --- a/src/view/phui/PHUIFeedStoryView.php +++ b/src/view/phui/PHUIFeedStoryView.php @@ -172,8 +172,8 @@ final class PHUIFeedStoryView extends AphrontView { if ($this->epoch) { // TODO: This is really bad; when rendering through Conduit and via // renderText() we don't have a user. - if ($this->user) { - $foot = phabricator_datetime($this->epoch, $this->user); + if ($this->hasViewer()) { + $foot = phabricator_datetime($this->epoch, $this->getViewer()); } else { $foot = null; } diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index 34299e39c3..d71554abe5 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -278,7 +278,7 @@ final class PHUICalendarDayView extends AphrontView { ->addClass('calendar-day-view-sidebar'); $list = id(new PHUICalendarListView()) - ->setUser($this->user) + ->setUser($this->getViewer()) ->setView('day'); if (count($events) == 0) { @@ -304,7 +304,7 @@ final class PHUICalendarDayView extends AphrontView { $box_start_time = clone $display_start_day; - $today_time = PhabricatorTime::getTodayMidnightDateTime($this->user); + $today_time = PhabricatorTime::getTodayMidnightDateTime($this->getViewer()); $tomorrow_time = clone $today_time; $tomorrow_time->modify('+1 day'); @@ -437,7 +437,7 @@ final class PHUICalendarDayView extends AphrontView { } private function getDateTime() { - $user = $this->user; + $user = $this->getViewer(); $timezone = new DateTimeZone($user->getTimezoneIdentifier()); $day = $this->day; diff --git a/src/view/phui/calendar/PHUICalendarMonthView.php b/src/view/phui/calendar/PHUICalendarMonthView.php index 5efa4c1059..d40736494e 100644 --- a/src/view/phui/calendar/PHUICalendarMonthView.php +++ b/src/view/phui/calendar/PHUICalendarMonthView.php @@ -51,9 +51,7 @@ final class PHUICalendarMonthView extends AphrontView { } public function render() { - if (empty($this->user)) { - throw new PhutilInvalidStateException('setUser'); - } + $viewer = $this->getViewer(); $events = msort($this->events, 'getEpochStart'); $days = $this->getDatesInMonth(); @@ -93,7 +91,7 @@ final class PHUICalendarMonthView extends AphrontView { $counter = 0; $list = new PHUICalendarListView(); - $list->setUser($this->user); + $list->setViewer($viewer); foreach ($all_day_events as $item) { if ($counter <= $max_daily) { $list->addEvent($item); @@ -495,9 +493,9 @@ final class PHUICalendarMonthView extends AphrontView { * @return list List of DateTimes, one for each day. */ private function getDatesInMonth() { - $user = $this->user; + $viewer = $this->getViewer(); - $timezone = new DateTimeZone($user->getTimezoneIdentifier()); + $timezone = new DateTimeZone($viewer->getTimezoneIdentifier()); $month = $this->month; $year = $this->year; @@ -575,7 +573,7 @@ final class PHUICalendarMonthView extends AphrontView { } private function getWeekStartAndEnd() { - $preferences = $this->user->loadPreferences(); + $preferences = $this->getViewer()->loadPreferences(); $pref_week_start = PhabricatorUserPreferences::PREFERENCE_WEEK_START_DAY; $week_start = $preferences->getPreference($pref_week_start, 0); @@ -585,7 +583,7 @@ final class PHUICalendarMonthView extends AphrontView { } private function getDateTime() { - $user = $this->user; + $user = $this->getViewer(); $timezone = new DateTimeZone($user->getTimezoneIdentifier()); $month = $this->month; From 61f82bb97b8a2fe45cdf7628cc1646f07a065671 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 5 Mar 2016 14:45:56 -0800 Subject: [PATCH 13/57] Introduce "Curtain" views, panels, and extensions Summary: This opens up the new action column to have specialized rendering and behavior. Briefly: - Converted applications (right now, only Paste) render a `CurtainView` to build the column content. - This view uses new extensions to build panels (projects, subscribers, tokens). - The panel extension code and rendering can be changed without breaking old stuff. Minor changes: - Token awards now load their tokens, for consistency/simplicity. - Removed the rest of the "fork of" / "forked from" UI in Paste -- I essentially removed these features a while ago, and no one has complained. Test Plan: UI is a bit rough, but works, and it's going to get changed now anyway: {F1160550} {F1160551} Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D15414 --- resources/celerity/map.php | 2 + src/__phutil_library_map__.php | 12 ++ .../base/controller/PhabricatorController.php | 21 ++- .../PhabricatorPasteViewController.php | 102 +++++--------- .../PhabricatorProjectsCurtainExtension.php | 91 +++++++++++++ ...abricatorSubscriptionsCurtainExtension.php | 39 ++++++ .../PhabricatorTokensCurtainExtension.php | 67 ++++++++++ .../query/PhabricatorTokenGivenQuery.php | 29 +++- .../tokens/storage/PhabricatorTokenGiven.php | 10 ++ src/view/extension/PHUICurtainExtension.php | 124 ++++++++++++++++++ src/view/layout/PHUICurtainPanelView.php | 63 +++++++++ src/view/layout/PHUICurtainView.php | 52 ++++++++ src/view/phui/PHUITwoColumnView.php | 25 +++- webroot/rsrc/css/phui/phui-curtain-view.css | 22 ++++ 14 files changed, 586 insertions(+), 73 deletions(-) create mode 100644 src/applications/project/engineextension/PhabricatorProjectsCurtainExtension.php create mode 100644 src/applications/subscriptions/engineextension/PhabricatorSubscriptionsCurtainExtension.php create mode 100644 src/applications/tokens/engineextension/PhabricatorTokensCurtainExtension.php create mode 100644 src/view/extension/PHUICurtainExtension.php create mode 100644 src/view/layout/PHUICurtainPanelView.php create mode 100644 src/view/layout/PHUICurtainView.php create mode 100644 webroot/rsrc/css/phui/phui-curtain-view.css diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 4ab5e3cd3a..dce8137749 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -127,6 +127,7 @@ return array( '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-curtain-view.css' => '8bb7ee8f', 'rsrc/css/phui/phui-document-pro.css' => '92d5b648', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => '9c71d2bf', @@ -811,6 +812,7 @@ return array( 'phui-calendar-month-css' => '476be7e0', 'phui-chart-css' => '6bf6f78e', 'phui-crumbs-view-css' => '79d536e5', + 'phui-curtain-view-css' => '8bb7ee8f', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => '9c71d2bf', 'phui-document-view-pro-css' => '92d5b648', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 0a5b5e7cd9..45adf16a3a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1505,6 +1505,9 @@ phutil_register_library_map(array( 'PHUIColorPalletteExample' => 'applications/uiexample/examples/PHUIColorPalletteExample.php', 'PHUICrumbView' => 'view/phui/PHUICrumbView.php', 'PHUICrumbsView' => 'view/phui/PHUICrumbsView.php', + 'PHUICurtainExtension' => 'view/extension/PHUICurtainExtension.php', + 'PHUICurtainPanelView' => 'view/layout/PHUICurtainPanelView.php', + 'PHUICurtainView' => 'view/layout/PHUICurtainView.php', 'PHUIDiffInlineCommentDetailView' => 'infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php', 'PHUIDiffInlineCommentEditView' => 'infrastructure/diff/view/PHUIDiffInlineCommentEditView.php', 'PHUIDiffInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php', @@ -3011,6 +3014,7 @@ phutil_register_library_map(array( 'PhabricatorProjectWatcherListView' => 'applications/project/view/PhabricatorProjectWatcherListView.php', 'PhabricatorProjectWorkboardBackgroundColor' => 'applications/project/constants/PhabricatorProjectWorkboardBackgroundColor.php', 'PhabricatorProjectWorkboardProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectWorkboardProfilePanel.php', + 'PhabricatorProjectsCurtainExtension' => 'applications/project/engineextension/PhabricatorProjectsCurtainExtension.php', 'PhabricatorProjectsEditEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php', 'PhabricatorProjectsEditField' => 'applications/transactions/editfield/PhabricatorProjectsEditField.php', 'PhabricatorProjectsFulltextEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsFulltextEngineExtension.php', @@ -3320,6 +3324,7 @@ phutil_register_library_map(array( 'PhabricatorSubscriptionsAddSelfHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsAddSelfHeraldAction.php', 'PhabricatorSubscriptionsAddSubscribersHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php', 'PhabricatorSubscriptionsApplication' => 'applications/subscriptions/application/PhabricatorSubscriptionsApplication.php', + 'PhabricatorSubscriptionsCurtainExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsCurtainExtension.php', 'PhabricatorSubscriptionsEditController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php', 'PhabricatorSubscriptionsEditEngineExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php', 'PhabricatorSubscriptionsEditor' => 'applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php', @@ -3388,6 +3393,7 @@ phutil_register_library_map(array( 'PhabricatorTokenUIEventListener' => 'applications/tokens/event/PhabricatorTokenUIEventListener.php', 'PhabricatorTokenizerEditField' => 'applications/transactions/editfield/PhabricatorTokenizerEditField.php', 'PhabricatorTokensApplication' => 'applications/tokens/application/PhabricatorTokensApplication.php', + 'PhabricatorTokensCurtainExtension' => 'applications/tokens/engineextension/PhabricatorTokensCurtainExtension.php', 'PhabricatorTokensSettingsPanel' => 'applications/settings/panel/PhabricatorTokensSettingsPanel.php', 'PhabricatorTooltipUIExample' => 'applications/uiexample/examples/PhabricatorTooltipUIExample.php', 'PhabricatorTransactions' => 'applications/transactions/constants/PhabricatorTransactions.php', @@ -5751,6 +5757,9 @@ phutil_register_library_map(array( 'PHUIColorPalletteExample' => 'PhabricatorUIExample', 'PHUICrumbView' => 'AphrontView', 'PHUICrumbsView' => 'AphrontView', + 'PHUICurtainExtension' => 'Phobject', + 'PHUICurtainPanelView' => 'AphrontTagView', + 'PHUICurtainView' => 'AphrontTagView', 'PHUIDiffInlineCommentDetailView' => 'PHUIDiffInlineCommentView', 'PHUIDiffInlineCommentEditView' => 'PHUIDiffInlineCommentView', 'PHUIDiffInlineCommentRowScaffold' => 'AphrontView', @@ -7503,6 +7512,7 @@ phutil_register_library_map(array( 'PhabricatorProjectWatcherListView' => 'PhabricatorProjectUserListView', 'PhabricatorProjectWorkboardBackgroundColor' => 'Phobject', 'PhabricatorProjectWorkboardProfilePanel' => 'PhabricatorProfilePanel', + 'PhabricatorProjectsCurtainExtension' => 'PHUICurtainExtension', 'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension', 'PhabricatorProjectsEditField' => 'PhabricatorTokenizerEditField', 'PhabricatorProjectsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', @@ -7872,6 +7882,7 @@ phutil_register_library_map(array( 'PhabricatorSubscriptionsAddSelfHeraldAction' => 'PhabricatorSubscriptionsHeraldAction', 'PhabricatorSubscriptionsAddSubscribersHeraldAction' => 'PhabricatorSubscriptionsHeraldAction', 'PhabricatorSubscriptionsApplication' => 'PhabricatorApplication', + 'PhabricatorSubscriptionsCurtainExtension' => 'PHUICurtainExtension', 'PhabricatorSubscriptionsEditController' => 'PhabricatorController', 'PhabricatorSubscriptionsEditEngineExtension' => 'PhabricatorEditEngineExtension', 'PhabricatorSubscriptionsEditor' => 'PhabricatorEditor', @@ -7945,6 +7956,7 @@ phutil_register_library_map(array( 'PhabricatorTokenUIEventListener' => 'PhabricatorEventListener', 'PhabricatorTokenizerEditField' => 'PhabricatorPHIDListEditField', 'PhabricatorTokensApplication' => 'PhabricatorApplication', + 'PhabricatorTokensCurtainExtension' => 'PHUICurtainExtension', 'PhabricatorTokensSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorTooltipUIExample' => 'PhabricatorUIExample', 'PhabricatorTransactions' => 'Phobject', diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index e6997d97ab..fffe299b3f 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -468,7 +468,26 @@ abstract class PhabricatorController extends AphrontController { public function newApplicationMenu() { return id(new PHUIApplicationMenuView()) - ->setViewer($this->getRequest()->getUser()); + ->setViewer($this->getViewer()); + } + + public function newCurtainView($object) { + $viewer = $this->getViewer(); + + $action_list = id(new PhabricatorActionListView()) + ->setViewer($viewer) + ->setObject($object); + + $curtain = id(new PHUICurtainView()) + ->setViewer($viewer) + ->setActionList($action_list); + + $panels = PHUICurtainExtension::buildExtensionPanels($viewer, $object); + foreach ($panels as $panel) { + $curtain->addPanel($panel); + } + + return $curtain; } protected function buildTransactionTimeline( diff --git a/src/applications/paste/controller/PhabricatorPasteViewController.php b/src/applications/paste/controller/PhabricatorPasteViewController.php index 18d3ad1fb6..f29c93a162 100644 --- a/src/applications/paste/controller/PhabricatorPasteViewController.php +++ b/src/applications/paste/controller/PhabricatorPasteViewController.php @@ -40,15 +40,9 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { return new Aphront404Response(); } - $forks = id(new PhabricatorPasteQuery()) - ->setViewer($viewer) - ->withParentPHIDs(array($paste->getPHID())) - ->execute(); - $fork_phids = mpull($forks, 'getPHID'); - $header = $this->buildHeaderView($paste); - $actions = $this->buildActionView($viewer, $paste); - $properties = $this->buildPropertyView($paste, $fork_phids); + $curtain = $this->buildCurtain($paste); + $subheader = $this->buildSubheaderView($paste); $source_code = $this->buildSourceCodeView($paste, $this->highlightMap); @@ -78,8 +72,7 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { $timeline, $comment_view, )) - ->setPropertyList($properties) - ->setActionList($actions) + ->setCurtain($curtain) ->addClass('ponder-question-view'); return $this->newPage() @@ -116,9 +109,9 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { return $header; } - private function buildActionView( - PhabricatorUser $viewer, - PhabricatorPaste $paste) { + private function buildCurtain(PhabricatorPaste $paste) { + $viewer = $this->getViewer(); + $curtain = $this->newCurtainView($paste); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -126,43 +119,42 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { PhabricatorPolicyCapability::CAN_EDIT); $id = $paste->getID(); + $edit_uri = $this->getApplicationURI("edit/{$id}/"); + $archive_uri = $this->getApplicationURI("archive/{$id}/"); + $raw_uri = $this->getApplicationURI("raw/{$id}/"); - $action_list = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($paste); - - $action_list->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Paste')) - ->setIcon('fa-pencil') - ->setDisabled(!$can_edit) - ->setHref($this->getApplicationURI("edit/{$id}/"))); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Paste')) + ->setIcon('fa-pencil') + ->setDisabled(!$can_edit) + ->setHref($edit_uri)); if ($paste->isArchived()) { - $action_list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) - ->setName(pht('Activate Paste')) - ->setIcon('fa-check') - ->setDisabled(!$can_edit) - ->setWorkflow($can_edit) - ->setHref($this->getApplicationURI("archive/{$id}/"))); + ->setName(pht('Activate Paste')) + ->setIcon('fa-check') + ->setDisabled(!$can_edit) + ->setWorkflow($can_edit) + ->setHref($archive_uri)); } else { - $action_list->addAction( + $curtain->addAction( id(new PhabricatorActionView()) - ->setName(pht('Archive Paste')) - ->setIcon('fa-ban') - ->setDisabled(!$can_edit) - ->setWorkflow($can_edit) - ->setHref($this->getApplicationURI("archive/{$id}/"))); + ->setName(pht('Archive Paste')) + ->setIcon('fa-ban') + ->setDisabled(!$can_edit) + ->setWorkflow($can_edit) + ->setHref($archive_uri)); } - $action_list->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View Raw File')) - ->setIcon('fa-file-text-o') - ->setHref($this->getApplicationURI("raw/{$id}/"))); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('View Raw File')) + ->setIcon('fa-file-text-o') + ->setHref($raw_uri)); - return $action_list; + return $curtain; } @@ -191,32 +183,4 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { ->setContent($content); } - private function buildPropertyView( - PhabricatorPaste $paste, - array $child_phids) { - $viewer = $this->getViewer(); - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($paste); - - if ($paste->getParentPHID()) { - $properties->addProperty( - pht('Forked From'), - $viewer->renderHandle($paste->getParentPHID())); - } - - if ($child_phids) { - $properties->addProperty( - pht('Forks'), - $viewer->renderHandleList($child_phids)); - } - - $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( - $viewer, - $paste); - - return $properties; - } - } diff --git a/src/applications/project/engineextension/PhabricatorProjectsCurtainExtension.php b/src/applications/project/engineextension/PhabricatorProjectsCurtainExtension.php new file mode 100644 index 0000000000..cc394c2283 --- /dev/null +++ b/src/applications/project/engineextension/PhabricatorProjectsCurtainExtension.php @@ -0,0 +1,91 @@ +getViewer(); + + $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( + $object->getPHID(), + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); + + $has_projects = (bool)$project_phids; + $project_phids = array_reverse($project_phids); + $handles = $viewer->loadHandles($project_phids); + + // If this object can appear on boards, build the workboard annotations. + // Some day, this might be a generic interface. For now, only tasks can + // appear on boards. + $can_appear_on_boards = ($object instanceof ManiphestTask); + + $annotations = array(); + if ($has_projects && $can_appear_on_boards) { + $engine = id(new PhabricatorBoardLayoutEngine()) + ->setViewer($viewer) + ->setBoardPHIDs($project_phids) + ->setObjectPHIDs(array($object->getPHID())) + ->executeLayout(); + + // TDOO: Generalize this UI and move it out of Maniphest. + require_celerity_resource('maniphest-task-summary-css'); + + foreach ($project_phids as $project_phid) { + $handle = $handles[$project_phid]; + + $columns = $engine->getObjectColumns( + $project_phid, + $object->getPHID()); + + $annotation = array(); + foreach ($columns as $column) { + $project_id = $column->getProject()->getID(); + + $column_name = pht('(%s)', $column->getDisplayName()); + $column_link = phutil_tag( + 'a', + array( + 'href' => "/project/board/{$project_id}/", + 'class' => 'maniphest-board-link', + ), + $column_name); + + $annotation[] = $column_link; + } + + if ($annotation) { + $annotations[$project_phid] = array( + ' ', + phutil_implode_html(', ', $annotation), + ); + } + } + + } + + if ($has_projects) { + $list = id(new PHUIHandleTagListView()) + ->setHandles($handles) + ->setAnnotations($annotations) + ->setShowHovercards(true); + } else { + $list = phutil_tag('em', array(), pht('None')); + } + + return $this->newPanel() + ->setHeaderText(pht('Projects')) + ->setOrder(10000) + ->appendChild($list); + } + +} diff --git a/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsCurtainExtension.php b/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsCurtainExtension.php new file mode 100644 index 0000000000..6467141de2 --- /dev/null +++ b/src/applications/subscriptions/engineextension/PhabricatorSubscriptionsCurtainExtension.php @@ -0,0 +1,39 @@ +getViewer(); + $object_phid = $object->getPHID(); + + $subscriber_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID( + $object_phid); + + $handles = $viewer->loadHandles($subscriber_phids); + + // TODO: This class can't accept a HandleList yet. + $handles = iterator_to_array($handles); + + $susbscribers_view = id(new SubscriptionListStringBuilder()) + ->setObjectPHID($object_phid) + ->setHandles($handles) + ->buildPropertyString(); + + return $this->newPanel() + ->setHeaderText(pht('Subscribers')) + ->setOrder(20000) + ->appendChild($susbscribers_view); + } + +} diff --git a/src/applications/tokens/engineextension/PhabricatorTokensCurtainExtension.php b/src/applications/tokens/engineextension/PhabricatorTokensCurtainExtension.php new file mode 100644 index 0000000000..1d1ca2551c --- /dev/null +++ b/src/applications/tokens/engineextension/PhabricatorTokensCurtainExtension.php @@ -0,0 +1,67 @@ +getViewer(); + + $tokens_given = id(new PhabricatorTokenGivenQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($object->getPHID())) + ->execute(); + if (!$tokens_given) { + return null; + } + + $author_phids = mpull($tokens_given, 'getAuthorPHID'); + $handles = $viewer->loadHandles($author_phids); + + Javelin::initBehavior('phabricator-tooltips'); + + $list = array(); + foreach ($tokens_given as $token_given) { + $token = $token_given->getToken(); + + $aural = javelin_tag( + 'span', + array( + 'aural' => true, + ), + pht( + '"%s" token, awarded by %s.', + $token->getName(), + $handles[$token_given->getAuthorPHID()]->getName())); + + $list[] = javelin_tag( + 'span', + array( + 'sigil' => 'has-tooltip', + 'class' => 'token-icon', + 'meta' => array( + 'tip' => $handles[$token_given->getAuthorPHID()]->getName(), + ), + ), + array( + $aural, + $token->renderIcon(), + )); + } + + return $this->newPanel() + ->setHeaderText(pht('Tokens')) + ->setOrder(30000) + ->appendChild($list); + } + +} diff --git a/src/applications/tokens/query/PhabricatorTokenGivenQuery.php b/src/applications/tokens/query/PhabricatorTokenGivenQuery.php index 224efff198..b4a86428c1 100644 --- a/src/applications/tokens/query/PhabricatorTokenGivenQuery.php +++ b/src/applications/tokens/query/PhabricatorTokenGivenQuery.php @@ -58,10 +58,12 @@ final class PhabricatorTokenGivenQuery } protected function willFilterPage(array $results) { + $viewer = $this->getViewer(); + $object_phids = mpull($results, 'getObjectPHID'); $objects = id(new PhabricatorObjectQuery()) - ->setViewer($this->getViewer()) + ->setViewer($viewer) ->withPHIDs($object_phids) ->execute(); $objects = mpull($objects, null, 'getPHID'); @@ -80,6 +82,31 @@ final class PhabricatorTokenGivenQuery unset($results[$key]); } + if (!$results) { + return $results; + } + + $token_phids = mpull($results, 'getTokenPHID'); + + $tokens = id(new PhabricatorTokenQuery()) + ->setViewer($viewer) + ->withPHIDs($token_phids) + ->execute(); + $tokens = mpull($tokens, null, 'getPHID'); + + foreach ($results as $key => $result) { + $token_phid = $result->getTokenPHID(); + + $token = idx($tokens, $token_phid); + if (!$token) { + $this->didRejectResult($result); + unset($results[$key]); + continue; + } + + $result->attachToken($token); + } + return $results; } diff --git a/src/applications/tokens/storage/PhabricatorTokenGiven.php b/src/applications/tokens/storage/PhabricatorTokenGiven.php index 59ffc819d1..44db1baaf5 100644 --- a/src/applications/tokens/storage/PhabricatorTokenGiven.php +++ b/src/applications/tokens/storage/PhabricatorTokenGiven.php @@ -8,6 +8,7 @@ final class PhabricatorTokenGiven extends PhabricatorTokenDAO protected $tokenPHID; private $object = self::ATTACHABLE; + private $token = self::ATTACHABLE; protected function getConfiguration() { return array( @@ -35,6 +36,15 @@ final class PhabricatorTokenGiven extends PhabricatorTokenDAO return $this->assertAttached($this->object); } + public function attachToken(PhabricatorToken $token) { + $this->token = $token; + return $this; + } + + public function getToken() { + return $this->assertAttached($this->token); + } + public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, diff --git a/src/view/extension/PHUICurtainExtension.php b/src/view/extension/PHUICurtainExtension.php new file mode 100644 index 0000000000..7f7693cbac --- /dev/null +++ b/src/view/extension/PHUICurtainExtension.php @@ -0,0 +1,124 @@ +viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + abstract public function shouldEnableForObject($object); + abstract public function getExtensionApplication(); + + public function buildCurtainPanels($object) { + $panel = $this->buildCurtainPanel($object); + + if ($panel !== null) { + return array($panel); + } + + return array(); + } + + public function buildCurtainPanel($object) { + throw new PhutilMethodNotImplementedException(); + } + + final public function getExtensionKey() { + return $this->getPhobjectClassConstant('EXTENSIONKEY'); + } + + final public static function getAllExtensions() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getExtensionKey') + ->execute(); + } + + protected function newPanel() { + return new PHUICurtainPanelView(); + } + + final public static function buildExtensionPanels( + PhabricatorUser $viewer, + $object) { + + $extensions = self::getAllExtensions(); + foreach ($extensions as $extension) { + $extension->setViewer($viewer); + } + + foreach ($extensions as $key => $extension) { + $application = $extension->getExtensionApplication(); + if (!($application instanceof PhabricatorApplication)) { + throw new Exception( + pht( + 'Curtain extension ("%s", of class "%s") did not return an '. + 'application from method "%s". This method must return an '. + 'object of class "%s".', + $key, + get_class($extension), + 'getExtensionApplication()', + 'PhabricatorApplication')); + } + + $has_application = PhabricatorApplication::isClassInstalledForViewer( + get_class($application), + $viewer); + + if (!$has_application) { + unset($extensions[$key]); + } + } + + foreach ($extensions as $key => $extension) { + if (!$extension->shouldEnableForObject($object)) { + unset($extensions[$key]); + } + } + + $result = array(); + + foreach ($extensions as $key => $extension) { + $panels = $extension->buildCurtainPanels($object); + if (!is_array($panels)) { + throw new Exception( + pht( + 'Curtain extension ("%s", of class "%s") did not return a list of '. + 'curtain panels from method "%s". This method must return an '. + 'array, and each value in the array must be a "%s" object.', + $key, + get_class($extension), + 'buildCurtainPanels()', + 'PHUICurtainPanelView')); + } + + foreach ($panels as $panel_key => $panel) { + if (!($panel instanceof PHUICurtainPanelView)) { + throw new Exception( + pht( + 'Curtain extension ("%s", of class "%s") returned a list of '. + 'curtain panels from "%s" that contains an invalid value: '. + 'a value (with key "%s") is not an object of class "%s". '. + 'Each item in the returned array must be a panel.', + $key, + get_class($extension), + 'buildCurtainPanels()', + $panel_key, + 'PHUICurtainPanelView')); + } + + $result[] = $panel; + } + } + + return $result; + } + +} diff --git a/src/view/layout/PHUICurtainPanelView.php b/src/view/layout/PHUICurtainPanelView.php new file mode 100644 index 0000000000..238e56a374 --- /dev/null +++ b/src/view/layout/PHUICurtainPanelView.php @@ -0,0 +1,63 @@ +headerText = $header_text; + return $this; + } + + public function getHeaderText() { + return $this->headerText; + } + + public function setOrder($order) { + $this->order = $order; + return $this; + } + + public function getOrder() { + return $this->order; + } + + public function getOrderVector() { + return id(new PhutilSortVector()) + ->addInt($this->getOrder()); + } + + protected function getTagAttributes() { + return array( + 'class' => 'phui-curtain-panel', + ); + } + + protected function getTagContent() { + $header = null; + + $header_text = $this->getHeaderText(); + if (strlen($header_text)) { + $header = phutil_tag( + 'div', + array( + 'class' => 'phui-curtain-panel-header', + ), + $header_text); + } + + $body = phutil_tag( + 'div', + array( + 'class' => 'phui-curtain-panel-body', + ), + $this->renderChildren()); + + return array( + $header, + $body, + ); + } + +} diff --git a/src/view/layout/PHUICurtainView.php b/src/view/layout/PHUICurtainView.php new file mode 100644 index 0000000000..6d2388802b --- /dev/null +++ b/src/view/layout/PHUICurtainView.php @@ -0,0 +1,52 @@ +getActionList()->addAction($action); + return $this; + } + + public function addPanel(PHUICurtainPanelView $curtain_panel) { + $this->panels[] = $curtain_panel; + return $this; + } + + public function setActionList(PhabricatorActionListView $action_list) { + $this->actionList = $action_list; + return $this; + } + + public function getActionList() { + return $this->actionList; + } + + protected function canAppendChild() { + return false; + } + + protected function getTagContent() { + $action_list = $this->actionList; + + require_celerity_resource('phui-curtain-view-css'); + + $panels = $this->renderPanels(); + + return id(new PHUIObjectBoxView()) + ->appendChild($action_list) + ->appendChild($panels) + ->addClass('phui-two-column-properties'); + } + + private function renderPanels() { + $panels = $this->panels; + $panels = msortv($panels, 'getOrderVector'); + + return $panels; + } + + +} diff --git a/src/view/phui/PHUITwoColumnView.php b/src/view/phui/PHUITwoColumnView.php index fb27a1a50d..1977ddce5e 100644 --- a/src/view/phui/PHUITwoColumnView.php +++ b/src/view/phui/PHUITwoColumnView.php @@ -11,6 +11,7 @@ final class PHUITwoColumnView extends AphrontTagView { private $propertySection = array(); private $actionList; private $propertyList; + private $curtain; const DISPLAY_LEFT = 'phui-side-column-left'; const DISPLAY_RIGHT = 'phui-side-column-right'; @@ -50,6 +51,15 @@ final class PHUITwoColumnView extends AphrontTagView { return $this; } + public function setCurtain(PHUICurtainView $curtain) { + $this->curtain = $curtain; + return $this; + } + + public function getCurtain() { + return $this->curtain; + } + public function setFluid($fluid) { $this->fluid = $fluid; return $this; @@ -98,9 +108,17 @@ final class PHUITwoColumnView extends AphrontTagView { $header = null; if ($this->header) { - if ($this->actionList) { - $this->header->setActionList($this->actionList); + $curtain = $this->getCurtain(); + if ($curtain) { + $action_list = $curtain->getActionList(); + } else { + $action_list = $this->actionList; } + + if ($action_list) { + $this->header->setActionList($action_list); + } + $header = phutil_tag_div( 'phui-two-column-header', $this->header); } @@ -166,6 +184,8 @@ final class PHUITwoColumnView extends AphrontTagView { ->addClass('phui-two-column-properties'); } + $curtain = $this->getCurtain(); + return phutil_tag( 'div', array( @@ -173,6 +193,7 @@ final class PHUITwoColumnView extends AphrontTagView { ), array( $properties, + $curtain, $this->sideColumn, )); } diff --git a/webroot/rsrc/css/phui/phui-curtain-view.css b/webroot/rsrc/css/phui/phui-curtain-view.css new file mode 100644 index 0000000000..0c507c8e84 --- /dev/null +++ b/webroot/rsrc/css/phui/phui-curtain-view.css @@ -0,0 +1,22 @@ +/** + * @provides phui-curtain-view-css + */ + +.phui-curtain-panel { + margin: 4px; + padding: 4px 0; +} + +.device-desktop .phui-curtain-panel { + border-top: 1px solid {$lightblueborder}; +} + +.phui-curtain-panel-header { + padding: 4px 0 0; + color: {$bluetext}; + font-weight: bold; +} + +.phui-curtain-panel-body { + padding: 4px 0 0; +} From 11774ef29031f672de8a13244d7eaf1cfbba65ae Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 6 Mar 2016 09:38:29 -0800 Subject: [PATCH 14/57] Use curtain views in Almanac Summary: Convert Almanac interfaces to Curtain views. Test Plan: Viewed Services, Bindings, Devices, Namespaces and Networks. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D15415 --- .../AlmanacBindingViewController.php | 29 +++++++------ .../AlmanacDeviceViewController.php | 33 +++++---------- .../AlmanacNamespaceViewController.php | 35 +++++----------- .../AlmanacNetworkViewController.php | 36 ++++++---------- .../AlmanacServiceViewController.php | 41 ++++++------------- 5 files changed, 60 insertions(+), 114 deletions(-) diff --git a/src/applications/almanac/controller/AlmanacBindingViewController.php b/src/applications/almanac/controller/AlmanacBindingViewController.php index 32915593cf..ead3e1b4c1 100644 --- a/src/applications/almanac/controller/AlmanacBindingViewController.php +++ b/src/applications/almanac/controller/AlmanacBindingViewController.php @@ -28,7 +28,7 @@ final class AlmanacBindingViewController $properties = $this->buildPropertyList($binding); $details = $this->buildPropertySection($binding); - $actions = $this->buildActionList($binding); + $curtain = $this->buildCurtain($binding); $header = id(new PHUIHeaderView()) ->setUser($viewer) @@ -62,14 +62,13 @@ final class AlmanacBindingViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setCurtain($curtain) ->setMainColumn(array( $issue, $this->buildAlmanacPropertiesTable($binding), $timeline, )) - ->setPropertyList($properties) - ->addPropertySection(pht('DETAILS'), $details) - ->setActionList($actions); + ->addPropertySection(pht('DETAILS'), $details); return $this->newPage() ->setTitle($title) @@ -116,23 +115,25 @@ final class AlmanacBindingViewController return $properties; } - private function buildActionList(AlmanacBinding $binding) { + private function buildCurtain(AlmanacBinding $binding) { $viewer = $this->getViewer(); - $id = $binding->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $binding, PhabricatorPolicyCapability::CAN_EDIT); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); + $id = $binding->getID(); + $edit_uri = $this->getApplicationURI("binding/edit/{$id}/"); + $disable_uri = $this->getApplicationURI("binding/disable/{$id}/"); - $actions->addAction( + $curtain = $this->newCurtainView($binding); + + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Binding')) - ->setHref($this->getApplicationURI("binding/edit/{$id}/")) + ->setHref($edit_uri) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); @@ -144,17 +145,15 @@ final class AlmanacBindingViewController $disable_text = pht('Disable Binding'); } - $disable_href = $this->getApplicationURI("binding/disable/{$id}/"); - - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon($disable_icon) ->setName($disable_text) - ->setHref($disable_href) + ->setHref($disable_uri) ->setWorkflow(true) ->setDisabled(!$can_edit)); - return $actions; + return $curtain; } } diff --git a/src/applications/almanac/controller/AlmanacDeviceViewController.php b/src/applications/almanac/controller/AlmanacDeviceViewController.php index f6bf697e31..efc4334132 100644 --- a/src/applications/almanac/controller/AlmanacDeviceViewController.php +++ b/src/applications/almanac/controller/AlmanacDeviceViewController.php @@ -23,8 +23,7 @@ final class AlmanacDeviceViewController $title = pht('Device %s', $device->getName()); - $properties = $this->buildPropertyList($device); - $actions = $this->buildActionList($device); + $curtain = $this->buildCurtain($device); $header = id(new PHUIHeaderView()) ->setUser($viewer) @@ -55,6 +54,7 @@ final class AlmanacDeviceViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setCurtain($curtain) ->setMainColumn(array( $issue, $interfaces, @@ -62,9 +62,7 @@ final class AlmanacDeviceViewController $this->buildSSHKeysTable($device), $this->buildServicesTable($device), $timeline, - )) - ->setPropertyList($properties) - ->setActionList($actions); + )); return $this->newPage() ->setTitle($title) @@ -75,37 +73,28 @@ final class AlmanacDeviceViewController )); } - private function buildPropertyList(AlmanacDevice $device) { + private function buildCurtain(AlmanacDevice $device) { $viewer = $this->getViewer(); - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($device); - - return $properties; - } - - private function buildActionList(AlmanacDevice $device) { - $viewer = $this->getViewer(); - $id = $device->getID(); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $device, PhabricatorPolicyCapability::CAN_EDIT); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); + $id = $device->getID(); + $edit_uri = $this->getApplicationURI("device/edit/{$id}/"); - $actions->addAction( + $curtain = $this->newCurtainView($device); + + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Device')) - ->setHref($this->getApplicationURI("device/edit/{$id}/")) + ->setHref($edit_uri) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); - return $actions; + return $curtain; } private function buildInterfaceList(AlmanacDevice $device) { diff --git a/src/applications/almanac/controller/AlmanacNamespaceViewController.php b/src/applications/almanac/controller/AlmanacNamespaceViewController.php index b999f219fc..a673bdf648 100644 --- a/src/applications/almanac/controller/AlmanacNamespaceViewController.php +++ b/src/applications/almanac/controller/AlmanacNamespaceViewController.php @@ -21,8 +21,7 @@ final class AlmanacNamespaceViewController $title = pht('Namespace %s', $namespace->getName()); - $properties = $this->buildPropertyList($namespace); - $actions = $this->buildActionList($namespace); + $curtain = $this->buildCurtain($namespace); $header = id(new PHUIHeaderView()) ->setUser($viewer) @@ -41,11 +40,10 @@ final class AlmanacNamespaceViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setCurtain($curtain) ->setMainColumn(array( $timeline, - )) - ->setPropertyList($properties) - ->setActionList($actions); + )); return $this->newPage() ->setTitle($title) @@ -56,39 +54,28 @@ final class AlmanacNamespaceViewController )); } - private function buildPropertyList(AlmanacNamespace $namespace) { + private function buildCurtain(AlmanacNamespace $namespace) { $viewer = $this->getViewer(); - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($namespace); - - $properties->invokeWillRenderEvent(); - - return $properties; - } - - private function buildActionList(AlmanacNamespace $namespace) { - $viewer = $this->getViewer(); - $id = $namespace->getID(); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $namespace, PhabricatorPolicyCapability::CAN_EDIT); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); + $id = $namespace->getID(); + $edit_uri = $this->getApplicationURI("namespace/edit/{$id}/"); - $actions->addAction( + $curtain = $this->newCurtainView($namespace); + + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Namespace')) - ->setHref($this->getApplicationURI("namespace/edit/{$id}/")) + ->setHref($edit_uri) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); - return $actions; + return $curtain; } } diff --git a/src/applications/almanac/controller/AlmanacNetworkViewController.php b/src/applications/almanac/controller/AlmanacNetworkViewController.php index 11e6eaf799..271ae1a0fb 100644 --- a/src/applications/almanac/controller/AlmanacNetworkViewController.php +++ b/src/applications/almanac/controller/AlmanacNetworkViewController.php @@ -21,8 +21,7 @@ final class AlmanacNetworkViewController $title = pht('Network %s', $network->getName()); - $properties = $this->buildPropertyList($network); - $actions = $this->buildActionList($network); + $curtain = $this->buildCurtain($network); $header = id(new PHUIHeaderView()) ->setUser($viewer) @@ -41,11 +40,10 @@ final class AlmanacNetworkViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setCurtain($curtain) ->setMainColumn(array( $timeline, - )) - ->setPropertyList($properties) - ->setActionList($actions); + )); return $this->newPage() ->setTitle($title) @@ -56,39 +54,29 @@ final class AlmanacNetworkViewController )); } - private function buildPropertyList(AlmanacNetwork $network) { + + private function buildCurtain(AlmanacNetwork $network) { $viewer = $this->getViewer(); - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($network); - - $properties->invokeWillRenderEvent(); - - return $properties; - } - - private function buildActionList(AlmanacNetwork $network) { - $viewer = $this->getViewer(); - $id = $network->getID(); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $network, PhabricatorPolicyCapability::CAN_EDIT); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); + $id = $network->getID(); + $edit_uri = $this->getApplicationURI("network/edit/{$id}/"); - $actions->addAction( + $curtain = $this->newCurtainView($network); + + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Network')) - ->setHref($this->getApplicationURI("network/edit/{$id}/")) + ->setHref($edit_uri) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); - return $actions; + return $curtain; } } diff --git a/src/applications/almanac/controller/AlmanacServiceViewController.php b/src/applications/almanac/controller/AlmanacServiceViewController.php index 0b59a47442..1036dc9e78 100644 --- a/src/applications/almanac/controller/AlmanacServiceViewController.php +++ b/src/applications/almanac/controller/AlmanacServiceViewController.php @@ -23,8 +23,7 @@ final class AlmanacServiceViewController $title = pht('Service %s', $service->getName()); - $properties = $this->buildPropertyList($service); - $actions = $this->buildActionList($service); + $curtain = $this->buildCurtain($service); $details = $this->buildPropertySection($service); $header = id(new PHUIHeaderView()) @@ -55,36 +54,19 @@ final class AlmanacServiceViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setCurtain($curtain) ->setMainColumn(array( $issue, $details, $bindings, $this->buildAlmanacPropertiesTable($service), $timeline, - )) - ->setPropertyList($properties) - ->setActionList($actions); + )); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild( - array( - $view, - )); - } - - private function buildPropertyList( - AlmanacService $service) { - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($service); - - $view->invokeWillRenderEvent(); - - return $view; + ->appendChild($view); } private function buildPropertySection( @@ -104,27 +86,28 @@ final class AlmanacServiceViewController ->appendChild($properties); } - private function buildActionList(AlmanacService $service) { + private function buildCurtain(AlmanacService $service) { $viewer = $this->getViewer(); - $id = $service->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $service, PhabricatorPolicyCapability::CAN_EDIT); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); + $id = $service->getID(); + $edit_uri = $this->getApplicationURI("service/edit/{$id}/"); - $actions->addAction( + $curtain = $this->newCurtainView($service); + + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Service')) - ->setHref($this->getApplicationURI("service/edit/{$id}/")) + ->setHref($edit_uri) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); - return $actions; + return $curtain; } private function buildBindingList(AlmanacService $service) { From eb1a0799ae8af5be2c07ba46aa0197fb64f1bee9 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 6 Mar 2016 10:16:00 -0800 Subject: [PATCH 15/57] Convert Maniphest to curtain view Summary: Moves Maniphest over, and allows application to provide ad-hoc panels more easily. Test Plan: {F1160591} Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D15417 --- .../ManiphestTaskDetailController.php | 80 ++++++++----------- src/view/layout/PHUICurtainView.php | 11 +++ 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index cbb55413ad..d1d93b6c1b 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -73,8 +73,7 @@ final class ManiphestTaskDetailController extends ManiphestController { $header = $this->buildHeaderView($task); $details = $this->buildPropertyView($task, $field_list, $edges, $handles); $description = $this->buildDescriptionView($task, $engine); - $actions = $this->buildActionView($task); - $properties = $this->buildPropertyListView($task, $handles); + $curtain = $this->buildCurtain($task); $title = pht('%s %s', $monogram, $task->getTitle()); @@ -87,14 +86,13 @@ final class ManiphestTaskDetailController extends ManiphestController { $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setCurtain($curtain) ->setMainColumn(array( $timeline, $comment_view, )) ->addPropertySection(pht('DETAILS'), $details) - ->addPropertySection(pht('DESCRIPTION'), $description) - ->setPropertyList($properties) - ->setActionList($actions); + ->addPropertySection(pht('DESCRIPTION'), $description); return $this->newPage() ->setTitle($title) @@ -148,8 +146,8 @@ final class ManiphestTaskDetailController extends ManiphestController { } - private function buildActionView(ManiphestTask $task) { - $viewer = $this->getRequest()->getUser(); + private function buildCurtain(ManiphestTask $task) { + $viewer = $this->getViewer(); $id = $task->getID(); $phid = $task->getPHID(); @@ -159,11 +157,9 @@ final class ManiphestTaskDetailController extends ManiphestController { $task, PhabricatorPolicyCapability::CAN_EDIT); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($task); + $curtain = $this->newCurtainView($task); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Task')) ->setIcon('fa-pencil') @@ -171,7 +167,7 @@ final class ManiphestTaskDetailController extends ManiphestController { ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Merge Duplicates In')) ->setHref("/search/attach/{$phid}/TASK/merge/") @@ -199,7 +195,7 @@ final class ManiphestTaskDetailController extends ManiphestController { $edit_uri = $this->getApplicationURI($edit_uri); } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Create Subtask')) ->setHref($edit_uri) @@ -207,7 +203,7 @@ final class ManiphestTaskDetailController extends ManiphestController { ->setDisabled(!$can_create) ->setWorkflow(!$can_create)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Blocking Tasks')) ->setHref("/search/attach/{$phid}/TASK/blocks/") @@ -216,7 +212,30 @@ final class ManiphestTaskDetailController extends ManiphestController { ->setDisabled(!$can_edit) ->setWorkflow(true)); - return $view; + + $owner_phid = $task->getOwnerPHID(); + if ($owner_phid) { + $assigned_to = $viewer + ->renderHandle($owner_phid) + ->setShowHovercard(true); + } else { + $assigned_to = phutil_tag('em', array(), pht('None')); + } + + $curtain->newPanel() + ->setHeaderText(pht('Assigned To')) + ->appendChild($assigned_to); + + $author_phid = $task->getAuthorPHID(); + $author = $viewer + ->renderHandle($author_phid) + ->setShowHovercard(true); + + $curtain->newPanel() + ->setHeaderText(pht('Author')) + ->appendChild($author); + + return $curtain; } private function buildPropertyView( @@ -307,37 +326,6 @@ final class ManiphestTaskDetailController extends ManiphestController { return null; } - private function buildPropertyListView(ManiphestTask $task, $handles) { - $viewer = $this->getRequest()->getUser(); - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($task); - - $view->invokeWillRenderEvent(); - - $owner_phid = $task->getOwnerPHID(); - if ($owner_phid) { - $assigned_to = $handles - ->renderHandle($owner_phid) - ->setShowHovercard(true); - } else { - $assigned_to = phutil_tag('em', array(), pht('None')); - } - - $view->addProperty(pht('Assigned To'), $assigned_to); - - $author_phid = $task->getAuthorPHID(); - $author = $handles - ->renderHandle($author_phid) - ->setShowHovercard(true); - - $date = phabricator_datetime($task->getDateCreated(), $viewer); - - $view->addProperty(pht('Author'), $author); - - return $view; - } - private function buildDescriptionView( ManiphestTask $task, PhabricatorMarkupEngine $engine) { diff --git a/src/view/layout/PHUICurtainView.php b/src/view/layout/PHUICurtainView.php index 6d2388802b..af02ceb932 100644 --- a/src/view/layout/PHUICurtainView.php +++ b/src/view/layout/PHUICurtainView.php @@ -15,6 +15,17 @@ final class PHUICurtainView extends AphrontTagView { return $this; } + public function newPanel() { + $panel = new PHUICurtainPanelView(); + $this->addPanel($panel); + + // By default, application panels go at the bottom of the curtain, below + // extension panels. + $panel->setOrder(100000); + + return $panel; + } + public function setActionList(PhabricatorActionListView $action_list) { $this->actionList = $action_list; return $this; From fd9de5d6ecc84420d70ef40542f1c645dc066653 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 6 Mar 2016 10:08:49 -0800 Subject: [PATCH 16/57] Convert every two-column application except Maniphest to curtain views Summary: Moves over everything except Maniphest, which has some special behavior. Test Plan: - Viewed a badge. - Viewed a calendar event. - Viewed a countdown. - Viewed a Fund initiative. - Viewed a Herald rule. - Viewed a macro. - Viewed an application. - Viewed an owners package. - Viewed a credential. - Viewed a Ponder question. - Viewed a poll. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D15416 --- .../PhabricatorBadgesViewController.php | 54 ++++------- .../base/controller/PhabricatorController.php | 10 ++- ...PhabricatorCalendarEventViewController.php | 41 +++------ .../PhabricatorCountdownViewController.php | 40 +++------ .../FundInitiativeViewController.php | 44 +++------ .../controller/HeraldRuleViewController.php | 49 +++------- .../PhabricatorMacroViewController.php | 44 +++------ ...ricatorApplicationDetailViewController.php | 50 +++++------ .../PhabricatorOwnersDetailController.php | 90 ++++++++----------- .../PassphraseCredentialViewController.php | 51 ++++------- .../PonderQuestionViewController.php | 43 +++------ .../PhabricatorSlowvotePollController.php | 41 +++------ 12 files changed, 184 insertions(+), 373 deletions(-) diff --git a/src/applications/badges/controller/PhabricatorBadgesViewController.php b/src/applications/badges/controller/PhabricatorBadgesViewController.php index 3858965d41..fac82ccc8d 100644 --- a/src/applications/badges/controller/PhabricatorBadgesViewController.php +++ b/src/applications/badges/controller/PhabricatorBadgesViewController.php @@ -43,8 +43,7 @@ final class PhabricatorBadgesViewController ->setStatus($status_icon, $status_color, $status_name) ->setHeaderIcon('fa-trophy'); - $properties = $this->buildPropertyListView($badge); - $actions = $this->buildActionListView($badge); + $curtain = $this->buildCurtain($badge); $details = $this->buildDetailsView($badge); $timeline = $this->buildTransactionTimeline( @@ -64,36 +63,19 @@ final class PhabricatorBadgesViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setCurtain($curtain) ->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( - $view, - )); - } - - private function buildPropertyListView( - PhabricatorBadgesBadge $badge) { - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($badge); - - $view->invokeWillRenderEvent(); - - return $view; + ->appendChild($view); } private function buildDetailsView( @@ -137,53 +119,55 @@ final class PhabricatorBadgesViewController return $view; } - private function buildActionListView(PhabricatorBadgesBadge $badge) { + private function buildCurtain(PhabricatorBadgesBadge $badge) { $viewer = $this->getViewer(); - $id = $badge->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $badge, PhabricatorPolicyCapability::CAN_EDIT); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($badge); + $id = $badge->getID(); + $edit_uri = $this->getApplicationURI("/edit/{$id}/"); + $archive_uri = $this->getApplicationURI("/archive/{$id}/"); + $award_uri = $this->getApplicationURI("/recipients/{$id}/"); - $view->addAction( + $curtain = $this->newCurtainView($badge); + + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Badge')) ->setIcon('fa-pencil') ->setDisabled(!$can_edit) - ->setHref($this->getApplicationURI("/edit/{$id}/"))); + ->setHref($edit_uri)); if ($badge->isArchived()) { - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Activate Badge')) ->setIcon('fa-check') ->setDisabled(!$can_edit) ->setWorkflow($can_edit) - ->setHref($this->getApplicationURI("/archive/{$id}/"))); + ->setHref($archive_uri)); } else { - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Archive Badge')) ->setIcon('fa-ban') ->setDisabled(!$can_edit) ->setWorkflow($can_edit) - ->setHref($this->getApplicationURI("/archive/{$id}/"))); + ->setHref($archive_uri)); } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName('Add Recipients') ->setIcon('fa-users') ->setDisabled(!$can_edit) ->setWorkflow(true) - ->setHref($this->getApplicationURI("/recipients/{$id}/"))); + ->setHref($award_uri)); - return $view; + return $curtain; } private function buildCommentForm(PhabricatorBadgesBadge $badge) { diff --git a/src/applications/base/controller/PhabricatorController.php b/src/applications/base/controller/PhabricatorController.php index fffe299b3f..5d8ff7ba60 100644 --- a/src/applications/base/controller/PhabricatorController.php +++ b/src/applications/base/controller/PhabricatorController.php @@ -475,8 +475,14 @@ abstract class PhabricatorController extends AphrontController { $viewer = $this->getViewer(); $action_list = id(new PhabricatorActionListView()) - ->setViewer($viewer) - ->setObject($object); + ->setViewer($viewer); + + // NOTE: Applications (objects of class PhabricatorApplication) can't + // currently be set here, although they don't need any of the extensions + // anyway. This should probably work differently than it does, though. + if ($object instanceof PhabricatorLiskDAO) { + $action_list->setObject($object); + } $curtain = id(new PHUICurtainView()) ->setViewer($viewer) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index 8d13f85e61..eb435a42e9 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -63,8 +63,7 @@ final class PhabricatorCalendarEventViewController } $header = $this->buildHeaderView($event); - $actions = $this->buildActionView($event); - $properties = $this->buildPropertyListView($event); + $curtain = $this->buildCurtain($event); $details = $this->buildPropertySection($event); $description = $this->buildDescriptionView($event); @@ -91,10 +90,9 @@ final class PhabricatorCalendarEventViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setMainColumn($timeline) - ->setPropertyList($properties) + ->setCurtain($curtain) ->addPropertySection(pht('DETAILS'), $details) - ->addPropertySection(pht('DESCRIPTION'), $description) - ->setActionList($actions); + ->addPropertySection(pht('DESCRIPTION'), $description); return $this->newPage() ->setTitle($page_title) @@ -148,16 +146,12 @@ final class PhabricatorCalendarEventViewController return $header; } - private function buildActionView(PhabricatorCalendarEvent $event) { + private function buildCurtain(PhabricatorCalendarEvent $event) { $viewer = $this->getRequest()->getUser(); $id = $event->getID(); $is_cancelled = $event->getIsCancelled(); $is_attending = $event->getIsUserAttending($viewer->getPHID()); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($event); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $event, @@ -178,8 +172,10 @@ final class PhabricatorCalendarEventViewController $edit_uri = "event/edit/{$id}/"; } + $curtain = $this->newCurtainView($event); + if ($edit_label && $edit_uri) { - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($edit_label) ->setIcon('fa-pencil') @@ -189,14 +185,14 @@ final class PhabricatorCalendarEventViewController } if ($is_attending) { - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Decline Event')) ->setIcon('fa-user-times') ->setHref($this->getApplicationURI("event/join/{$id}/")) ->setWorkflow(true)); } else { - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Join Event')) ->setIcon('fa-user-plus') @@ -230,7 +226,7 @@ final class PhabricatorCalendarEventViewController } if ($is_cancelled) { - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($reinstate_label) ->setIcon('fa-plus') @@ -238,7 +234,7 @@ final class PhabricatorCalendarEventViewController ->setDisabled($cancel_disabled) ->setWorkflow(true)); } else { - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($cancel_label) ->setIcon('fa-times') @@ -247,20 +243,7 @@ final class PhabricatorCalendarEventViewController ->setWorkflow(true)); } - return $actions; - } - - private function buildPropertyListView( - PhabricatorCalendarEvent $event) { - $viewer = $this->getViewer(); - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($event); - - $properties->invokeWillRenderEvent(); - - return $properties; + return $curtain; } private function buildPropertySection( diff --git a/src/applications/countdown/controller/PhabricatorCountdownViewController.php b/src/applications/countdown/controller/PhabricatorCountdownViewController.php index 9a983f0ed9..6e259df555 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownViewController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownViewController.php @@ -49,8 +49,7 @@ final class PhabricatorCountdownViewController ->setStatus($icon, $color, $status) ->setHeaderIcon('fa-rocket'); - $actions = $this->buildActionListView($countdown); - $properties = $this->buildPropertyListView($countdown); + $curtain = $this->buildCurtain($countdown); $subheader = $this->buildSubheaderView($countdown); $timeline = $this->buildTransactionTimeline( @@ -67,9 +66,8 @@ final class PhabricatorCountdownViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setSubheader($subheader) - ->setMainColumn($content) - ->setPropertyList($properties) - ->setActionList($actions); + ->setCurtain($curtain) + ->setMainColumn($content); return $this->newPage() ->setTitle($title) @@ -78,28 +76,22 @@ final class PhabricatorCountdownViewController array( $countdown->getPHID(), )) - ->appendChild( - array( - $view, - )); + ->appendChild($view); } - private function buildActionListView(PhabricatorCountdown $countdown) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + private function buildCurtain(PhabricatorCountdown $countdown) { + $viewer = $this->getViewer(); $id = $countdown->getID(); - $view = id(new PhabricatorActionListView()) - ->setObject($countdown) - ->setUser($viewer); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $countdown, PhabricatorPolicyCapability::CAN_EDIT); - $view->addAction( + $curtain = $this->newCurtainView($countdown); + + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Countdown')) @@ -107,7 +99,7 @@ final class PhabricatorCountdownViewController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-times') ->setName(pht('Delete Countdown')) @@ -115,17 +107,7 @@ final class PhabricatorCountdownViewController ->setDisabled(!$can_edit) ->setWorkflow(true)); - return $view; - } - - private function buildPropertyListView( - PhabricatorCountdown $countdown) { - $viewer = $this->getViewer(); - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($countdown); - $view->invokeWillRenderEvent(); - return $view; + return $curtain; } private function buildSubheaderView( diff --git a/src/applications/fund/controller/FundInitiativeViewController.php b/src/applications/fund/controller/FundInitiativeViewController.php index f4535d574e..a416054ae3 100644 --- a/src/applications/fund/controller/FundInitiativeViewController.php +++ b/src/applications/fund/controller/FundInitiativeViewController.php @@ -46,8 +46,7 @@ final class FundInitiativeViewController ->setStatus($status_icon, $status_color, $status_name) ->setHeaderIcon('fa-heart'); - $properties = $this->buildPropertyListView($initiative); - $actions = $this->buildActionListView($initiative); + $curtain = $this->buildCurtain($initiative); $details = $this->buildPropertySectionView($initiative); $timeline = $this->buildTransactionTimeline( @@ -57,31 +56,15 @@ final class FundInitiativeViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setCurtain($curtain) ->setMainColumn($timeline) - ->setPropertyList($properties) - ->addPropertySection(pht('DETAILS'), $details) - ->setActionList($actions); + ->addPropertySection(pht('DETAILS'), $details); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->setPageObjectPHIDs(array($initiative->getPHID())) - ->appendChild( - array( - $view, - )); - } - - private function buildPropertyListView(FundInitiative $initiative) { - $viewer = $this->getRequest()->getUser(); - - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($initiative); - - $view->invokeWillRenderEvent(); - - return $view; + ->appendChild($view); } private function buildPropertySectionView(FundInitiative $initiative) { @@ -124,8 +107,9 @@ final class FundInitiativeViewController return $view; } - private function buildActionListView(FundInitiative $initiative) { - $viewer = $this->getRequest()->getUser(); + private function buildCurtain(FundInitiative $initiative) { + $viewer = $this->getViewer(); + $id = $initiative->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -133,11 +117,9 @@ final class FundInitiativeViewController $initiative, PhabricatorPolicyCapability::CAN_EDIT); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($initiative); + $curtain = $this->newCurtainView($initiative); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Initiative')) ->setIcon('fa-pencil') @@ -153,7 +135,7 @@ final class FundInitiativeViewController $close_icon = 'fa-times'; } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($close_name) ->setIcon($close_icon) @@ -161,7 +143,7 @@ final class FundInitiativeViewController ->setWorkflow(true) ->setHref($this->getApplicationURI("/close/{$id}/"))); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Back Initiative')) ->setIcon('fa-money') @@ -169,13 +151,13 @@ final class FundInitiativeViewController ->setWorkflow(true) ->setHref($this->getApplicationURI("/back/{$id}/"))); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('View Backers')) ->setIcon('fa-bank') ->setHref($this->getApplicationURI("/backers/{$id}/"))); - return $view; + return $curtain; } } diff --git a/src/applications/herald/controller/HeraldRuleViewController.php b/src/applications/herald/controller/HeraldRuleViewController.php index 4036be250b..f5e058d3f3 100644 --- a/src/applications/herald/controller/HeraldRuleViewController.php +++ b/src/applications/herald/controller/HeraldRuleViewController.php @@ -33,8 +33,7 @@ final class HeraldRuleViewController extends HeraldController { pht('Active')); } - $actions = $this->buildActionView($rule); - $properties = $this->buildPropertyView($rule); + $curtain = $this->buildCurtain($rule); $details = $this->buildPropertySectionView($rule); $description = $this->buildDescriptionView($rule); @@ -44,10 +43,6 @@ final class HeraldRuleViewController extends HeraldController { $crumbs->addTextCrumb("H{$id}"); $crumbs->setBorder(true); - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); - $timeline = $this->buildTransactionTimeline( $rule, new HeraldTransactionQuery()); @@ -57,35 +52,30 @@ final class HeraldRuleViewController extends HeraldController { $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setCurtain($curtain) ->setMainColumn($timeline) ->addPropertySection(pht('DETAILS'), $details) - ->addPropertySection(pht('DESCRIPTION'), $description) - ->setPropertyList($properties) - ->setActionList($actions); + ->addPropertySection(pht('DESCRIPTION'), $description); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild( - array( - $view, - )); + ->appendChild($view); } - private function buildActionView(HeraldRule $rule) { - $viewer = $this->getRequest()->getUser(); - $id = $rule->getID(); + private function buildCurtain(HeraldRule $rule) { + $viewer = $this->getViewer(); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($rule); + $id = $rule->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $rule, PhabricatorPolicyCapability::CAN_EDIT); - $view->addAction( + $curtain = $this->newCurtainView($rule); + + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Rule')) ->setHref($this->getApplicationURI("edit/{$id}/")) @@ -103,7 +93,7 @@ final class HeraldRuleViewController extends HeraldController { $disable_name = pht('Archive Rule'); } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Disable Rule')) ->setHref($this->getApplicationURI($disable_uri)) @@ -112,23 +102,10 @@ final class HeraldRuleViewController extends HeraldController { ->setDisabled(!$can_edit) ->setWorkflow(true)); - return $view; + return $curtain; } - private function buildPropertyView( - HeraldRule $rule) { - - $viewer = $this->getRequest()->getUser(); - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($rule); - - $view->invokeWillRenderEvent(); - - return $view; - } - - private function buildPropertySectionView( + private function buildPropertySectionView( HeraldRule $rule) { $viewer = $this->getRequest()->getUser(); diff --git a/src/applications/macro/controller/PhabricatorMacroViewController.php b/src/applications/macro/controller/PhabricatorMacroViewController.php index 9ee70eecdc..8c12e5528a 100644 --- a/src/applications/macro/controller/PhabricatorMacroViewController.php +++ b/src/applications/macro/controller/PhabricatorMacroViewController.php @@ -23,9 +23,8 @@ final class PhabricatorMacroViewController $title_short = pht('Macro "%s"', $macro->getName()); $title_long = pht('Image Macro "%s"', $macro->getName()); - $actions = $this->buildActionView($macro); + $curtain = $this->buildCurtain($macro); $subheader = $this->buildSubheaderView($macro); - $properties = $this->buildPropertyView($macro); $file = $this->buildFileView($macro); $details = $this->buildPropertySectionView($macro); @@ -68,35 +67,29 @@ final class PhabricatorMacroViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setSubheader($subheader) + ->setCurtain($curtain) ->setMainColumn(array( $timeline, $add_comment_form, )) ->addPropertySection(pht('MACRO'), $file) - ->addPropertySection(pht('DETAILS'), $details) - ->setPropertyList($properties) - ->setActionList($actions); + ->addPropertySection(pht('DETAILS'), $details); return $this->newPage() ->setTitle($title_short) ->setCrumbs($crumbs) ->setPageObjectPHIDs(array($macro->getPHID())) - ->appendChild( - array( - $view, - )); + ->appendChild($view); } - private function buildActionView( + private function buildCurtain( PhabricatorFileImageMacro $macro) { $can_manage = $this->hasApplicationCapability( PhabricatorMacroManageCapability::CAPABILITY); - $request = $this->getRequest(); - $view = id(new PhabricatorActionListView()) - ->setUser($request->getUser()) - ->setObject($macro) - ->addAction( + $curtain = $this->newCurtainView($macro); + + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Macro')) ->setHref($this->getApplicationURI('/edit/'.$macro->getID().'/')) @@ -104,7 +97,7 @@ final class PhabricatorMacroViewController ->setWorkflow(!$can_manage) ->setIcon('fa-pencil')); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Audio')) ->setHref($this->getApplicationURI('/audio/'.$macro->getID().'/')) @@ -113,7 +106,7 @@ final class PhabricatorMacroViewController ->setIcon('fa-music')); if ($macro->getIsDisabled()) { - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Activate Macro')) ->setHref($this->getApplicationURI('/disable/'.$macro->getID().'/')) @@ -121,7 +114,7 @@ final class PhabricatorMacroViewController ->setDisabled(!$can_manage) ->setIcon('fa-check')); } else { - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Archive Macro')) ->setHref($this->getApplicationURI('/disable/'.$macro->getID().'/')) @@ -130,7 +123,7 @@ final class PhabricatorMacroViewController ->setIcon('fa-ban')); } - return $view; + return $curtain; } private function buildSubheaderView( @@ -206,17 +199,4 @@ final class PhabricatorMacroViewController return null; } - private function buildPropertyView( - PhabricatorFileImageMacro $macro) { - $viewer = $this->getViewer(); - - $view = id(new PHUIPropertyListView()) - ->setUser($this->getRequest()->getUser()) - ->setObject($macro); - - $view->invokeWillRenderEvent(); - - return $view; - } - } diff --git a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php index 07dea613db..443a7e37d3 100644 --- a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php +++ b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php @@ -38,7 +38,7 @@ final class PhabricatorApplicationDetailViewController $header->setStatus('fa-ban', 'dark', pht('Uninstalled')); } - $actions = $this->buildActionView($viewer, $selected); + $curtain = $this->buildCurtain($selected); $details = $this->buildPropertySectionView($selected); $policies = $this->buildPolicyView($selected); @@ -58,12 +58,12 @@ final class PhabricatorApplicationDetailViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setCurtain($curtain) ->setMainColumn(array( $policies, $panels, )) - ->addPropertySection(pht('DETAILS'), $details) - ->setActionList($actions); + ->addPropertySection(pht('DETAILS'), $details); return $this->newPage() ->setTitle($title) @@ -147,21 +147,22 @@ final class PhabricatorApplicationDetailViewController } - private function buildActionView( - PhabricatorUser $user, - PhabricatorApplication $selected) { - - $view = id(new PhabricatorActionListView()) - ->setUser($user); + private function buildCurtain(PhabricatorApplication $application) { + $viewer = $this->getViewer(); $can_edit = PhabricatorPolicyFilter::hasCapability( - $user, - $selected, + $viewer, + $application, PhabricatorPolicyCapability::CAN_EDIT); - $edit_uri = $this->getApplicationURI('edit/'.get_class($selected).'/'); + $key = get_class($application); + $edit_uri = $this->getApplicationURI("edit/{$key}/"); + $install_uri = $this->getApplicationURI("{$key}/install/"); + $uninstall_uri = $this->getApplicationURI("{$key}/uninstall/"); - $view->addAction( + $curtain = $this->newCurtainView($application); + + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Policies')) ->setIcon('fa-pencil') @@ -169,45 +170,42 @@ final class PhabricatorApplicationDetailViewController ->setWorkflow(!$can_edit) ->setHref($edit_uri)); - if ($selected->canUninstall()) { - if ($selected->isInstalled()) { - $view->addAction( + if ($application->canUninstall()) { + if ($application->isInstalled()) { + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Uninstall')) ->setIcon('fa-times') ->setDisabled(!$can_edit) ->setWorkflow(true) - ->setHref( - $this->getApplicationURI(get_class($selected).'/uninstall/'))); + ->setHref($uninstall_uri)); } else { $action = id(new PhabricatorActionView()) ->setName(pht('Install')) ->setIcon('fa-plus') ->setDisabled(!$can_edit) ->setWorkflow(true) - ->setHref( - $this->getApplicationURI(get_class($selected).'/install/')); + ->setHref($install_uri); $prototypes_enabled = PhabricatorEnv::getEnvConfig( 'phabricator.show-prototypes'); - if ($selected->isPrototype() && !$prototypes_enabled) { + if ($application->isPrototype() && !$prototypes_enabled) { $action->setDisabled(true); } - $view->addAction($action); + $curtain->addAction($action); } } else { - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Uninstall')) ->setIcon('fa-times') ->setWorkflow(true) ->setDisabled(true) - ->setHref( - $this->getApplicationURI(get_class($selected).'/uninstall/'))); + ->setHref($uninstall_uri)); } - return $view; + return $curtain; } } diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index 9b2da6c3c1..5da36ad473 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -43,8 +43,7 @@ final class PhabricatorOwnersDetailController ->setViewer($viewer) ->readFieldsFromStorage($package); - $actions = $this->buildPackageActionView($package); - $properties = $this->buildPackagePropertyView($package, $field_list); + $curtain = $this->buildCurtain($package); $details = $this->buildPackageDetailView($package, $field_list); if ($package->isArchived()) { @@ -155,35 +154,18 @@ final class PhabricatorOwnersDetailController $view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setCurtain($curtain) ->setMainColumn(array( $this->renderPathsTable($paths, $repositories), $commit_panels, $timeline, )) - ->addPropertySection(pht('Details'), $details) - ->setPropertyList($properties) - ->setActionList($actions); + ->addPropertySection(pht('Details'), $details); return $this->newPage() ->setTitle($package->getName()) ->setCrumbs($crumbs) - ->appendChild( - array( - $view, - )); - } - - private function buildPackagePropertyView( - PhabricatorOwnersPackage $package, - PhabricatorCustomFieldList $field_list) { - - $viewer = $this->getViewer(); - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($package); - $view->invokeWillRenderEvent(); - - return $view; + ->appendChild($view); } private function buildPackageDetailView( @@ -224,7 +206,7 @@ final class PhabricatorOwnersDetailController return $view; } - private function buildPackageActionView(PhabricatorOwnersPackage $package) { + private function buildCurtain(PhabricatorOwnersPackage $package) { $viewer = $this->getViewer(); $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -236,45 +218,43 @@ final class PhabricatorOwnersDetailController $edit_uri = $this->getApplicationURI("/edit/{$id}/"); $paths_uri = $this->getApplicationURI("/paths/{$id}/"); - $action_list = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($package); + $curtain = $this->newCurtainView($package); - $action_list->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Package')) - ->setIcon('fa-pencil') - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit) - ->setHref($edit_uri)); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Package')) + ->setIcon('fa-pencil') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setHref($edit_uri)); if ($package->isArchived()) { - $action_list->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Activate Package')) - ->setIcon('fa-check') - ->setDisabled(!$can_edit) - ->setWorkflow($can_edit) - ->setHref($this->getApplicationURI("/archive/{$id}/"))); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Activate Package')) + ->setIcon('fa-check') + ->setDisabled(!$can_edit) + ->setWorkflow($can_edit) + ->setHref($this->getApplicationURI("/archive/{$id}/"))); } else { - $action_list->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Archive Package')) - ->setIcon('fa-ban') - ->setDisabled(!$can_edit) - ->setWorkflow($can_edit) - ->setHref($this->getApplicationURI("/archive/{$id}/"))); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Archive Package')) + ->setIcon('fa-ban') + ->setDisabled(!$can_edit) + ->setWorkflow($can_edit) + ->setHref($this->getApplicationURI("/archive/{$id}/"))); } - $action_list->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Paths')) - ->setIcon('fa-folder-open') - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit) - ->setHref($paths_uri)); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Paths')) + ->setIcon('fa-folder-open') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setHref($paths_uri)); - return $action_list; + return $curtain; } private function renderPathsTable(array $paths, array $repositories) { diff --git a/src/applications/passphrase/controller/PassphraseCredentialViewController.php b/src/applications/passphrase/controller/PassphraseCredentialViewController.php index fa52681d82..46bb13a688 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialViewController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialViewController.php @@ -31,26 +31,21 @@ final class PassphraseCredentialViewController extends PassphraseController { $crumbs->setBorder(true); $header = $this->buildHeaderView($credential); - $actions = $this->buildActionView($credential, $type); - $properties = $this->buildPropertyView($credential, $type); + $curtain = $this->buildCurtain($credential, $type); $subheader = $this->buildSubheaderView($credential); $content = $this->buildPropertySectionView($credential, $type); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setSubheader($subheader) + ->setCurtain($curtain) ->setMainColumn($timeline) - ->addPropertySection(pht('PROPERTIES'), $content) - ->setPropertyList($properties) - ->setActionList($actions); + ->addPropertySection(pht('PROPERTIES'), $content); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) - ->appendChild( - array( - $view, - )); + ->appendChild($view); } private function buildHeaderView(PassphraseCredential $credential) { @@ -98,10 +93,10 @@ final class PassphraseCredentialViewController extends PassphraseController { ->setContent($content); } - private function buildActionView( + private function buildCurtain( PassphraseCredential $credential, PassphraseCredentialType $type) { - $viewer = $this->getRequest()->getUser(); + $viewer = $this->getViewer(); $id = $credential->getID(); @@ -123,16 +118,14 @@ final class PassphraseCredentialViewController extends PassphraseController { $credential_conduit_icon = 'fa-wrench'; } - $actions = id(new PhabricatorActionListView()) - ->setObject($credential) - ->setUser($viewer); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $credential, PhabricatorPolicyCapability::CAN_EDIT); - $actions->addAction( + $curtain = $this->newCurtainView($credential); + + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Credential')) ->setIcon('fa-pencil') @@ -141,7 +134,7 @@ final class PassphraseCredentialViewController extends PassphraseController { ->setWorkflow(!$can_edit)); if (!$credential->getIsDestroyed()) { - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Destroy Credential')) ->setIcon('fa-times') @@ -149,7 +142,7 @@ final class PassphraseCredentialViewController extends PassphraseController { ->setDisabled(!$can_edit) ->setWorkflow(true)); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Show Secret')) ->setIcon('fa-eye') @@ -158,7 +151,7 @@ final class PassphraseCredentialViewController extends PassphraseController { ->setWorkflow(true)); if ($type->hasPublicKey()) { - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Show Public Key')) ->setIcon('fa-download') @@ -167,7 +160,7 @@ final class PassphraseCredentialViewController extends PassphraseController { ->setWorkflow(true)); } - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($credential_conduit_text) ->setIcon($credential_conduit_icon) @@ -175,7 +168,7 @@ final class PassphraseCredentialViewController extends PassphraseController { ->setDisabled(!$can_edit) ->setWorkflow(true)); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($credential_lock_text) ->setIcon($credential_lock_icon) @@ -184,8 +177,7 @@ final class PassphraseCredentialViewController extends PassphraseController { ->setWorkflow(true)); } - - return $actions; + return $curtain; } private function buildPropertySectionView( @@ -236,17 +228,4 @@ final class PassphraseCredentialViewController extends PassphraseController { return $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/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index 41819a34f0..a1105c6c8e 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -43,8 +43,7 @@ final class PonderQuestionViewController extends PonderController { $header->setStatus($icon, 'dark', $text); } - $properties = $this->buildPropertyListView($question); - $actions = $this->buildActionListView($question); + $curtain = $this->buildCurtain($question); $details = $this->buildPropertySectionView($question); $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -118,29 +117,24 @@ final class PonderQuestionViewController extends PonderController { $ponder_view = id(new PHUITwoColumnView()) ->setHeader($header) ->setSubheader($subheader) + ->setCurtain($curtain) ->setMainColumn($ponder_content) - ->setPropertyList($properties) ->addPropertySection(pht('DETAILS'), $details) - ->setActionList($actions) ->addClass('ponder-question-view'); $page_objects = array_merge( - array($question->getPHID()), - mpull($question->getAnswers(), 'getPHID')); + array($question->getPHID()), + mpull($question->getAnswers(), 'getPHID')); return $this->newPage() ->setTitle('Q'.$question->getID().' '.$question->getTitle()) ->setCrumbs($crumbs) ->setPageObjectPHIDs($page_objects) - ->appendChild( - array( - $ponder_view, - )); + ->appendChild($ponder_view); } - private function buildActionListView(PonderQuestion $question) { + private function buildCurtain(PonderQuestion $question) { $viewer = $this->getViewer(); - $request = $this->getRequest(); $id = $question->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -148,9 +142,7 @@ final class PonderQuestionViewController extends PonderController { $question, PhabricatorPolicyCapability::CAN_EDIT); - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($question); + $curtain = $this->newCurtainView($question); if ($question->getStatus() == PonderQuestionStatus::STATUS_OPEN) { $name = pht('Close Question'); @@ -160,7 +152,7 @@ final class PonderQuestionViewController extends PonderController { $icon = 'fa-square-o'; } - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Question')) @@ -168,7 +160,7 @@ final class PonderQuestionViewController extends PonderController { ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($name) ->setIcon($icon) @@ -176,26 +168,13 @@ final class PonderQuestionViewController extends PonderController { ->setDisabled(!$can_edit) ->setHref($this->getApplicationURI("/question/status/{$id}/"))); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-list') ->setName(pht('View History')) ->setHref($this->getApplicationURI("/question/history/{$id}/"))); - return $view; - } - - private function buildPropertyListView( - PonderQuestion $question) { - - $viewer = $this->getViewer(); - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($question); - - $view->invokeWillRenderEvent(); - - return $view; + return $curtain; } private function buildSubheaderView( diff --git a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php index 2eb2c08c75..f788f5e94c 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php @@ -46,8 +46,7 @@ final class PhabricatorSlowvotePollController ->setPolicyObject($poll) ->setHeaderIcon('fa-bar-chart'); - $actions = $this->buildActionView($poll); - $properties = $this->buildPropertyView($poll); + $curtain = $this->buildCurtain($poll); $subheader = $this->buildSubheaderView($poll); $crumbs = $this->buildApplicationCrumbs(); @@ -68,37 +67,31 @@ final class PhabricatorSlowvotePollController $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setSubheader($subheader) - ->setMainColumn($poll_content) - ->setPropertyList($properties) - ->setActionList($actions); + ->setCurtain($curtain) + ->setMainColumn($poll_content); return $this->newPage() ->setTitle('V'.$poll->getID().' '.$poll->getQuestion()) ->setCrumbs($crumbs) ->setPageObjectPHIDs(array($poll->getPHID())) - ->appendChild( - array( - $view, - )); + ->appendChild($view); } - private function buildActionView(PhabricatorSlowvotePoll $poll) { - $viewer = $this->getRequest()->getUser(); - - $view = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($poll); + private function buildCurtain(PhabricatorSlowvotePoll $poll) { + $viewer = $this->getViewer(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $poll, PhabricatorPolicyCapability::CAN_EDIT); + $curtain = $this->newCurtainView($poll); + $is_closed = $poll->getIsClosed(); $close_poll_text = $is_closed ? pht('Reopen Poll') : pht('Close Poll'); $close_poll_icon = $is_closed ? 'fa-play-circle-o' : 'fa-ban'; - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Poll')) ->setIcon('fa-pencil') @@ -106,7 +99,7 @@ final class PhabricatorSlowvotePollController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName($close_poll_text) ->setIcon($close_poll_icon) @@ -114,19 +107,7 @@ final class PhabricatorSlowvotePollController ->setDisabled(!$can_edit) ->setWorkflow(true)); - return $view; - } - - private function buildPropertyView( - PhabricatorSlowvotePoll $poll) { - - $viewer = $this->getRequest()->getUser(); - $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($poll); - $view->invokeWillRenderEvent(); - - return $view; + return $curtain; } private function buildSubheaderView( From 8175f524e6857c21f60b43bd88533d8fc817c14e Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 6 Mar 2016 13:04:19 -0800 Subject: [PATCH 17/57] Clean up Herald CSS Summary: I didn't test these very well and they looked silly when multiple rules applied. Test Plan: Test various cases more, with single and multiple triggers. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15419 --- resources/celerity/map.php | 4 ++-- webroot/rsrc/css/application/herald/herald.css | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index dce8137749..a3fc0ae771 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' => '46596280', + 'rsrc/css/application/herald/herald.css' => 'dc31f6e9', 'rsrc/css/application/maniphest/batch-editor.css' => 'b0f0b6d5', 'rsrc/css/application/maniphest/report.css' => '9b9580b7', 'rsrc/css/application/maniphest/task-edit.css' => 'fda62a9b', @@ -561,7 +561,7 @@ return array( 'font-lato' => 'c7ccd872', 'global-drag-and-drop-css' => '5c1b47c2', 'harbormaster-css' => '834879db', - 'herald-css' => '46596280', + 'herald-css' => 'dc31f6e9', 'herald-rule-editor' => '746ca158', 'herald-test-css' => 'a52e323e', 'inline-comment-summary-css' => '51efda3a', diff --git a/webroot/rsrc/css/application/herald/herald.css b/webroot/rsrc/css/application/herald/herald.css index 26cb9cfd71..17b09a1a86 100644 --- a/webroot/rsrc/css/application/herald/herald.css +++ b/webroot/rsrc/css/application/herald/herald.css @@ -44,7 +44,7 @@ .herald-list-description { color: {$bluetext}; font-weight: bold; - padding: 8px 0; + padding: 12px 0; } .herald-list-icon { @@ -52,6 +52,6 @@ } .herald-list-item { - padding-bottom: 20px; + padding-bottom: 4px; color: {$darkbluetext}; } From 85b85529ad1c0d07c132dbe3fa14afed74929049 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 6 Mar 2016 13:28:02 -0800 Subject: [PATCH 18/57] Minor curtain spacing update Summary: Removes unused CSS, cleans up curtain spacing. Test Plan: Test maniphest, etc, in mobile, tablet, desktop Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15418 --- resources/celerity/map.php | 12 +++---- .../ManiphestTaskDetailController.php | 34 +++++++++++++------ webroot/rsrc/css/phui/phui-curtain-view.css | 17 +++++++--- webroot/rsrc/css/phui/phui-head-thing.css | 2 +- .../rsrc/css/phui/phui-two-column-view.css | 30 ---------------- 5 files changed, 43 insertions(+), 52 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index a3fc0ae771..df05681185 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -127,7 +127,7 @@ return array( '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-curtain-view.css' => '8bb7ee8f', + 'rsrc/css/phui/phui-curtain-view.css' => 'd590da33', 'rsrc/css/phui/phui-document-pro.css' => '92d5b648', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => '9c71d2bf', @@ -135,7 +135,7 @@ 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-head-thing.css' => '11731da0', + 'rsrc/css/phui/phui-head-thing.css' => '31638812', 'rsrc/css/phui/phui-header-view.css' => '26cffd3d', 'rsrc/css/phui/phui-hovercard.css' => 'de1a2119', 'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad', @@ -156,7 +156,7 @@ return array( 'rsrc/css/phui/phui-status.css' => '37309046', 'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2', 'rsrc/css/phui/phui-timeline-view.css' => '2efceff8', - 'rsrc/css/phui/phui-two-column-view.css' => 'ecd7ec62', + 'rsrc/css/phui/phui-two-column-view.css' => '38871c98', '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', @@ -812,7 +812,7 @@ return array( 'phui-calendar-month-css' => '476be7e0', 'phui-chart-css' => '6bf6f78e', 'phui-crumbs-view-css' => '79d536e5', - 'phui-curtain-view-css' => '8bb7ee8f', + 'phui-curtain-view-css' => 'd590da33', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => '9c71d2bf', 'phui-document-view-pro-css' => '92d5b648', @@ -821,7 +821,7 @@ return array( 'phui-fontkit-css' => '9cda225e', 'phui-form-css' => 'aac1d51d', 'phui-form-view-css' => '4a1a0f5e', - 'phui-head-thing-view-css' => '11731da0', + 'phui-head-thing-view-css' => '31638812', 'phui-header-view-css' => '26cffd3d', 'phui-hovercard' => '1bd28176', 'phui-hovercard-view-css' => 'de1a2119', @@ -845,7 +845,7 @@ return array( 'phui-tag-view-css' => '6bbd83e2', 'phui-theme-css' => '027ba77e', 'phui-timeline-view-css' => '2efceff8', - 'phui-two-column-view-css' => 'ecd7ec62', + 'phui-two-column-view-css' => '38871c98', 'phui-workboard-color-css' => 'ac6fe6a7', 'phui-workboard-view-css' => 'e6d89647', 'phui-workcard-view-css' => '3646fb96', diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index d1d93b6c1b..d711229428 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -91,8 +91,8 @@ final class ManiphestTaskDetailController extends ManiphestController { $timeline, $comment_view, )) - ->addPropertySection(pht('DETAILS'), $details) - ->addPropertySection(pht('DESCRIPTION'), $description); + ->addPropertySection(pht('DESCRIPTION'), $description) + ->addPropertySection(pht('DETAILS'), $details); return $this->newPage() ->setTitle($title) @@ -214,10 +214,18 @@ final class ManiphestTaskDetailController extends ManiphestController { $owner_phid = $task->getOwnerPHID(); + $author_phid = $task->getAuthorPHID(); + $handles = $viewer->loadHandles(array($owner_phid, $author_phid)); + if ($owner_phid) { - $assigned_to = $viewer - ->renderHandle($owner_phid) - ->setShowHovercard(true); + $image_uri = $handles[$owner_phid]->getImageURI(); + $image_href = $handles[$owner_phid]->getURI(); + $owner = $viewer->renderHandle($owner_phid)->render(); + $content = phutil_tag('strong', array(), $owner); + $assigned_to = id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); } else { $assigned_to = phutil_tag('em', array(), pht('None')); } @@ -226,14 +234,18 @@ final class ManiphestTaskDetailController extends ManiphestController { ->setHeaderText(pht('Assigned To')) ->appendChild($assigned_to); - $author_phid = $task->getAuthorPHID(); - $author = $viewer - ->renderHandle($author_phid) - ->setShowHovercard(true); + $author_uri = $handles[$author_phid]->getImageURI(); + $author_href = $handles[$author_phid]->getURI(); + $author = $viewer->renderHandle($author_phid)->render(); + $content = phutil_tag('strong', array(), $author); + $authored_by = id(new PHUIHeadThingView()) + ->setImage($author_uri) + ->setImageHref($author_href) + ->setContent($content); $curtain->newPanel() - ->setHeaderText(pht('Author')) - ->appendChild($author); + ->setHeaderText(pht('Authored By')) + ->appendChild($authored_by); return $curtain; } diff --git a/webroot/rsrc/css/phui/phui-curtain-view.css b/webroot/rsrc/css/phui/phui-curtain-view.css index 0c507c8e84..811aaf11c7 100644 --- a/webroot/rsrc/css/phui/phui-curtain-view.css +++ b/webroot/rsrc/css/phui/phui-curtain-view.css @@ -3,16 +3,21 @@ */ .phui-curtain-panel { - margin: 4px; - padding: 4px 0; + padding: 16px 0; + margin: 0 4px; +} + +.device .phui-curtain-panel { + padding: 8px 0; + margin: 0; } .device-desktop .phui-curtain-panel { - border-top: 1px solid {$lightblueborder}; + border-top: 1px solid rgba({$alphablue}, .1); } .phui-curtain-panel-header { - padding: 4px 0 0; + padding: 0 0 4px; color: {$bluetext}; font-weight: bold; } @@ -20,3 +25,7 @@ .phui-curtain-panel-body { padding: 4px 0 0; } + +.device .phui-curtain-panel-body { + padding: 0; +} diff --git a/webroot/rsrc/css/phui/phui-head-thing.css b/webroot/rsrc/css/phui/phui-head-thing.css index bce67ef387..b82c9321a3 100644 --- a/webroot/rsrc/css/phui/phui-head-thing.css +++ b/webroot/rsrc/css/phui/phui-head-thing.css @@ -10,7 +10,7 @@ padding-left: 32px; } -.device-phone .phui-head-thing-view { +.device-phone .phui-two-column-subheader .phui-head-thing-view { min-height: 24px; height: auto; line-height: inherit; diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index 769eaf84e4..156b061c07 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -139,36 +139,6 @@ border: 1px solid rgba({$alphablue}, .2); } -.phui-two-column-properties .phui-property-list-stacked - .phui-property-list-properties .phui-property-list-key { - 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 0 20px 0; - padding: 0 4px; -} - -.device .phui-two-column-properties .phui-property-list-stacked - .phui-property-list-properties .phui-property-list-value { - margin-bottom: 12px; -} - -.device-desktop .phui-two-column-properties .phui-property-list-container, -.device .phui-two-column-properties .phui-property-list-container { - padding: 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-content .phui-two-column-properties.phui-object-box { padding: 0 12px; } From 821ba8b22e58ddb469c152e67a25cb13f45f5ec7 Mon Sep 17 00:00:00 2001 From: "tycho.tatitscheff" Date: Sun, 6 Mar 2016 15:46:41 -0800 Subject: [PATCH 19/57] Fix a typo on Almanac User Guide Summary: Ref T10527 The lack of a * messed up the remarkup. Test Plan: Tested on my instance by pasting the sentence in a phriction document. See the markup correctly done. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin Maniphest Tasks: T10527 Differential Revision: https://secure.phabricator.com/D15421 --- src/docs/user/userguide/almanac.diviner | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/user/userguide/almanac.diviner b/src/docs/user/userguide/almanac.diviner index b7c30cf77a..a6b04e1fd1 100644 --- a/src/docs/user/userguide/almanac.diviner +++ b/src/docs/user/userguide/almanac.diviner @@ -96,7 +96,7 @@ them from the service. Concepts ======== -The major concepts in Almanac are **devices*, **interfaces**, **services**, +The major concepts in Almanac are **devices**, **interfaces**, **services**, **bindings**, **networks**, and **namespaces**. **Devices**: Almanac devices represent physical or virtual devices. From 19eee427adb36abdf449113a1da9e6aa602ecf29 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 6 Mar 2016 15:13:40 -0800 Subject: [PATCH 20/57] Improve Drydock errors for empty commits and missing changes Summary: Ref T10093. Show better errors when a commit fails because it has already been merged and when a fetch fails because the ref isn't present in the remote. Test Plan: {F1160794} {F1160795} Reviewers: chad Reviewed By: chad Subscribers: michaeljs1990, yelirekim Maniphest Tasks: T10093 Differential Revision: https://secure.phabricator.com/D15420 --- src/__phutil_library_map__.php | 3 +- .../DrydockCommandError.php | 18 --- ...dockWorkingCopyBlueprintImplementation.php | 111 ++++++++++++------ .../drydock/exception/DrydockCommandError.php | 58 +++++++++ .../DrydockLandRepositoryOperation.php | 53 ++++++--- .../DrydockRepositoryOperationStatusView.php | 33 +++++- 6 files changed, 204 insertions(+), 72 deletions(-) delete mode 100644 src/applications/drydock/DrydockCommandError/DrydockCommandError.php create mode 100644 src/applications/drydock/exception/DrydockCommandError.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 45adf16a3a..42b6975c02 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -886,7 +886,7 @@ phutil_register_library_map(array( 'DrydockBlueprintTransactionQuery' => 'applications/drydock/query/DrydockBlueprintTransactionQuery.php', 'DrydockBlueprintViewController' => 'applications/drydock/controller/DrydockBlueprintViewController.php', 'DrydockCommand' => 'applications/drydock/storage/DrydockCommand.php', - 'DrydockCommandError' => 'applications/drydock/DrydockCommandError/DrydockCommandError.php', + 'DrydockCommandError' => 'applications/drydock/exception/DrydockCommandError.php', 'DrydockCommandInterface' => 'applications/drydock/interface/command/DrydockCommandInterface.php', 'DrydockCommandQuery' => 'applications/drydock/query/DrydockCommandQuery.php', 'DrydockConsoleController' => 'applications/drydock/controller/DrydockConsoleController.php', @@ -5014,6 +5014,7 @@ phutil_register_library_map(array( 'DrydockDAO', 'PhabricatorPolicyInterface', ), + 'DrydockCommandError' => 'Phobject', 'DrydockCommandInterface' => 'DrydockInterface', 'DrydockCommandQuery' => 'DrydockQuery', 'DrydockConsoleController' => 'DrydockController', diff --git a/src/applications/drydock/DrydockCommandError/DrydockCommandError.php b/src/applications/drydock/DrydockCommandError/DrydockCommandError.php deleted file mode 100644 index d9acbe7456..0000000000 --- a/src/applications/drydock/DrydockCommandError/DrydockCommandError.php +++ /dev/null @@ -1,18 +0,0 @@ - $phase, - 'command' => (string)$command, - 'raw' => (string)$ex->getCommand(), - 'err' => $ex->getError(), - 'stdout' => $ex->getStdout(), - 'stderr' => $ex->getStderr(), - ); - return $error; - } -} diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php index 8b138405eb..a5d61ec067 100644 --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -4,6 +4,8 @@ final class DrydockWorkingCopyBlueprintImplementation extends DrydockBlueprintImplementation { const PHASE_SQUASHMERGE = 'squashmerge'; + const PHASE_REMOTEFETCH = 'blueprint.workingcopy.fetch.remote'; + const PHASE_MERGEFETCH = 'blueprint.workingcopy.fetch.staging'; public function isEnabled() { return true; @@ -240,11 +242,11 @@ final class DrydockWorkingCopyBlueprintImplementation $default = null; foreach ($map as $directory => $spec) { + $interface->pushWorkingDirectory("{$root}/repo/{$directory}/"); + $cmd = array(); $arg = array(); - $interface->pushWorkingDirectory("{$root}/repo/{$directory}/"); - $cmd[] = 'git clean -d --force'; $cmd[] = 'git fetch'; @@ -266,7 +268,20 @@ final class DrydockWorkingCopyBlueprintImplementation $cmd[] = 'git reset --hard origin/%s'; $arg[] = $branch; - } else if ($ref) { + } + + $this->execxv($interface, $cmd, $arg); + + if (idx($spec, 'default')) { + $default = $directory; + } + + // If we're fetching a ref from a remote, do that separately so we can + // raise a more tailored error. + if ($ref) { + $cmd = array(); + $arg = array(); + $ref_uri = $ref['uri']; $ref_ref = $ref['ref']; @@ -277,17 +292,25 @@ final class DrydockWorkingCopyBlueprintImplementation $cmd[] = 'git checkout %s --'; $arg[] = $ref_ref; - } - $cmd = implode(' && ', $cmd); - $argv = array_merge(array($cmd), $arg); + try { + $this->execxv($interface, $cmd, $arg); + } catch (CommandException $ex) { + $display_command = csprintf( + 'git fetch %R %R', + $ref_uri, + $ref_ref); - $result = call_user_func_array( - array($interface, 'execx'), - $argv); + $error = DrydockCommandError::newFromCommandException($ex) + ->setPhase(self::PHASE_REMOTEFETCH) + ->setDisplayCommand($display_command); - if (idx($spec, 'default')) { - $default = $directory; + $lease->setAttribute( + 'workingcopy.vcs.error', + $error->toDictionary()); + + throw $ex; + } } $merges = idx($spec, 'merges'); @@ -428,11 +451,29 @@ final class DrydockWorkingCopyBlueprintImplementation $src_uri = $merge['src.uri']; $src_ref = $merge['src.ref']; - $interface->execx( - 'git fetch --no-tags -- %s +%s:%s', - $src_uri, - $src_ref, - $src_ref); + + try { + $interface->execx( + 'git fetch --no-tags -- %s +%s:%s', + $src_uri, + $src_ref, + $src_ref); + } catch (CommandException $ex) { + $display_command = csprintf( + 'git fetch %R +%R:%R', + $src_uri, + $src_ref, + $src_ref); + + $error = DrydockCommandError::newFromCommandException($ex) + ->setPhase(self::PHASE_MERGEFETCH) + ->setDisplayCommand($display_command); + + $lease->setAttribute('workingcopy.vcs.error', $error->toDictionary()); + + throw $ex; + } + // NOTE: This can never actually generate a commit because we pass // "--squash", but git sometimes runs code to check that a username and @@ -443,32 +484,36 @@ final class DrydockWorkingCopyBlueprintImplementation 'drydock@phabricator', $src_ref); - // Show the user a simplified command if the operation fails and we need to - // report an error. - $show_command = csprintf( - 'git merge --squash -- %R', - $src_ref); - try { $interface->execx('%C', $real_command); } catch (CommandException $ex) { - $error = DrydockCommandError::newFromCommandException( - self::PHASE_SQUASHMERGE, - $show_command, - $ex); + $display_command = csprintf( + 'git merge --squash %R', + $src_ref); - $lease->setAttribute('workingcopy.vcs.error', $error); + $error = DrydockCommandError::newFromCommandException($ex) + ->setPhase(self::PHASE_SQUASHMERGE) + ->setDisplayCommand($display_command); + + $lease->setAttribute('workingcopy.vcs.error', $error->toDictionary()); throw $ex; } } public function getCommandError(DrydockLease $lease) { - $error = $lease->getAttribute('workingcopy.vcs.error'); - if (!$error) { - return null; - } else { - return $error; - } + return $lease->getAttribute('workingcopy.vcs.error'); } + private function execxv( + DrydockCommandInterface $interface, + array $commands, + array $arguments) { + + $commands = implode(' && ', $commands); + $argv = array_merge(array($commands), $arguments); + + return call_user_func_array(array($interface, 'execx'), $argv); + } + + } diff --git a/src/applications/drydock/exception/DrydockCommandError.php b/src/applications/drydock/exception/DrydockCommandError.php new file mode 100644 index 0000000000..26314d1f89 --- /dev/null +++ b/src/applications/drydock/exception/DrydockCommandError.php @@ -0,0 +1,58 @@ +command = (string)$ex->getCommand(); + + $error->error = $ex->getError(); + $error->stdout = $ex->getStdout(); + $error->stderr = $ex->getStderr(); + + return $error; + } + + public function setPhase($phase) { + $this->phase = $phase; + return $this; + } + + public function getPhase() { + return $this->phase; + } + + public function setDisplayCommand($display_command) { + $this->displayCommand = (string)$display_command; + return $this; + } + + public function getDisplayCommand() { + return $this->displayCommand; + } + + public function toDictionary() { + $display_command = $this->getDisplayCommand(); + if ($display_command === null) { + $display_command = $this->command; + } + + return array( + 'phase' => $this->getPhase(), + 'command' => $display_command, + 'raw' => $this->command, + 'err' => $this->error, + 'stdout' => $this->stdout, + 'stderr' => $this->stderr, + ); + } + +} diff --git a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php index 5beb18d07b..77de219115 100644 --- a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php +++ b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php @@ -4,7 +4,9 @@ final class DrydockLandRepositoryOperation extends DrydockRepositoryOperationType { const OPCONST = 'land'; - const PHASE_PUSH = 'push'; + + const PHASE_PUSH = 'op.land.push'; + const PHASE_COMMIT = 'op.land.commit'; public function getOperationDescription( DrydockRepositoryOperation $operation, @@ -119,25 +121,42 @@ final class DrydockLandRepositoryOperation $committer_info['email'], "{$author_name} <{$author_email}>"); - $future - ->write($commit_message) - ->resolvex(); + $future->write($commit_message); try { - $interface->execx( - 'git push origin -- %s:%s', - 'HEAD', - $push_dst); + $future->resolvex(); } catch (CommandException $ex) { - $show_command = csprintf( - 'git push origin -- %s:%s', - 'HEAD', - $push_dst); - $error = DrydockCommandError::newFromCommandException( - self::PHASE_PUSH, - $show_command, - $ex); - $operation->setCommandError($error); + $display_command = csprintf('git commit'); + + // TODO: One reason this can fail is if the changes have already been + // merged. We could try to detect that. + + $error = DrydockCommandError::newFromCommandException($ex) + ->setPhase(self::PHASE_COMMIT) + ->setDisplayCommand($display_command); + + $operation->setCommandError($error->toDictionary()); + + throw $ex; + } + + try { + $interface->execx( + 'git push origin -- %s:%s', + 'HEAD', + $push_dst); + } catch (CommandException $ex) { + $display_command = csprintf( + 'git push origin %R:%R', + 'HEAD', + $push_dst); + + $error = DrydockCommandError::newFromCommandException($ex) + ->setPhase(self::PHASE_PUSH) + ->setDisplayCommand($display_command); + + $operation->setCommandError($error->toDictionary()); + throw $ex; } } diff --git a/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php index 23ad6b81fe..d84b4cc0e7 100644 --- a/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php +++ b/src/applications/drydock/view/DrydockRepositoryOperationStatusView.php @@ -82,6 +82,20 @@ final class DrydockRepositoryOperationStatusView 'This change did not merge cleanly. This usually indicates '. 'that the change is out of date and needs to be updated.'); break; + case DrydockWorkingCopyBlueprintImplementation::PHASE_REMOTEFETCH: + $message = pht( + 'This change could not be fetched from the remote.'); + break; + case DrydockWorkingCopyBlueprintImplementation::PHASE_MERGEFETCH: + $message = pht( + 'This change could not be fetched from the remote staging '. + 'area. It may not have been pushed, or may have been removed.'); + break; + case DrydockLandRepositoryOperation::PHASE_COMMIT: + $message = pht( + 'Committing this change failed. It may already have been '. + 'merged.'); + break; case DrydockLandRepositoryOperation::PHASE_PUSH: $message = pht( 'The push failed. This usually indicates '. @@ -123,10 +137,23 @@ final class DrydockRepositoryOperationStatusView private function renderVCSErrorTable(array $vcs_error) { $rows = array(); - $rows[] = array(pht('Command'), $vcs_error['command']); + + $rows[] = array( + pht('Command'), + phutil_censor_credentials($vcs_error['command']), + ); + $rows[] = array(pht('Error'), $vcs_error['err']); - $rows[] = array(pht('Stdout'), $vcs_error['stdout']); - $rows[] = array(pht('Stderr'), $vcs_error['stderr']); + + $rows[] = array( + pht('Stdout'), + phutil_censor_credentials($vcs_error['stdout']), + ); + + $rows[] = array( + pht('Stderr'), + phutil_censor_credentials($vcs_error['stderr']), + ); $table = id(new AphrontTableView($rows)) ->setColumnClasses( From 16a584ac7e3a74d7072d1a1028165514b413c3d5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 6 Mar 2016 16:01:11 -0800 Subject: [PATCH 21/57] Make `bin/phd debug` quieter by default Summary: By default, `bin/phd debug` activates `--trace`, which is incredibly verbose. Instead, be moderately verbose by default, and only include tracing if `--trace` was passed to `bin/phd debug`. See also D15422. Test Plan: - Ran `bin/phd debug task`, got moderate amount of most useful debug output. - Ran `bin/phd debug task --trace`, got very verbose, detailed low-level debug output. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D15423 --- .../daemon/management/PhabricatorDaemonManagementWorkflow.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php b/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php index a2beba3cd2..447a614600 100644 --- a/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php +++ b/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php @@ -158,8 +158,10 @@ abstract class PhabricatorDaemonManagementWorkflow $this->printLaunchingDaemons($daemons, $debug); + $trace = PhutilArgumentParser::isTraceModeEnabled(); + $flags = array(); - if ($debug || PhabricatorEnv::getEnvConfig('phd.trace')) { + if ($trace || PhabricatorEnv::getEnvConfig('phd.trace')) { $flags[] = '--trace'; } From 2ddd78647b20d615191da1f07313cb8b81f13216 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 7 Mar 2016 08:46:11 -0800 Subject: [PATCH 22/57] Improve "Land Revision" errors for issues related to staging areas Summary: Ref T10093. Changes must be pushed to staging before they can be landed from the web. Test Plan: Changes must be pushed to staging before they can be landed from the web. {F1161909} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10093 Differential Revision: https://secure.phabricator.com/D15427 --- .../query/DifferentialDiffQuery.php | 54 +++++---- .../DrydockLandRepositoryOperation.php | 104 ++++++++++++++++++ .../storage/build/HarbormasterBuildLog.php | 2 + 3 files changed, 141 insertions(+), 19 deletions(-) diff --git a/src/applications/differential/query/DifferentialDiffQuery.php b/src/applications/differential/query/DifferentialDiffQuery.php index 1616d58ac8..23c016446c 100644 --- a/src/applications/differential/query/DifferentialDiffQuery.php +++ b/src/applications/differential/query/DifferentialDiffQuery.php @@ -6,7 +6,9 @@ final class DifferentialDiffQuery private $ids; private $phids; private $revisionIDs; + private $needChangesets = false; + private $needProperties; public function withIDs(array $ids) { $this->ids = $ids; @@ -28,19 +30,17 @@ final class DifferentialDiffQuery return $this; } + public function needProperties($need_properties) { + $this->needProperties = $need_properties; + return $this; + } + + public function newResultObject() { + return new DifferentialDiff(); + } + protected function loadPage() { - $table = new DifferentialDiff(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $diffs) { @@ -76,6 +76,23 @@ final class DifferentialDiffQuery return $diffs; } + protected function didFilterPage(array $diffs) { + if ($this->needProperties) { + $properties = id(new DifferentialDiffProperty())->loadAllWhere( + 'diffID IN (%Ld)', + mpull($diffs, 'getID')); + + $properties = mgroup($properties, 'getDiffID'); + foreach ($diffs as $diff) { + $map = idx($properties, $diff->getID(), array()); + $map = mpull($map, 'getData', 'getName'); + $diff->attachDiffProperties($map); + } + } + + return $diffs; + } + private function loadChangesets(array $diffs) { id(new DifferentialChangesetQuery()) ->setViewer($this->getViewer()) @@ -88,32 +105,31 @@ final class DifferentialDiffQuery return $diffs; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if ($this->ids) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( - $conn_r, + $conn, 'phid IN (%Ls)', $this->phids); } if ($this->revisionIDs) { $where[] = qsprintf( - $conn_r, + $conn, 'revisionID IN (%Ld)', $this->revisionIDs); } - $where[] = $this->buildPagingClause($conn_r); - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { diff --git a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php index 77de219115..4e306772a3 100644 --- a/src/applications/drydock/operation/DrydockLandRepositoryOperation.php +++ b/src/applications/drydock/operation/DrydockLandRepositoryOperation.php @@ -248,6 +248,29 @@ final class DrydockLandRepositoryOperation ); } + // Check if this diff was pushed to a staging area. + $diff = id(new DifferentialDiffQuery()) + ->setViewer($viewer) + ->withIDs(array($revision->getActiveDiff()->getID())) + ->needProperties(true) + ->executeOne(); + + // Older diffs won't have this property. They may still have been pushed. + // At least for now, assume staging changes are present if the property + // is missing. This should smooth the transition to the more formal + // approach. + $has_staging = $diff->hasDiffProperty('arc.staging'); + if ($has_staging) { + $staging = $diff->getProperty('arc.staging'); + if (!is_array($staging)) { + $staging = array(); + } + $status = idx($staging, 'status'); + if ($status != ArcanistDiffWorkflow::STAGING_PUSHED) { + return $this->getBarrierToLandingFromStagingStatus($status); + } + } + // TODO: At some point we should allow installs to give "land reviewed // code" permission to more users than "push any commit", because it is // a much less powerful operation. For now, just require push so this @@ -336,4 +359,85 @@ final class DrydockLandRepositoryOperation return null; } + private function getBarrierToLandingFromStagingStatus($status) { + switch ($status) { + case ArcanistDiffWorkflow::STAGING_USER_SKIP: + return array( + 'title' => pht('Staging Area Skipped'), + 'body' => pht( + 'The diff author used the %s flag to skip pushing this change to '. + 'staging. Changes must be pushed to staging before they can be '. + 'landed from the web.', + phutil_tag('tt', array(), '--skip-staging')), + ); + case ArcanistDiffWorkflow::STAGING_DIFF_RAW: + return array( + 'title' => pht('Raw Diff Source'), + 'body' => pht( + 'The diff was generated from a raw input source, so the change '. + 'could not be pushed to staging. Changes must be pushed to '. + 'staging before they can be landed from the web.'), + ); + case ArcanistDiffWorkflow::STAGING_REPOSITORY_UNKNOWN: + return array( + 'title' => pht('Unknown Repository'), + 'body' => pht( + 'When the diff was generated, the client was not able to '. + 'determine which repository it belonged to, so the change '. + 'was not pushed to staging. Changes must be pushed to staging '. + 'before they can be landed from the web.'), + ); + case ArcanistDiffWorkflow::STAGING_REPOSITORY_UNAVAILABLE: + return array( + 'title' => pht('Staging Unavailable'), + 'body' => pht( + 'When this diff was generated, the server was running an older '. + 'version of Phabricator which did not support staging areas, so '. + 'the change was not pushed to staging. Changes must be pushed '. + 'to staging before they can be landed from the web.'), + ); + case ArcanistDiffWorkflow::STAGING_REPOSITORY_UNSUPPORTED: + return array( + 'title' => pht('Repository Unsupported'), + 'body' => pht( + 'When this diff was generated, the server was running an older '. + 'version of Phabricator which did not support staging areas for '. + 'this version control system, so the chagne was not pushed to '. + 'staging. Changes must be pushed to staging before they can be '. + 'landed from the web.'), + ); + + case ArcanistDiffWorkflow::STAGING_REPOSITORY_UNCONFIGURED: + return array( + 'title' => pht('Repository Unconfigured'), + 'body' => pht( + 'When this diff was generated, the repository was not configured '. + 'with a staging area, so the change was not pushed to staging. '. + 'Changes must be pushed to staging before they can be landed '. + 'from the web.'), + ); + case ArcanistDiffWorkflow::STAGING_CLIENT_UNSUPPORTED: + return array( + 'title' => pht('Client Support Unavailable'), + 'body' => pht( + 'When this diff was generated, the client did not support '. + 'staging areas for this version control system, so the change '. + 'was not pushed to staging. Changes must be pushed to staging '. + 'before they can be landed from the web. Updating the client '. + 'may resolve this issue.'), + ); + default: + return array( + 'title' => pht('Unknown Error'), + 'body' => pht( + 'When this diff was generated, it was not pushed to staging for '. + 'an unknown reason (the status code was "%s"). Changes must be '. + 'pushed to staging before they can be landed from the web. '. + 'The server may be running an out-of-date version of Phabricator, '. + 'and updating may provide more information about this error.', + $status), + ); + } + } + } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php index 40090d0ac3..290b288c6c 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php @@ -114,6 +114,8 @@ final class HarbormasterBuildLog $this->rope->append($content); $this->flush(); + + return $this; } private function flush() { From 98542637a1f340a69dfb0c210423e3492935c545 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 7 Mar 2016 15:18:55 -0800 Subject: [PATCH 23/57] Use modern SearchEngine construction in Nuance Summary: Ref T10537. Minor cleanup of controllers to be more modern / work better on mobile. Test Plan: Browsed all queue / source pages. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10537 Differential Revision: https://secure.phabricator.com/D15428 --- src/__phutil_library_map__.php | 18 ++++++++----- .../controller/NuanceQueueController.php | 11 ++++++++ .../controller/NuanceQueueEditController.php | 3 ++- .../controller/NuanceQueueListController.php | 27 +++---------------- .../controller/NuanceQueueViewController.php | 3 ++- .../controller/NuanceSourceController.php | 11 ++++++++ .../NuanceSourceCreateController.php | 3 ++- .../controller/NuanceSourceEditController.php | 3 ++- .../controller/NuanceSourceListController.php | 27 +++---------------- .../controller/NuanceSourceViewController.php | 3 ++- 10 files changed, 51 insertions(+), 58 deletions(-) create mode 100644 src/applications/nuance/controller/NuanceQueueController.php create mode 100644 src/applications/nuance/controller/NuanceSourceController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 42b6975c02..2678885909 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1433,6 +1433,7 @@ phutil_register_library_map(array( 'NuancePhabricatorFormSourceDefinition' => 'applications/nuance/source/NuancePhabricatorFormSourceDefinition.php', 'NuanceQuery' => 'applications/nuance/query/NuanceQuery.php', 'NuanceQueue' => 'applications/nuance/storage/NuanceQueue.php', + 'NuanceQueueController' => 'applications/nuance/controller/NuanceQueueController.php', 'NuanceQueueDatasource' => 'applications/nuance/typeahead/NuanceQueueDatasource.php', 'NuanceQueueEditController' => 'applications/nuance/controller/NuanceQueueEditController.php', 'NuanceQueueEditor' => 'applications/nuance/editor/NuanceQueueEditor.php', @@ -1457,6 +1458,7 @@ phutil_register_library_map(array( 'NuanceSchemaSpec' => 'applications/nuance/storage/NuanceSchemaSpec.php', 'NuanceSource' => 'applications/nuance/storage/NuanceSource.php', 'NuanceSourceActionController' => 'applications/nuance/controller/NuanceSourceActionController.php', + 'NuanceSourceController' => 'applications/nuance/controller/NuanceSourceController.php', 'NuanceSourceCreateController' => 'applications/nuance/controller/NuanceSourceCreateController.php', 'NuanceSourceDefaultEditCapability' => 'applications/nuance/capability/NuanceSourceDefaultEditCapability.php', 'NuanceSourceDefaultViewCapability' => 'applications/nuance/capability/NuanceSourceDefaultViewCapability.php', @@ -5678,17 +5680,18 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorApplicationTransactionInterface', ), + 'NuanceQueueController' => 'NuanceController', 'NuanceQueueDatasource' => 'PhabricatorTypeaheadDatasource', - 'NuanceQueueEditController' => 'NuanceController', + 'NuanceQueueEditController' => 'NuanceQueueController', 'NuanceQueueEditor' => 'PhabricatorApplicationTransactionEditor', - 'NuanceQueueListController' => 'NuanceController', + 'NuanceQueueListController' => 'NuanceQueueController', 'NuanceQueuePHIDType' => 'PhabricatorPHIDType', 'NuanceQueueQuery' => 'NuanceQuery', 'NuanceQueueSearchEngine' => 'PhabricatorApplicationSearchEngine', 'NuanceQueueTransaction' => 'NuanceTransaction', 'NuanceQueueTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceQueueTransactionQuery' => 'PhabricatorApplicationTransactionQuery', - 'NuanceQueueViewController' => 'NuanceController', + 'NuanceQueueViewController' => 'NuanceQueueController', 'NuanceRequestor' => array( 'NuanceDAO', 'PhabricatorPolicyInterface', @@ -5710,14 +5713,15 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', ), 'NuanceSourceActionController' => 'NuanceController', - 'NuanceSourceCreateController' => 'NuanceController', + 'NuanceSourceController' => 'NuanceController', + 'NuanceSourceCreateController' => 'NuanceSourceController', 'NuanceSourceDefaultEditCapability' => 'PhabricatorPolicyCapability', 'NuanceSourceDefaultViewCapability' => 'PhabricatorPolicyCapability', 'NuanceSourceDefinition' => 'Phobject', 'NuanceSourceDefinitionTestCase' => 'PhabricatorTestCase', - 'NuanceSourceEditController' => 'NuanceController', + 'NuanceSourceEditController' => 'NuanceSourceController', 'NuanceSourceEditor' => 'PhabricatorApplicationTransactionEditor', - 'NuanceSourceListController' => 'NuanceController', + 'NuanceSourceListController' => 'NuanceSourceController', 'NuanceSourceManageCapability' => 'PhabricatorPolicyCapability', 'NuanceSourcePHIDType' => 'PhabricatorPHIDType', 'NuanceSourceQuery' => 'NuanceQuery', @@ -5725,7 +5729,7 @@ phutil_register_library_map(array( 'NuanceSourceTransaction' => 'NuanceTransaction', 'NuanceSourceTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceSourceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', - 'NuanceSourceViewController' => 'NuanceController', + 'NuanceSourceViewController' => 'NuanceSourceController', 'NuanceTransaction' => 'PhabricatorApplicationTransaction', 'OwnersConduitAPIMethod' => 'ConduitAPIMethod', 'OwnersEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', diff --git a/src/applications/nuance/controller/NuanceQueueController.php b/src/applications/nuance/controller/NuanceQueueController.php new file mode 100644 index 0000000000..5736c9a234 --- /dev/null +++ b/src/applications/nuance/controller/NuanceQueueController.php @@ -0,0 +1,11 @@ +newApplicationMenu() + ->setSearchEngine(new NuanceQueueSearchEngine()); + } + +} diff --git a/src/applications/nuance/controller/NuanceQueueEditController.php b/src/applications/nuance/controller/NuanceQueueEditController.php index cfb657615d..3091cf384d 100644 --- a/src/applications/nuance/controller/NuanceQueueEditController.php +++ b/src/applications/nuance/controller/NuanceQueueEditController.php @@ -1,6 +1,7 @@ getViewer(); diff --git a/src/applications/nuance/controller/NuanceQueueListController.php b/src/applications/nuance/controller/NuanceQueueListController.php index e139386bdf..d2372dbe3a 100644 --- a/src/applications/nuance/controller/NuanceQueueListController.php +++ b/src/applications/nuance/controller/NuanceQueueListController.php @@ -1,31 +1,12 @@ getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) - ->setQueryKey($request->getURIData('queryKey')) - ->setSearchEngine(new NuanceQueueSearchEngine()) - ->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())); - - id(new NuanceQueueSearchEngine()) - ->setViewer($user) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; + return id(new NuanceQueueSearchEngine()) + ->setController($this) + ->buildResponse(); } protected function buildApplicationCrumbs() { diff --git a/src/applications/nuance/controller/NuanceQueueViewController.php b/src/applications/nuance/controller/NuanceQueueViewController.php index 71e5e04ce7..a5fa908c4a 100644 --- a/src/applications/nuance/controller/NuanceQueueViewController.php +++ b/src/applications/nuance/controller/NuanceQueueViewController.php @@ -1,6 +1,7 @@ getViewer(); diff --git a/src/applications/nuance/controller/NuanceSourceController.php b/src/applications/nuance/controller/NuanceSourceController.php new file mode 100644 index 0000000000..c3b73376b5 --- /dev/null +++ b/src/applications/nuance/controller/NuanceSourceController.php @@ -0,0 +1,11 @@ +newApplicationMenu() + ->setSearchEngine(new NuanceSourceSearchEngine()); + } + +} diff --git a/src/applications/nuance/controller/NuanceSourceCreateController.php b/src/applications/nuance/controller/NuanceSourceCreateController.php index 22dd41024c..fb02fe6bbd 100644 --- a/src/applications/nuance/controller/NuanceSourceCreateController.php +++ b/src/applications/nuance/controller/NuanceSourceCreateController.php @@ -1,6 +1,7 @@ requireApplicationCapability( diff --git a/src/applications/nuance/controller/NuanceSourceEditController.php b/src/applications/nuance/controller/NuanceSourceEditController.php index 18234bc778..b4dff7fc48 100644 --- a/src/applications/nuance/controller/NuanceSourceEditController.php +++ b/src/applications/nuance/controller/NuanceSourceEditController.php @@ -1,6 +1,7 @@ requireApplicationCapability( diff --git a/src/applications/nuance/controller/NuanceSourceListController.php b/src/applications/nuance/controller/NuanceSourceListController.php index 1d906dc3d3..f18ed66706 100644 --- a/src/applications/nuance/controller/NuanceSourceListController.php +++ b/src/applications/nuance/controller/NuanceSourceListController.php @@ -1,31 +1,12 @@ getRequest(); - $controller = id(new PhabricatorApplicationSearchController($request)) - ->setQueryKey($request->getURIData('queryKey')) - ->setSearchEngine(new NuanceSourceSearchEngine()) - ->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())); - - id(new NuanceSourceSearchEngine()) - ->setViewer($user) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; + return id(new NuanceSourceSearchEngine()) + ->setController($this) + ->buildResponse(); } protected function buildApplicationCrumbs() { diff --git a/src/applications/nuance/controller/NuanceSourceViewController.php b/src/applications/nuance/controller/NuanceSourceViewController.php index 78d6949455..90eaea8046 100644 --- a/src/applications/nuance/controller/NuanceSourceViewController.php +++ b/src/applications/nuance/controller/NuanceSourceViewController.php @@ -1,6 +1,7 @@ getViewer(); From 01ed526527b36386ae474298c51931c0f8f0d62e Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 7 Mar 2016 15:24:59 -0800 Subject: [PATCH 24/57] Modernize Nuance queries and search engines Summary: Ref T10537. Minor updates to simplify and modernize these codepaths. Test Plan: Searched for queues and sources. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10537 Differential Revision: https://secure.phabricator.com/D15429 --- .../query/AlmanacServiceSearchEngine.php | 4 ---- .../nuance/query/NuanceQueueQuery.php | 18 +++++------------- .../nuance/query/NuanceQueueSearchEngine.php | 16 +++++++--------- .../nuance/query/NuanceSourceQuery.php | 18 +++++------------- .../nuance/query/NuanceSourceSearchEngine.php | 16 +++++++--------- 5 files changed, 24 insertions(+), 48 deletions(-) diff --git a/src/applications/almanac/query/AlmanacServiceSearchEngine.php b/src/applications/almanac/query/AlmanacServiceSearchEngine.php index 1a9509a2c2..a2fd9c23b7 100644 --- a/src/applications/almanac/query/AlmanacServiceSearchEngine.php +++ b/src/applications/almanac/query/AlmanacServiceSearchEngine.php @@ -15,10 +15,6 @@ final class AlmanacServiceSearchEngine return new AlmanacServiceQuery(); } - public function newResultObject() { - return new AlmanacService(); - } - protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); diff --git a/src/applications/nuance/query/NuanceQueueQuery.php b/src/applications/nuance/query/NuanceQueueQuery.php index d50e393667..10f761d189 100644 --- a/src/applications/nuance/query/NuanceQueueQuery.php +++ b/src/applications/nuance/query/NuanceQueueQuery.php @@ -16,20 +16,12 @@ final class NuanceQueueQuery return $this; } + public function newResultObject() { + return new NuanceQueue(); + } + protected function loadPage() { - $table = new NuanceQueue(); - $conn = $table->establishConnection('r'); - - $data = queryfx_all( - $conn, - '%Q FROM %T %Q %Q %Q', - $this->buildSelectClause($conn), - $table->getTableName(), - $this->buildWhereClause($conn), - $this->buildOrderClause($conn), - $this->buildLimitClause($conn)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); } protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { diff --git a/src/applications/nuance/query/NuanceQueueSearchEngine.php b/src/applications/nuance/query/NuanceQueueSearchEngine.php index 12259982f1..2f794c2a9c 100644 --- a/src/applications/nuance/query/NuanceQueueSearchEngine.php +++ b/src/applications/nuance/query/NuanceQueueSearchEngine.php @@ -11,21 +11,19 @@ final class NuanceQueueSearchEngine return pht('Nuance Queues'); } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - return $saved; + public function newQuery() { + return new NuanceQueueQuery(); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new NuanceQueueQuery()); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved_query) {} + protected function buildCustomSearchFields() { + return array(); + } protected function getURI($path) { return '/nuance/queue/'.$path; diff --git a/src/applications/nuance/query/NuanceSourceQuery.php b/src/applications/nuance/query/NuanceSourceQuery.php index ee4b964ee3..6fbc4d3ddf 100644 --- a/src/applications/nuance/query/NuanceSourceQuery.php +++ b/src/applications/nuance/query/NuanceSourceQuery.php @@ -22,20 +22,12 @@ final class NuanceSourceQuery return $this; } + public function newResultObject() { + return new NuanceSource(); + } + protected function loadPage() { - $table = new NuanceSource(); - $conn = $table->establishConnection('r'); - - $data = queryfx_all( - $conn, - '%Q FROM %T %Q %Q %Q', - $this->buildSelectClause($conn), - $table->getTableName(), - $this->buildWhereClause($conn), - $this->buildOrderClause($conn), - $this->buildLimitClause($conn)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); } protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { diff --git a/src/applications/nuance/query/NuanceSourceSearchEngine.php b/src/applications/nuance/query/NuanceSourceSearchEngine.php index 02a8b502fb..2991c69c5f 100644 --- a/src/applications/nuance/query/NuanceSourceSearchEngine.php +++ b/src/applications/nuance/query/NuanceSourceSearchEngine.php @@ -11,21 +11,19 @@ final class NuanceSourceSearchEngine return pht('Nuance Sources'); } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - return $saved; + public function newQuery() { + return new NuanceSourceQuery(); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new NuanceSourceQuery()); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved_query) {} + protected function buildCustomSearchFields() { + return array(); + } protected function getURI($path) { return '/nuance/source/'.$path; From 6872b9680877168fc53e66ee246e99deb049f0f4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 7 Mar 2016 16:14:01 -0800 Subject: [PATCH 25/57] Convert Nuance sources and queues to two-column + curtain Summary: Ref T10537. Update the detail views. Test Plan: {F1162212} {F1162213} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10537 Differential Revision: https://secure.phabricator.com/D15430 --- .../controller/NuanceQueueViewController.php | 49 ++++------- .../controller/NuanceSourceViewController.php | 84 +++++++------------ 2 files changed, 46 insertions(+), 87 deletions(-) diff --git a/src/applications/nuance/controller/NuanceQueueViewController.php b/src/applications/nuance/controller/NuanceQueueViewController.php index a5fa908c4a..8f4e85565a 100644 --- a/src/applications/nuance/controller/NuanceQueueViewController.php +++ b/src/applications/nuance/controller/NuanceQueueViewController.php @@ -19,29 +19,25 @@ final class NuanceQueueViewController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Queues'), $this->getApplicationURI('queue/')); $crumbs->addTextCrumb($queue->getName()); + $crumbs->setBorder(true); $header = $this->buildHeaderView($queue); - $actions = $this->buildActionView($queue); - $properties = $this->buildPropertyView($queue, $actions); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $curtain = $this->buildCurtain($queue); $timeline = $this->buildTransactionTimeline( $queue, new NuanceQueueTransactionQuery()); $timeline->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - $timeline, - ), - array( - 'title' => $title, - )); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn($timeline); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function buildHeaderView(NuanceQueue $queue) { @@ -55,19 +51,18 @@ final class NuanceQueueViewController return $header; } - private function buildActionView(NuanceQueue $queue) { + private function buildCurtain(NuanceQueue $queue) { $viewer = $this->getViewer(); $id = $queue->getID(); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $queue, PhabricatorPolicyCapability::CAN_EDIT); - $actions->addAction( + $curtain = $this->newCurtainView($queue); + + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Queue')) ->setIcon('fa-pencil') @@ -75,19 +70,7 @@ final class NuanceQueueViewController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - return $actions; + return $curtain; } - private function buildPropertyView( - NuanceQueue $queue, - PhabricatorActionListView $actions) { - $viewer = $this->getViewer(); - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($queue) - ->setActionList($actions); - - return $properties; - } } diff --git a/src/applications/nuance/controller/NuanceSourceViewController.php b/src/applications/nuance/controller/NuanceSourceViewController.php index 90eaea8046..babd4e88f8 100644 --- a/src/applications/nuance/controller/NuanceSourceViewController.php +++ b/src/applications/nuance/controller/NuanceSourceViewController.php @@ -16,53 +16,38 @@ final class NuanceSourceViewController $source_id = $source->getID(); - $timeline = $this->buildTransactionTimeline( - $source, - new NuanceSourceTransactionQuery()); - $timeline->setShouldTerminate(true); - $header = $this->buildHeaderView($source); - $actions = $this->buildActionView($source); - $properties = $this->buildPropertyView($source, $actions); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $curtain = $this->buildCurtain($source); + $properties = $this->buildPropertyView($source); $title = $source->getName(); - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Sources'), $this->getApplicationURI('source/')); - - $crumbs->addTextCrumb($title); - - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $source, - PhabricatorPolicyCapability::CAN_EDIT); $routing_list = id(new PHUIPropertyListView()) ->addProperty( pht('Default Queue'), $viewer->renderHandle($source->getDefaultQueuePHID())); - $routing_header = id(new PHUIHeaderView()) - ->setHeader(pht('Routing Rules')); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Sources'), $this->getApplicationURI('source/')); + $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); - $routing = id(new PHUIObjectBoxView()) - ->setHeader($routing_header) - ->addPropertyList($routing_list); + $timeline = $this->buildTransactionTimeline( + $source, + new NuanceSourceTransactionQuery()); + $timeline->setShouldTerminate(true); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - $routing, - $timeline, - ), - array( - 'title' => $title, - )); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('DETAILS'), $properties) + ->addPropertySection(pht('ROUTING'), $routing_list) + ->setMainColumn($timeline); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function buildHeaderView(NuanceSource $source) { @@ -76,7 +61,7 @@ final class NuanceSourceViewController return $header; } - private function buildActionView(NuanceSource $source) { + private function buildCurtain(NuanceSource $source) { $viewer = $this->getViewer(); $id = $source->getID(); @@ -88,7 +73,9 @@ final class NuanceSourceViewController $source, PhabricatorPolicyCapability::CAN_EDIT); - $actions->addAction( + $curtain = $this->newCurtainView($source); + + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Source')) ->setIcon('fa-pencil') @@ -100,35 +87,24 @@ final class NuanceSourceViewController $definition = $source->requireDefinition(); $source_actions = $definition->getSourceViewActions($request); foreach ($source_actions as $source_action) { - $actions->addAction($source_action); + $curtain->addAction($source_action); } - return $actions; + return $curtain; } private function buildPropertyView( - NuanceSource $source, - PhabricatorActionListView $actions) { - $viewer = $this->getRequest()->getUser(); + NuanceSource $source) { + $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($source) - ->setActionList($actions); + ->setViewer($viewer); $definition = $source->requireDefinition(); $properties->addProperty( pht('Source Type'), $definition->getName()); - $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( - $viewer, - $source); - - $properties->addProperty( - pht('Editable By'), - $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); - return $properties; } } From 86768737c597981d17a3cae85b928d47a4729d7f Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 7 Mar 2016 16:22:39 -0800 Subject: [PATCH 26/57] Move Nuance Queues to EditEngine Summary: Ref T10537. Update queue editing to use EditEngine. Test Plan: - Created a new queue. - Edited an existing queue. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10537 Differential Revision: https://secure.phabricator.com/D15431 --- src/__phutil_library_map__.php | 2 + .../PhabricatorNuanceApplication.php | 6 +- .../controller/NuanceQueueEditController.php | 130 +----------------- .../nuance/editor/NuanceQueueEditEngine.php | 80 +++++++++++ .../nuance/editor/NuanceQueueEditor.php | 1 + .../nuance/storage/NuanceQueue.php | 4 +- .../nuance/storage/NuanceQueueTransaction.php | 16 +-- 7 files changed, 96 insertions(+), 143 deletions(-) create mode 100644 src/applications/nuance/editor/NuanceQueueEditEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 2678885909..5a5a96de5a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1436,6 +1436,7 @@ phutil_register_library_map(array( 'NuanceQueueController' => 'applications/nuance/controller/NuanceQueueController.php', 'NuanceQueueDatasource' => 'applications/nuance/typeahead/NuanceQueueDatasource.php', 'NuanceQueueEditController' => 'applications/nuance/controller/NuanceQueueEditController.php', + 'NuanceQueueEditEngine' => 'applications/nuance/editor/NuanceQueueEditEngine.php', 'NuanceQueueEditor' => 'applications/nuance/editor/NuanceQueueEditor.php', 'NuanceQueueListController' => 'applications/nuance/controller/NuanceQueueListController.php', 'NuanceQueuePHIDType' => 'applications/nuance/phid/NuanceQueuePHIDType.php', @@ -5683,6 +5684,7 @@ phutil_register_library_map(array( 'NuanceQueueController' => 'NuanceController', 'NuanceQueueDatasource' => 'PhabricatorTypeaheadDatasource', 'NuanceQueueEditController' => 'NuanceQueueController', + 'NuanceQueueEditEngine' => 'PhabricatorEditEngine', 'NuanceQueueEditor' => 'PhabricatorApplicationTransactionEditor', 'NuanceQueueListController' => 'NuanceQueueController', 'NuanceQueuePHIDType' => 'PhabricatorPHIDType', diff --git a/src/applications/nuance/application/PhabricatorNuanceApplication.php b/src/applications/nuance/application/PhabricatorNuanceApplication.php index e3d5825d4e..67a6dca556 100644 --- a/src/applications/nuance/application/PhabricatorNuanceApplication.php +++ b/src/applications/nuance/application/PhabricatorNuanceApplication.php @@ -45,16 +45,16 @@ final class PhabricatorNuanceApplication extends PhabricatorApplication { 'new/' => 'NuanceItemEditController', ), 'source/' => array( - '(?:query/(?P[^/]+)/)?' => 'NuanceSourceListController', + $this->getQueryRoutePattern() => 'NuanceSourceListController', 'view/(?P[1-9]\d*)/' => 'NuanceSourceViewController', 'edit/(?P[1-9]\d*)/' => 'NuanceSourceEditController', 'new/(?P[^/]+)/' => 'NuanceSourceEditController', 'create/' => 'NuanceSourceCreateController', ), 'queue/' => array( - '(?:query/(?P[^/]+)/)?' => 'NuanceQueueListController', + $this->getQueryRoutePattern() => 'NuanceQueueListController', + $this->getEditRoutePattern('edit/') => 'NuanceQueueEditController', 'view/(?P[1-9]\d*)/' => 'NuanceQueueViewController', - 'edit/(?P[1-9]\d*)/' => 'NuanceQueueEditController', 'new/' => 'NuanceQueueEditController', ), 'requestor/' => array( diff --git a/src/applications/nuance/controller/NuanceQueueEditController.php b/src/applications/nuance/controller/NuanceQueueEditController.php index 3091cf384d..5f220e1ee4 100644 --- a/src/applications/nuance/controller/NuanceQueueEditController.php +++ b/src/applications/nuance/controller/NuanceQueueEditController.php @@ -4,133 +4,9 @@ final class NuanceQueueEditController extends NuanceQueueController { public function handleRequest(AphrontRequest $request) { - $viewer = $this->getViewer(); - $queues_uri = $this->getApplicationURI('queue/'); - - $queue_id = $request->getURIData('id'); - $is_new = !$queue_id; - if ($is_new) { - $queue = NuanceQueue::initializeNewQueue(); - $cancel_uri = $queues_uri; - } else { - $queue = id(new NuanceQueueQuery()) - ->setViewer($viewer) - ->withIDs(array($queue_id)) - ->executeOne(); - if (!$queue) { - return new Aphront404Response(); - } - $cancel_uri = $queue->getURI(); - } - - $v_name = $queue->getName(); - $e_name = true; - $v_edit = $queue->getEditPolicy(); - $v_view = $queue->getViewPolicy(); - - $validation_exception = null; - if ($request->isFormPost()) { - $e_name = null; - - $v_name = $request->getStr('name'); - $v_edit = $request->getStr('editPolicy'); - $v_view = $request->getStr('viewPolicy'); - - $type_name = NuanceQueueTransaction::TYPE_NAME; - $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; - $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; - - $xactions = array(); - - $xactions[] = id(new NuanceQueueTransaction()) - ->setTransactionType($type_name) - ->setNewValue($v_name); - - $xactions[] = id(new NuanceQueueTransaction()) - ->setTransactionType($type_view) - ->setNewValue($v_view); - - $xactions[] = id(new NuanceQueueTransaction()) - ->setTransactionType($type_edit) - ->setNewValue($v_edit); - - $editor = id(new NuanceQueueEditor()) - ->setActor($viewer) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true); - - try { - - $editor->applyTransactions($queue, $xactions); - - $uri = $queue->getURI(); - return id(new AphrontRedirectResponse())->setURI($uri); - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - - $e_name = $ex->getShortMessage($type_name); - } - } - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Queues'), $queues_uri); - - if ($is_new) { - $title = pht('Create Queue'); - $crumbs->addTextCrumb(pht('Create')); - } else { - $title = pht('Edit %s', $queue->getName()); - $crumbs->addTextCrumb($queue->getName(), $queue->getURI()); - $crumbs->addTextCrumb(pht('Edit')); - } - - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($queue) - ->execute(); - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Name')) - ->setName('name') - ->setError($e_name) - ->setValue($v_name)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicyObject($queue) - ->setPolicies($policies) - ->setValue($v_view) - ->setName('viewPolicy')) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($viewer) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicyObject($queue) - ->setPolicies($policies) - ->setValue($v_edit) - ->setName('editPolicy')) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($cancel_uri) - ->setValue(pht('Save'))); - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->setValidationException($validation_exception) - ->appendChild($form); - - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, - )); + return id(new NuanceQueueEditEngine()) + ->setController($this) + ->buildResponse(); } } diff --git a/src/applications/nuance/editor/NuanceQueueEditEngine.php b/src/applications/nuance/editor/NuanceQueueEditEngine.php new file mode 100644 index 0000000000..8e4de2611f --- /dev/null +++ b/src/applications/nuance/editor/NuanceQueueEditEngine.php @@ -0,0 +1,80 @@ +getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Queue'); + } + + protected function getObjectCreateShortText() { + return pht('Create Queue'); + } + + protected function getEditorURI() { + return '/nuance/queue/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/nuance/queue/'; + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the queue.')) + ->setTransactionType(NuanceQueueTransaction::TYPE_NAME) + ->setIsRequired(true) + ->setValue($object->getName()), + ); + } + +} diff --git a/src/applications/nuance/editor/NuanceQueueEditor.php b/src/applications/nuance/editor/NuanceQueueEditor.php index 589ab5da5c..cb3ead2417 100644 --- a/src/applications/nuance/editor/NuanceQueueEditor.php +++ b/src/applications/nuance/editor/NuanceQueueEditor.php @@ -68,6 +68,7 @@ final class NuanceQueueEditor return parent::applyCustomExternalTransaction($object, $xaction); } + protected function validateTransaction( PhabricatorLiskDAO $object, $type, diff --git a/src/applications/nuance/storage/NuanceQueue.php b/src/applications/nuance/storage/NuanceQueue.php index 093b437e24..15197d9bd8 100644 --- a/src/applications/nuance/storage/NuanceQueue.php +++ b/src/applications/nuance/storage/NuanceQueue.php @@ -27,7 +27,9 @@ final class NuanceQueue } public static function initializeNewQueue() { - return new NuanceQueue(); + return id(new self()) + ->setViewPolicy(PhabricatorPolicies::POLICY_USER) + ->setEditPolicy(PhabricatorPolicies::POLICY_USER); } public function save() { diff --git a/src/applications/nuance/storage/NuanceQueueTransaction.php b/src/applications/nuance/storage/NuanceQueueTransaction.php index c1630cdbd8..44309c2ae6 100644 --- a/src/applications/nuance/storage/NuanceQueueTransaction.php +++ b/src/applications/nuance/storage/NuanceQueueTransaction.php @@ -12,18 +12,6 @@ final class NuanceQueueTransaction extends NuanceTransaction { return new NuanceQueueTransactionComment(); } - public function shouldHide() { - $old = $this->getOldValue(); - $type = $this->getTransactionType(); - - switch ($type) { - case self::TYPE_NAME: - return ($old === null); - } - - return parent::shouldHide(); - } - public function getTitle() { $old = $this->getOldValue(); $new = $this->getNewValue(); @@ -32,6 +20,10 @@ final class NuanceQueueTransaction extends NuanceTransaction { $author_phid = $this->getAuthorPHID(); switch ($type) { + case PhabricatorTransactions::TYPE_CREATE: + return pht( + '%s created this queue.', + $this->renderHandleLink($author_phid)); case self::TYPE_NAME: return pht( '%s renamed this queue from "%s" to "%s".', From d653b125b52633f0978e66223f0af573a328e38d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 8 Mar 2016 07:26:56 -0800 Subject: [PATCH 27/57] Add back calendar comment form Summary: Fix T10544, missed this in testing. Test Plan: Pull up event, see form, leave comment. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T10544 Differential Revision: https://secure.phabricator.com/D15437 --- .../controller/PhabricatorCalendarEventViewController.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index eb435a42e9..5880db1210 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -89,7 +89,10 @@ final class PhabricatorCalendarEventViewController $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setMainColumn($timeline) + ->setMainColumn(array( + $timeline, + $add_comment_form, + )) ->setCurtain($curtain) ->addPropertySection(pht('DETAILS'), $details) ->addPropertySection(pht('DESCRIPTION'), $description); From 8a7c9639086277cf26bf4a7314bfe1260c74e398 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 8 Mar 2016 04:42:58 -0800 Subject: [PATCH 28/57] Allow applications to test if a user could edit a certain field by clicking "Edit Thing" Summary: See D15432. There, we can use this test to check if the user //could// reassign the task by using "Edit Form" or the stacked actions, so any dedicated "claim" element is consistent with the other permissions. Test Plan: - Added a `var_dump($can_reassign)` after the call. - Saw `true`. - Edited the edit form, locked and disabled "Assigned To". - Saw `false`. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D15433 --- .../ManiphestTaskDetailController.php | 22 +++++--- .../editengine/PhabricatorEditEngine.php | 56 ++++++++++++++++++- 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index d711229428..d944ffb0f4 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -26,6 +26,10 @@ final class ManiphestTaskDetailController extends ManiphestController { ->setViewer($viewer) ->readFieldsFromStorage($task); + $edit_engine = id(new ManiphestEditEngine()) + ->setViewer($viewer) + ->setTargetObject($task); + $e_commit = ManiphestTaskHasCommitEdgeType::EDGECONST; $e_dep_on = ManiphestTaskDependsOnTaskEdgeType::EDGECONST; $e_dep_by = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST; @@ -73,12 +77,11 @@ final class ManiphestTaskDetailController extends ManiphestController { $header = $this->buildHeaderView($task); $details = $this->buildPropertyView($task, $field_list, $edges, $handles); $description = $this->buildDescriptionView($task, $engine); - $curtain = $this->buildCurtain($task); + $curtain = $this->buildCurtain($task, $edit_engine); $title = pht('%s %s', $monogram, $task->getTitle()); - $comment_view = id(new ManiphestEditEngine()) - ->setViewer($viewer) + $comment_view = $edit_engine ->buildEditEngineCommentView($task); $timeline->setQuoteRef($monogram); @@ -146,7 +149,9 @@ final class ManiphestTaskDetailController extends ManiphestController { } - private function buildCurtain(ManiphestTask $task) { + private function buildCurtain( + ManiphestTask $task, + PhabricatorEditEngine $edit_engine) { $viewer = $this->getViewer(); $id = $task->getID(); @@ -176,11 +181,12 @@ final class ManiphestTaskDetailController extends ManiphestController { ->setDisabled(!$can_edit) ->setWorkflow(true)); - $edit_config = id(new ManiphestEditEngine()) - ->setViewer($viewer) - ->loadDefaultEditConfiguration(); - + $edit_config = $edit_engine->loadDefaultEditConfiguration(); $can_create = (bool)$edit_config; + + $can_reassign = $edit_engine->hasEditAccessToTransaction( + ManiphestTransaction::TYPE_OWNER); + if ($can_create) { $form_key = $edit_config->getIdentifier(); $edit_uri = id(new PhutilURI("/task/edit/form/{$form_key}/")) diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index ce7ec1c437..8be7c9d6f2 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -821,7 +821,7 @@ abstract class PhabricatorEditEngine } private function buildCrumbs($object, $final = false) { - $controller = $this->getcontroller(); + $controller = $this->getController(); $crumbs = $controller->buildApplicationCrumbsForEditEngine(); if ($this->getIsCreate()) { @@ -1179,6 +1179,60 @@ abstract class PhabricatorEditEngine return $actions; } + + /** + * Test if the viewer could apply a certain type of change by using the + * normal "Edit" form. + * + * This method returns `true` if the user has access to an edit form and + * that edit form has a field which applied the specified transaction type, + * and that field is visible and editable for the user. + * + * For example, you can use it to test if a user is able to reassign tasks + * or not, prior to rendering dedicated UI for task reassingment. + * + * Note that this method does NOT test if the user can actually edit the + * current object, just if they have access to the related field. + * + * @param const Transaction type to test for. + * @return bool True if the user could "Edit" to apply the transaction type. + */ + final public function hasEditAccessToTransaction($xaction_type) { + $viewer = $this->getViewer(); + + $config = $this->loadDefaultEditConfiguration(); + if (!$config) { + return false; + } + + $object = $this->getTargetObject(); + if (!$object) { + $object = $this->newEditableObject(); + } + + $fields = $this->buildEditFields($object); + + $field = null; + foreach ($fields as $form_field) { + $field_xaction_type = $form_field->getTransactionType(); + if ($field_xaction_type === $xaction_type) { + $field = $form_field; + break; + } + } + + if (!$field) { + return false; + } + + if (!$field->shouldReadValueFromSubmit()) { + return false; + } + + return true; + } + + final public function addActionToCrumbs(PHUICrumbsView $crumbs) { $viewer = $this->getViewer(); From aa5df5fb07861265d00c5482e74fdec4b86ec6d2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 8 Mar 2016 05:09:25 -0800 Subject: [PATCH 29/57] Convert Nuance Sources to EditEngine Summary: Ref T10537. Converts sources to EditEngine. Test Plan: - Created a new source. - Edited an existing source. - Submitted a complaint with the complaint form. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10537 Differential Revision: https://secure.phabricator.com/D15434 --- src/__phutil_library_map__.php | 4 +- .../PhabricatorNuanceApplication.php | 5 +- .../controller/NuanceItemViewController.php | 2 +- .../controller/NuanceQueueListController.php | 13 +- .../NuanceSourceActionController.php | 19 +- .../NuanceSourceCreateController.php | 58 ----- .../controller/NuanceSourceEditController.php | 109 +++++----- .../controller/NuanceSourceListController.php | 13 +- .../controller/NuanceSourceViewController.php | 10 +- .../nuance/editor/NuanceSourceEditEngine.php | 108 ++++++++++ .../nuance/query/NuanceSourceQuery.php | 17 ++ .../NuancePhabricatorFormSourceDefinition.php | 19 -- .../nuance/source/NuanceSourceDefinition.php | 203 ++---------------- .../nuance/storage/NuanceSource.php | 34 +-- 14 files changed, 235 insertions(+), 379 deletions(-) delete mode 100644 src/applications/nuance/controller/NuanceSourceCreateController.php create mode 100644 src/applications/nuance/editor/NuanceSourceEditEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5a5a96de5a..8c7071c673 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1460,12 +1460,12 @@ phutil_register_library_map(array( 'NuanceSource' => 'applications/nuance/storage/NuanceSource.php', 'NuanceSourceActionController' => 'applications/nuance/controller/NuanceSourceActionController.php', 'NuanceSourceController' => 'applications/nuance/controller/NuanceSourceController.php', - 'NuanceSourceCreateController' => 'applications/nuance/controller/NuanceSourceCreateController.php', 'NuanceSourceDefaultEditCapability' => 'applications/nuance/capability/NuanceSourceDefaultEditCapability.php', 'NuanceSourceDefaultViewCapability' => 'applications/nuance/capability/NuanceSourceDefaultViewCapability.php', 'NuanceSourceDefinition' => 'applications/nuance/source/NuanceSourceDefinition.php', 'NuanceSourceDefinitionTestCase' => 'applications/nuance/source/__tests__/NuanceSourceDefinitionTestCase.php', 'NuanceSourceEditController' => 'applications/nuance/controller/NuanceSourceEditController.php', + 'NuanceSourceEditEngine' => 'applications/nuance/editor/NuanceSourceEditEngine.php', 'NuanceSourceEditor' => 'applications/nuance/editor/NuanceSourceEditor.php', 'NuanceSourceListController' => 'applications/nuance/controller/NuanceSourceListController.php', 'NuanceSourceManageCapability' => 'applications/nuance/capability/NuanceSourceManageCapability.php', @@ -5716,12 +5716,12 @@ phutil_register_library_map(array( ), 'NuanceSourceActionController' => 'NuanceController', 'NuanceSourceController' => 'NuanceController', - 'NuanceSourceCreateController' => 'NuanceSourceController', 'NuanceSourceDefaultEditCapability' => 'PhabricatorPolicyCapability', 'NuanceSourceDefaultViewCapability' => 'PhabricatorPolicyCapability', 'NuanceSourceDefinition' => 'Phobject', 'NuanceSourceDefinitionTestCase' => 'PhabricatorTestCase', 'NuanceSourceEditController' => 'NuanceSourceController', + 'NuanceSourceEditEngine' => 'PhabricatorEditEngine', 'NuanceSourceEditor' => 'PhabricatorApplicationTransactionEditor', 'NuanceSourceListController' => 'NuanceSourceController', 'NuanceSourceManageCapability' => 'PhabricatorPolicyCapability', diff --git a/src/applications/nuance/application/PhabricatorNuanceApplication.php b/src/applications/nuance/application/PhabricatorNuanceApplication.php index 67a6dca556..e6cd787970 100644 --- a/src/applications/nuance/application/PhabricatorNuanceApplication.php +++ b/src/applications/nuance/application/PhabricatorNuanceApplication.php @@ -46,16 +46,13 @@ final class PhabricatorNuanceApplication extends PhabricatorApplication { ), 'source/' => array( $this->getQueryRoutePattern() => 'NuanceSourceListController', + $this->getEditRoutePattern('edit/') => 'NuanceSourceEditController', 'view/(?P[1-9]\d*)/' => 'NuanceSourceViewController', - 'edit/(?P[1-9]\d*)/' => 'NuanceSourceEditController', - 'new/(?P[^/]+)/' => 'NuanceSourceEditController', - 'create/' => 'NuanceSourceCreateController', ), 'queue/' => array( $this->getQueryRoutePattern() => 'NuanceQueueListController', $this->getEditRoutePattern('edit/') => 'NuanceQueueEditController', 'view/(?P[1-9]\d*)/' => 'NuanceQueueViewController', - 'new/' => 'NuanceQueueEditController', ), 'requestor/' => array( 'view/(?P[1-9]\d*)/' => 'NuanceRequestorViewController', diff --git a/src/applications/nuance/controller/NuanceItemViewController.php b/src/applications/nuance/controller/NuanceItemViewController.php index d325afbd29..5f50ea1aaf 100644 --- a/src/applications/nuance/controller/NuanceItemViewController.php +++ b/src/applications/nuance/controller/NuanceItemViewController.php @@ -49,7 +49,7 @@ final class NuanceItemViewController extends NuanceController { phabricator_datetime($item->getDateCreated(), $viewer)); $source = $item->getSource(); - $definition = $source->requireDefinition(); + $definition = $source->getDefinition(); $definition->renderItemViewProperties( $viewer, diff --git a/src/applications/nuance/controller/NuanceQueueListController.php b/src/applications/nuance/controller/NuanceQueueListController.php index d2372dbe3a..bf104da2a8 100644 --- a/src/applications/nuance/controller/NuanceQueueListController.php +++ b/src/applications/nuance/controller/NuanceQueueListController.php @@ -12,16 +12,9 @@ final class NuanceQueueListController protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - // TODO: Maybe use SourceManage capability? - $can_create = true; - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create Queue')) - ->setHref($this->getApplicationURI('queue/new/')) - ->setIcon('fa-plus-square') - ->setDisabled(!$can_create) - ->setWorkflow(!$can_create)); + id(new NuanceQueueEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } diff --git a/src/applications/nuance/controller/NuanceSourceActionController.php b/src/applications/nuance/controller/NuanceSourceActionController.php index 06739cea1e..a9fb41bccd 100644 --- a/src/applications/nuance/controller/NuanceSourceActionController.php +++ b/src/applications/nuance/controller/NuanceSourceActionController.php @@ -13,8 +13,11 @@ final class NuanceSourceActionController extends NuanceController { return new Aphront404Response(); } - $def = $source->requireDefinition(); - $def->setActor($viewer); + $def = $source->getDefinition(); + + $def + ->setViewer($viewer) + ->setSource($source); $response = $def->handleActionRequest($request); if ($response instanceof AphrontResponse) { @@ -25,14 +28,10 @@ final class NuanceSourceActionController extends NuanceController { $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); - return $this->buildApplicationPage( - array( - $crumbs, - $response, - ), - array( - 'title' => $title, - )); + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($response); } } diff --git a/src/applications/nuance/controller/NuanceSourceCreateController.php b/src/applications/nuance/controller/NuanceSourceCreateController.php deleted file mode 100644 index fb02fe6bbd..0000000000 --- a/src/applications/nuance/controller/NuanceSourceCreateController.php +++ /dev/null @@ -1,58 +0,0 @@ -requireApplicationCapability( - NuanceSourceManageCapability::CAPABILITY); - - $viewer = $this->getViewer(); - $map = NuanceSourceDefinition::getAllDefinitions(); - $cancel_uri = $this->getApplicationURI('source/'); - - if ($request->isFormPost()) { - $type = $request->getStr('type'); - if (isset($map[$type])) { - $uri = $this->getApplicationURI('source/new/'.$type.'/'); - return id(new AphrontRedirectResponse())->setURI($uri); - } - } - - $source_types = id(new AphrontFormRadioButtonControl()) - ->setName('type') - ->setLabel(pht('Source Type')); - - foreach ($map as $type => $definition) { - $source_types->addButton( - $type, - $definition->getName(), - $definition->getSourceDescription()); - } - - $form = id(new AphrontFormView()) - ->setUser($viewer) - ->appendChild($source_types) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Continue')) - ->addCancelButton($cancel_uri)); - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Choose Source Type')) - ->appendChild($form); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Sources'), $cancel_uri); - $crumbs->addTextCrumb(pht('New')); - - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => pht('Choose Source Type'), - )); - } -} diff --git a/src/applications/nuance/controller/NuanceSourceEditController.php b/src/applications/nuance/controller/NuanceSourceEditController.php index b4dff7fc48..76025b2eb9 100644 --- a/src/applications/nuance/controller/NuanceSourceEditController.php +++ b/src/applications/nuance/controller/NuanceSourceEditController.php @@ -4,70 +4,73 @@ final class NuanceSourceEditController extends NuanceSourceController { public function handleRequest(AphrontRequest $request) { - $can_edit = $this->requireApplicationCapability( - NuanceSourceManageCapability::CAPABILITY); + $engine = id(new NuanceSourceEditEngine()) + ->setController($this); - $viewer = $this->getViewer(); + $id = $request->getURIData('id'); + if (!$id) { + $this->requireApplicationCapability( + NuanceSourceManageCapability::CAPABILITY); - $sources_uri = $this->getApplicationURI('source/'); - - $source_id = $request->getURIData('id'); - $is_new = !$source_id; - - if ($is_new) { - $source = NuanceSource::initializeNewSource($viewer); - - $type = $request->getURIData('type'); + $cancel_uri = $this->getApplicationURI('source/'); $map = NuanceSourceDefinition::getAllDefinitions(); - - if (empty($map[$type])) { - return new Aphront404Response(); + $source_type = $request->getStr('sourceType'); + if (!isset($map[$source_type])) { + return $this->buildSourceTypeResponse($cancel_uri); } - $source->setType($type); - $cancel_uri = $sources_uri; - } else { - $source = id(new NuanceSourceQuery()) - ->setViewer($viewer) - ->withIDs(array($source_id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$source) { - return new Aphront404Response(); - } - $cancel_uri = $source->getURI(); + $engine + ->setSourceDefinition($map[$source_type]) + ->addContextParameter('sourceType', $source_type); } - $definition = $source->requireDefinition(); - $definition->setActor($viewer); + return $engine->buildResponse(); + } - $response = $definition->buildEditLayout($request); - if ($response instanceof AphrontResponse) { - return $response; + private function buildSourceTypeResponse($cancel_uri) { + $viewer = $this->getViewer(); + $request = $this->getRequest(); + $map = NuanceSourceDefinition::getAllDefinitions(); + + $errors = array(); + $e_source = null; + if ($request->isFormPost()) { + $errors[] = pht('You must choose a source type.'); + $e_source = pht('Required'); } - $layout = $response; + + $source_types = id(new AphrontFormRadioButtonControl()) + ->setName('sourceType') + ->setLabel(pht('Source Type')); + + foreach ($map as $type => $definition) { + $source_types->addButton( + $type, + $definition->getName(), + $definition->getSourceDescription()); + } + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild($source_types) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Continue')) + ->addCancelButton($cancel_uri)); + + $box = id(new PHUIObjectBoxView()) + ->setFormErrors($errors) + ->setHeaderText(pht('Choose Source Type')) + ->appendChild($form); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Sources'), $sources_uri); + $crumbs->addTextCrumb(pht('Sources'), $cancel_uri); + $crumbs->addTextCrumb(pht('New')); - if ($is_new) { - $crumbs->addTextCrumb(pht('New')); - } else { - $crumbs->addTextCrumb($source->getName(), $cancel_uri); - $crumbs->addTextCrumb(pht('Edit')); - } - - return $this->buildApplicationPage( - array( - $crumbs, - $layout, - ), - array( - 'title' => $definition->getEditTitle(), - )); + return $this->newPage() + ->setTitle(pht('Choose Source Type')) + ->setCrumbs($crumbs) + ->appendChild($box); } + } diff --git a/src/applications/nuance/controller/NuanceSourceListController.php b/src/applications/nuance/controller/NuanceSourceListController.php index f18ed66706..a38c992d9a 100644 --- a/src/applications/nuance/controller/NuanceSourceListController.php +++ b/src/applications/nuance/controller/NuanceSourceListController.php @@ -12,16 +12,9 @@ final class NuanceSourceListController protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $can_create = $this->hasApplicationCapability( - NuanceSourceManageCapability::CAPABILITY); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create Source')) - ->setHref($this->getApplicationURI('source/create/')) - ->setIcon('fa-plus-square') - ->setDisabled(!$can_create) - ->setWorkflow(!$can_create)); + id(new NuanceSourceEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } diff --git a/src/applications/nuance/controller/NuanceSourceViewController.php b/src/applications/nuance/controller/NuanceSourceViewController.php index babd4e88f8..af602bfd7e 100644 --- a/src/applications/nuance/controller/NuanceSourceViewController.php +++ b/src/applications/nuance/controller/NuanceSourceViewController.php @@ -84,7 +84,12 @@ final class NuanceSourceViewController ->setWorkflow(!$can_edit)); $request = $this->getRequest(); - $definition = $source->requireDefinition(); + $definition = $source->getDefinition(); + + $definition + ->setViewer($viewer) + ->setSource($source); + $source_actions = $definition->getSourceViewActions($request); foreach ($source_actions as $source_action) { $curtain->addAction($source_action); @@ -100,7 +105,8 @@ final class NuanceSourceViewController $properties = id(new PHUIPropertyListView()) ->setViewer($viewer); - $definition = $source->requireDefinition(); + $definition = $source->getDefinition(); + $properties->addProperty( pht('Source Type'), $definition->getName()); diff --git a/src/applications/nuance/editor/NuanceSourceEditEngine.php b/src/applications/nuance/editor/NuanceSourceEditEngine.php new file mode 100644 index 0000000000..a01a21c20b --- /dev/null +++ b/src/applications/nuance/editor/NuanceSourceEditEngine.php @@ -0,0 +1,108 @@ +sourceDefinition = $source_definition; + return $this; + } + + public function getSourceDefinition() { + return $this->sourceDefinition; + } + + public function isEngineConfigurable() { + return false; + } + + public function getEngineName() { + return pht('Nuance Sources'); + } + + public function getSummaryHeader() { + return pht('Edit Nuance Source Configurations'); + } + + public function getSummaryText() { + return pht('This engine is used to edit Nuance sources.'); + } + + public function getEngineApplicationClass() { + return 'PhabricatorNuanceApplication'; + } + + protected function newEditableObject() { + $viewer = $this->getViewer(); + + $definition = $this->getSourceDefinition(); + if (!$definition) { + throw new PhutilInvalidStateException('setSourceDefinition'); + } + + return NuanceSource::initializeNewSource( + $viewer, + $definition); + } + + protected function newObjectQuery() { + return new NuanceSourceQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Source'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Source'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Source: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Source'); + } + + protected function getObjectCreateShortText() { + return pht('Create Source'); + } + + protected function getEditorURI() { + return '/nuance/source/edit/'; + } + + protected function getObjectCreateCancelURI($object) { + return '/nuance/source/'; + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the source.')) + ->setTransactionType(NuanceSourceTransaction::TYPE_NAME) + ->setIsRequired(true) + ->setValue($object->getName()), + id(new PhabricatorDatasourceEditField()) + ->setKey('defaultQueue') + ->setLabel(pht('Default Queue')) + ->setDescription(pht('Default queue.')) + ->setTransactionType(NuanceSourceTransaction::TYPE_DEFAULT_QUEUE) + ->setDatasource(new NuanceQueueDatasource()) + ->setSingleValue($object->getDefaultQueuePHID()), + ); + } + +} diff --git a/src/applications/nuance/query/NuanceSourceQuery.php b/src/applications/nuance/query/NuanceSourceQuery.php index 6fbc4d3ddf..32a5212d1f 100644 --- a/src/applications/nuance/query/NuanceSourceQuery.php +++ b/src/applications/nuance/query/NuanceSourceQuery.php @@ -30,6 +30,23 @@ final class NuanceSourceQuery return $this->loadStandardPage($this->newResultObject()); } + protected function willFilterPage(array $sources) { + $all_types = NuanceSourceDefinition::getAllDefinitions(); + + foreach ($sources as $key => $source) { + $definition = idx($all_types, $source->getType()); + if (!$definition) { + $this->didRejectResult($source); + unset($sources[$key]); + continue; + } + $source->attachDefinition($definition); + } + + return $sources; + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); diff --git a/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php b/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php index cca5a19cd9..9d8644e82a 100644 --- a/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php +++ b/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php @@ -30,25 +30,6 @@ final class NuancePhabricatorFormSourceDefinition return null; } - protected function augmentEditForm( - AphrontFormView $form, - PhabricatorApplicationTransactionValidationException $ex = null) { - - /* TODO - add a box to allow for custom fields to be defined here, so that - * these NuanceSource objects made from this definition can be used to - * capture arbitrary data */ - - return $form; - } - - protected function buildTransactions(AphrontRequest $request) { - $transactions = parent::buildTransactions($request); - - // TODO -- as above - - return $transactions; - } - public function renderView() {} public function renderListView() {} diff --git a/src/applications/nuance/source/NuanceSourceDefinition.php b/src/applications/nuance/source/NuanceSourceDefinition.php index 4b37e39845..10a612aa00 100644 --- a/src/applications/nuance/source/NuanceSourceDefinition.php +++ b/src/applications/nuance/source/NuanceSourceDefinition.php @@ -5,42 +5,31 @@ */ abstract class NuanceSourceDefinition extends Phobject { - private $actor; - private $sourceObject; + private $viewer; + private $source; - public function setActor(PhabricatorUser $actor) { - $this->actor = $actor; + public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; return $this; } - public function getActor() { - return $this->actor; - } - - public function requireActor() { - $actor = $this->getActor(); - if (!$actor) { - throw new PhutilInvalidStateException('setActor'); + public function getViewer() { + if (!$this->viewer) { + throw new PhutilInvalidStateException('setViewer'); } - return $actor; + return $this->viewer; } - public function setSourceObject(NuanceSource $source) { - $source->setType($this->getSourceTypeConstant()); - $this->sourceObject = $source; + public function setSource(NuanceSource $source) { + $this->source = $source; return $this; } - public function getSourceObject() { - return $this->sourceObject; - } - - public function requireSourceObject() { - $source = $this->getSourceObject(); - if (!$source) { - throw new PhutilInvalidStateException('setSourceObject'); + public function getSource() { + if (!$this->source) { + throw new PhutilInvalidStateException('setSource'); } - return $source; + return $this->source; } public function getSourceViewActions(AphrontRequest $request) { @@ -84,165 +73,6 @@ abstract class NuanceSourceDefinition extends Phobject { */ abstract public function updateItems(); - private function loadSourceObjectPolicies( - PhabricatorUser $user, - NuanceSource $source) { - - $user = $this->requireActor(); - $source = $this->requireSourceObject(); - return id(new PhabricatorPolicyQuery()) - ->setViewer($user) - ->setObject($source) - ->execute(); - } - - final public function getEditTitle() { - $source = $this->requireSourceObject(); - if ($source->getPHID()) { - $title = pht('Edit "%s" source.', $source->getName()); - } else { - $title = pht('Create a new "%s" source.', $this->getName()); - } - - return $title; - } - - final public function buildEditLayout(AphrontRequest $request) { - $actor = $this->requireActor(); - $source = $this->requireSourceObject(); - - $form_errors = array(); - $error_messages = array(); - $transactions = array(); - $validation_exception = null; - if ($request->isFormPost()) { - $transactions = $this->buildTransactions($request); - try { - $editor = id(new NuanceSourceEditor()) - ->setActor($actor) - ->setContentSourceFromRequest($request) - ->setContinueOnNoEffect(true) - ->applyTransactions($source, $transactions); - - return id(new AphrontRedirectResponse()) - ->setURI($source->getURI()); - - } catch (PhabricatorApplicationTransactionValidationException $ex) { - $validation_exception = $ex; - } - - } - - $form = $this->renderEditForm($validation_exception); - $layout = id(new PHUIObjectBoxView()) - ->setHeaderText($this->getEditTitle()) - ->setValidationException($validation_exception) - ->setFormErrors($error_messages) - ->setForm($form); - - return $layout; - } - - /** - * Code to create a form to edit the @{class:NuanceItem} you are defining. - * - * return @{class:AphrontFormView} - */ - private function renderEditForm( - PhabricatorApplicationTransactionValidationException $ex = null) { - $user = $this->requireActor(); - $source = $this->requireSourceObject(); - $policies = $this->loadSourceObjectPolicies($user, $source); - $e_name = null; - if ($ex) { - $e_name = $ex->getShortMessage(NuanceSourceTransaction::TYPE_NAME); - } - - $form = id(new AphrontFormView()) - ->setUser($user) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Name')) - ->setName('name') - ->setError($e_name) - ->setValue($source->getName())); - - $form = $this->augmentEditForm($form, $ex); - - $default_phid = $source->getDefaultQueuePHID(); - if ($default_phid) { - $default_queues = array($default_phid); - } else { - $default_queues = array(); - } - - $form - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Default Queue')) - ->setName('defaultQueuePHIDs') - ->setLimit(1) - ->setDatasource(new NuanceQueueDatasource()) - ->setValue($default_queues)) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($user) - ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) - ->setPolicyObject($source) - ->setPolicies($policies) - ->setName('viewPolicy')) - ->appendChild( - id(new AphrontFormPolicyControl()) - ->setUser($user) - ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) - ->setPolicyObject($source) - ->setPolicies($policies) - ->setName('editPolicy')) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton($source->getURI()) - ->setValue(pht('Save'))); - - return $form; - } - - /** - * return @{class:AphrontFormView} - */ - protected function augmentEditForm( - AphrontFormView $form, - PhabricatorApplicationTransactionValidationException $ex = null) { - - return $form; - } - - /** - * Hook to build up @{class:PhabricatorTransactions}. - * - * return array $transactions - */ - protected function buildTransactions(AphrontRequest $request) { - $transactions = array(); - - $transactions[] = id(new NuanceSourceTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) - ->setNewValue($request->getStr('editPolicy')); - - $transactions[] = id(new NuanceSourceTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) - ->setNewValue($request->getStr('viewPolicy')); - - $transactions[] = id(new NuanceSourceTransaction()) - ->setTransactionType(NuanceSourceTransaction::TYPE_NAME) - ->setNewvalue($request->getStr('name')); - - $transactions[] = id(new NuanceSourceTransaction()) - ->setTransactionType(NuanceSourceTransaction::TYPE_DEFAULT_QUEUE) - ->setNewvalue(head($request->getArr('defaultQueuePHIDs'))); - - return $transactions; - } - abstract public function renderView(); abstract public function renderListView(); @@ -256,8 +86,7 @@ abstract class NuanceSourceDefinition extends Phobject { // TODO: Should we have a tighter actor/viewer model? Requestors will // often have no real user associated with them... $actor = PhabricatorUser::getOmnipotentUser(); - - $source = $this->requireSourceObject(); + $source = $this->getSource(); $item = NuanceItem::initializeNewItem(); @@ -317,7 +146,7 @@ abstract class NuanceSourceDefinition extends Phobject { } public function getActionURI($path = null) { - $source_id = $this->getSourceObject()->getID(); + $source_id = $this->getSource()->getID(); return '/action/'.$source_id.'/'.ltrim($path, '/'); } diff --git a/src/applications/nuance/storage/NuanceSource.php b/src/applications/nuance/storage/NuanceSource.php index 4ce61289f9..dd45e241c0 100644 --- a/src/applications/nuance/storage/NuanceSource.php +++ b/src/applications/nuance/storage/NuanceSource.php @@ -13,7 +13,7 @@ final class NuanceSource extends NuanceDAO protected $editPolicy; protected $defaultQueuePHID; - private $definition; + private $definition = self::ATTACHABLE; protected function getConfiguration() { return array( @@ -49,7 +49,9 @@ final class NuanceSource extends NuanceDAO return '/nuance/source/view/'.$this->getID().'/'; } - public static function initializeNewSource(PhabricatorUser $actor) { + public static function initializeNewSource( + PhabricatorUser $actor, + NuanceSourceDefinition $definition) { $app = id(new PhabricatorApplicationQuery()) ->setViewer($actor) ->withClasses(array('PhabricatorNuanceApplication')) @@ -62,32 +64,18 @@ final class NuanceSource extends NuanceDAO return id(new NuanceSource()) ->setViewPolicy($view_policy) - ->setEditPolicy($edit_policy); + ->setEditPolicy($edit_policy) + ->setType($definition->getSourceTypeConstant()) + ->attachDefinition($definition); } public function getDefinition() { - if ($this->definition === null) { - $definitions = NuanceSourceDefinition::getAllDefinitions(); - if (isset($definitions[$this->getType()])) { - $definition = clone $definitions[$this->getType()]; - $definition->setSourceObject($this); - $this->definition = $definition; - } - } - - return $this->definition; + return $this->assertAttached($this->definition); } - public function requireDefinition() { - $definition = $this->getDefinition(); - if (!$definition) { - throw new Exception( - pht( - 'Unable to load source definition implementation for source '. - 'type "%s".', - $this->getType())); - } - return $definition; + public function attachDefinition(NuanceSourceDefinition $definition) { + $this->definition = $definition; + return $this; } From 3f4cc3ad6eb4628ad5bdf5505f04ef1134ef1089 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 8 Mar 2016 05:59:24 -0800 Subject: [PATCH 30/57] Allow Nuances sources to provide import cursors Summary: Ref T10537. Some sources (like the future "GitHub Repository" source) need to poll remotes. - Provide a mechanism for sources to emit import cursors. - Hook them into the trigger daemon so they'll fire periodically. - Provide some storage. This diff does nothing useful or interesting, and is pure infrastructure. Test Plan: - Ran `bin/storage upgrade -f`, no adjustment issues. - Poked around Nuance. - Ran the trigger daemon, verified it didn't crash and checked for Nuance stuff to do. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10537 Differential Revision: https://secure.phabricator.com/D15435 --- .../20160308.nuance.01.disabled.sql | 2 + .../20160308.nuance.02.cursordata.sql | 12 ++++ src/__phutil_library_map__.php | 8 +++ .../nuance/cursor/NuanceImportCursor.php | 10 +++ .../phid/NuanceImportCursorPHIDType.php | 38 ++++++++++ .../query/NuanceImportCursorDataQuery.php | 60 ++++++++++++++++ .../nuance/query/NuanceSourceQuery.php | 50 +++++++++++++ .../nuance/source/NuanceSourceDefinition.php | 17 +++++ .../nuance/storage/NuanceImportCursorData.php | 35 +++++++++ .../nuance/storage/NuanceSource.php | 5 +- .../workers/PhabricatorTriggerDaemon.php | 71 +++++++++++++++++++ 11 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 resources/sql/autopatches/20160308.nuance.01.disabled.sql create mode 100644 resources/sql/autopatches/20160308.nuance.02.cursordata.sql create mode 100644 src/applications/nuance/cursor/NuanceImportCursor.php create mode 100644 src/applications/nuance/phid/NuanceImportCursorPHIDType.php create mode 100644 src/applications/nuance/query/NuanceImportCursorDataQuery.php create mode 100644 src/applications/nuance/storage/NuanceImportCursorData.php diff --git a/resources/sql/autopatches/20160308.nuance.01.disabled.sql b/resources/sql/autopatches/20160308.nuance.01.disabled.sql new file mode 100644 index 0000000000..f9a6d11320 --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.01.disabled.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_source + ADD isDisabled BOOL NOT NULL; diff --git a/resources/sql/autopatches/20160308.nuance.02.cursordata.sql b/resources/sql/autopatches/20160308.nuance.02.cursordata.sql new file mode 100644 index 0000000000..a3ac917f0c --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.02.cursordata.sql @@ -0,0 +1,12 @@ +CREATE TABLE {$NAMESPACE}_nuance.nuance_importcursordata ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + sourcePHID VARBINARY(64) NOT NULL, + cursorKey VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, + cursorType VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, + properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (phid), + UNIQUE KEY `key_source` (sourcePHID, cursorKey) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 8c7071c673..2a49fd0979 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1421,6 +1421,10 @@ phutil_register_library_map(array( 'NuanceController' => 'applications/nuance/controller/NuanceController.php', 'NuanceCreateItemConduitAPIMethod' => 'applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php', 'NuanceDAO' => 'applications/nuance/storage/NuanceDAO.php', + 'NuanceImportCursor' => 'applications/nuance/cursor/NuanceImportCursor.php', + 'NuanceImportCursorData' => 'applications/nuance/storage/NuanceImportCursorData.php', + 'NuanceImportCursorDataQuery' => 'applications/nuance/query/NuanceImportCursorDataQuery.php', + 'NuanceImportCursorPHIDType' => 'applications/nuance/phid/NuanceImportCursorPHIDType.php', 'NuanceItem' => 'applications/nuance/storage/NuanceItem.php', 'NuanceItemEditController' => 'applications/nuance/controller/NuanceItemEditController.php', 'NuanceItemEditor' => 'applications/nuance/editor/NuanceItemEditor.php', @@ -5661,6 +5665,10 @@ phutil_register_library_map(array( 'NuanceController' => 'PhabricatorController', 'NuanceCreateItemConduitAPIMethod' => 'NuanceConduitAPIMethod', 'NuanceDAO' => 'PhabricatorLiskDAO', + 'NuanceImportCursor' => 'Phobject', + 'NuanceImportCursorData' => 'NuanceDAO', + 'NuanceImportCursorDataQuery' => 'NuanceQuery', + 'NuanceImportCursorPHIDType' => 'PhabricatorPHIDType', 'NuanceItem' => array( 'NuanceDAO', 'PhabricatorPolicyInterface', diff --git a/src/applications/nuance/cursor/NuanceImportCursor.php b/src/applications/nuance/cursor/NuanceImportCursor.php new file mode 100644 index 0000000000..0769f7feee --- /dev/null +++ b/src/applications/nuance/cursor/NuanceImportCursor.php @@ -0,0 +1,10 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + $viewer = $query->getViewer(); + foreach ($handles as $phid => $handle) { + $item = $objects[$phid]; + } + } + +} diff --git a/src/applications/nuance/query/NuanceImportCursorDataQuery.php b/src/applications/nuance/query/NuanceImportCursorDataQuery.php new file mode 100644 index 0000000000..ae451abfb9 --- /dev/null +++ b/src/applications/nuance/query/NuanceImportCursorDataQuery.php @@ -0,0 +1,60 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withSourcePHIDs(array $source_phids) { + $this->sourcePHIDs = $source_phids; + return $this; + } + + public function newResultObject() { + return new NuanceImportCursorData(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->sourcePHIDs !== null) { + $where[] = qsprintf( + $conn, + 'sourcePHID IN (%Ls)', + $this->sourcePHIDs); + } + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'phid IN (%Ls)', + $this->phids); + } + + return $where; + } + +} diff --git a/src/applications/nuance/query/NuanceSourceQuery.php b/src/applications/nuance/query/NuanceSourceQuery.php index 32a5212d1f..7bde9440e2 100644 --- a/src/applications/nuance/query/NuanceSourceQuery.php +++ b/src/applications/nuance/query/NuanceSourceQuery.php @@ -6,6 +6,8 @@ final class NuanceSourceQuery private $ids; private $phids; private $types; + private $isDisabled; + private $hasCursors; public function withIDs(array $ids) { $this->ids = $ids; @@ -22,6 +24,16 @@ final class NuanceSourceQuery return $this; } + public function withIsDisabled($disabled) { + $this->isDisabled = $disabled; + return $this; + } + + public function withHasImportCursors($has_cursors) { + $this->hasCursors = $has_cursors; + return $this; + } + public function newResultObject() { return new NuanceSource(); } @@ -71,6 +83,44 @@ final class NuanceSourceQuery $this->phids); } + if ($this->isDisabled !== null) { + $where[] = qsprintf( + $conn, + 'isDisabled = %d', + (int)$this->isDisabled); + } + + if ($this->hasCursors !== null) { + $cursor_types = array(); + + $definitions = NuanceSourceDefinition::getAllDefinitions(); + foreach ($definitions as $key => $definition) { + if ($definition->hasImportCursors()) { + $cursor_types[] = $key; + } + } + + if ($this->hasCursors) { + if (!$cursor_types) { + throw new PhabricatorEmptyQueryException(); + } else { + $where[] = qsprintf( + $conn, + 'type IN (%Ls)', + $cursor_types); + } + } else { + if (!$cursor_types) { + // Apply no constraint. + } else { + $where[] = qsprintf( + $conn, + 'type NOT IN (%Ls)', + $cursor_types); + } + } + } + return $where; } diff --git a/src/applications/nuance/source/NuanceSourceDefinition.php b/src/applications/nuance/source/NuanceSourceDefinition.php index 10a612aa00..103ac8214b 100644 --- a/src/applications/nuance/source/NuanceSourceDefinition.php +++ b/src/applications/nuance/source/NuanceSourceDefinition.php @@ -43,6 +43,23 @@ abstract class NuanceSourceDefinition extends Phobject { ->execute(); } + public function hasImportCursors() { + return false; + } + + final public function getImportCursors() { + if (!$this->hasImportCursors()) { + throw new Exception( + pht('This source has no input cursors.')); + } + + return $this->newImportCursors(); + } + + protected function newImportCursors() { + throw new PhutilMethodNotImplementedException(); + } + /** * A human readable string like "Twitter" or "Phabricator Form". */ diff --git a/src/applications/nuance/storage/NuanceImportCursorData.php b/src/applications/nuance/storage/NuanceImportCursorData.php new file mode 100644 index 0000000000..8b0bae5c63 --- /dev/null +++ b/src/applications/nuance/storage/NuanceImportCursorData.php @@ -0,0 +1,35 @@ + true, + self::CONFIG_SERIALIZATION => array( + 'properties' => self::SERIALIZATION_JSON, + ), + self::CONFIG_COLUMN_SCHEMA => array( + 'cursorType' => 'text32', + 'cursorKey' => 'text32', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_source' => array( + 'columns' => array('sourcePHID', 'cursorKey'), + 'unique' => true, + ), + ), + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + NuanceImportCursorPHIDType::TYPECONST); + } + +} diff --git a/src/applications/nuance/storage/NuanceSource.php b/src/applications/nuance/storage/NuanceSource.php index dd45e241c0..52da800e4f 100644 --- a/src/applications/nuance/storage/NuanceSource.php +++ b/src/applications/nuance/storage/NuanceSource.php @@ -12,6 +12,7 @@ final class NuanceSource extends NuanceDAO protected $viewPolicy; protected $editPolicy; protected $defaultQueuePHID; + protected $isDisabled; private $definition = self::ATTACHABLE; @@ -25,6 +26,7 @@ final class NuanceSource extends NuanceDAO 'name' => 'text255?', 'type' => 'text32', 'mailKey' => 'bytes20', + 'isDisabled' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( 'key_type' => array( @@ -66,7 +68,8 @@ final class NuanceSource extends NuanceDAO ->setViewPolicy($view_policy) ->setEditPolicy($edit_policy) ->setType($definition->getSourceTypeConstant()) - ->attachDefinition($definition); + ->attachDefinition($definition) + ->setIsDisabled(0); } public function getDefinition() { diff --git a/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php b/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php index 87955fe595..213449be10 100644 --- a/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php +++ b/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php @@ -16,6 +16,10 @@ final class PhabricatorTriggerDaemon private $garbageCollectors; private $nextCollection; + private $anyNuanceData; + private $nuanceSources; + private $nuanceCursors; + protected function run() { // The trigger daemon is a low-level infrastructure daemon which schedules @@ -99,6 +103,7 @@ final class PhabricatorTriggerDaemon $lock->unlock(); $sleep_duration = $this->getSleepDuration(); + $sleep_duration = $this->runNuanceImportCursors($sleep_duration); $sleep_duration = $this->runGarbageCollection($sleep_duration); $this->sleep($sleep_duration); } while (!$this->shouldExit()); @@ -379,4 +384,70 @@ final class PhabricatorTriggerDaemon return false; } + +/* -( Nuance Importers )--------------------------------------------------- */ + + + private function runNuanceImportCursors($duration) { + $run_until = (PhabricatorTime::getNow() + $duration); + + do { + $more_data = $this->updateNuanceImportCursors(); + if (!$more_data) { + break; + } + } while (PhabricatorTime::getNow() <= $run_until); + + $remaining = max(0, $run_until - PhabricatorTime::getNow()); + + return $remaining; + } + + + private function updateNuanceImportCursors() { + $nuance_app = 'PhabricatorNuanceApplication'; + if (!PhabricatorApplication::isClassInstalled($nuance_app)) { + return false; + } + + // If we haven't loaded sources yet, load them first. + if (!$this->nuanceSources) { + $this->anyNuanceData = false; + + $sources = id(new NuanceSourceQuery()) + ->setViewer($this->getViewer()) + ->withIsDisabled(false) + ->withHasImportCursors(true) + ->execute(); + if (!$sources) { + return false; + } + + $this->nuanceSources = array_reverse($sources); + } + + // If we don't have any cursors, move to the next source and generate its + // cursors. + if (!$this->nuanceCursors) { + $source = array_pop($this->nuanceSources); + $cursors = $source->getImportCursors(); + $this->nuanceCursors = array_reverse($cursors); + } + + // Update the next cursor. + $cursor = array_pop($this->nuanceCursors); + if ($cursor) { + $more_data = $cursor->importFromSource(); + if ($more_data) { + $this->anyNuanceData = true; + } + } + + if (!$this->nuanceSources && !$this->nuanceCursors) { + return $this->anyNuanceData; + } + + return true; + } + } From 2a3c3b2b98fe922969ebad5ac6521397259cd655 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 8 Mar 2016 06:27:26 -0800 Subject: [PATCH 31/57] Provide `bin/nuance import` and ngram indexes for sources Summary: Ref T10537. More infrastructure: - Put a `bin/nuance` in place with `bin/nuance import`. This has no useful behavior yet. - Allow sources to be searched by substring. This supports `bin/nuance import --source whatever` so you don't have to dig up PHIDs. Test Plan: - Applied migrations. - Ran `bin/nuance import --source ...` (no meaningful effect, but works fine). - Searched for sources by substring in the UI. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10537 Differential Revision: https://secure.phabricator.com/D15436 --- bin/nuance | 1 + .../20160308.nuance.03.sourcen.sql | 7 ++ .../20160308.nuance.04.sourcei.php | 11 +++ .../20160308.nuance.05.sourcename.sql | 2 + scripts/setup/manage_nuance.php | 21 ++++++ src/__phutil_library_map__.php | 7 ++ .../almanac/storage/AlmanacDevice.php | 2 +- .../almanac/storage/AlmanacNamespace.php | 2 +- .../almanac/storage/AlmanacNetwork.php | 2 +- .../almanac/storage/AlmanacService.php | 2 +- .../drydock/storage/DrydockBlueprint.php | 2 +- .../configuration/HarbormasterBuildPlan.php | 2 +- .../nuance/editor/NuanceSourceEditor.php | 4 ++ .../NuanceManagementImportWorkflow.php | 50 ++++++++++++++ .../management/NuanceManagementWorkflow.php | 67 +++++++++++++++++++ .../nuance/query/NuanceSourceQuery.php | 22 ++++-- .../nuance/query/NuanceSourceSearchEngine.php | 11 ++- .../nuance/storage/NuanceSource.php | 16 ++++- .../nuance/storage/NuanceSourceNameNgrams.php | 18 +++++ .../storage/PhabricatorOwnersPackage.php | 2 +- .../workers/PhabricatorTriggerDaemon.php | 7 +- 21 files changed, 241 insertions(+), 17 deletions(-) create mode 120000 bin/nuance create mode 100644 resources/sql/autopatches/20160308.nuance.03.sourcen.sql create mode 100644 resources/sql/autopatches/20160308.nuance.04.sourcei.php create mode 100644 resources/sql/autopatches/20160308.nuance.05.sourcename.sql create mode 100755 scripts/setup/manage_nuance.php create mode 100644 src/applications/nuance/management/NuanceManagementImportWorkflow.php create mode 100644 src/applications/nuance/management/NuanceManagementWorkflow.php create mode 100644 src/applications/nuance/storage/NuanceSourceNameNgrams.php diff --git a/bin/nuance b/bin/nuance new file mode 120000 index 0000000000..c2cf50a211 --- /dev/null +++ b/bin/nuance @@ -0,0 +1 @@ +../scripts/setup/manage_nuance.php \ No newline at end of file diff --git a/resources/sql/autopatches/20160308.nuance.03.sourcen.sql b/resources/sql/autopatches/20160308.nuance.03.sourcen.sql new file mode 100644 index 0000000000..42ec4b87eb --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.03.sourcen.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_nuance.nuance_sourcename_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/20160308.nuance.04.sourcei.php b/resources/sql/autopatches/20160308.nuance.04.sourcei.php new file mode 100644 index 0000000000..eb0d1da113 --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.04.sourcei.php @@ -0,0 +1,11 @@ +getPHID(), + array( + 'force' => true, + )); +} diff --git a/resources/sql/autopatches/20160308.nuance.05.sourcename.sql b/resources/sql/autopatches/20160308.nuance.05.sourcename.sql new file mode 100644 index 0000000000..a2b70c683f --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.05.sourcename.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_source + CHANGE name name VARCHAR(255) NOT NULL COLLATE {$COLLATE_SORT}; diff --git a/scripts/setup/manage_nuance.php b/scripts/setup/manage_nuance.php new file mode 100755 index 0000000000..ebf312305a --- /dev/null +++ b/scripts/setup/manage_nuance.php @@ -0,0 +1,21 @@ +#!/usr/bin/env php +setTagline(pht('manage Nuance')); +$args->setSynopsis(<<parseStandardArguments(); + +$workflows = id(new PhutilClassMapQuery()) + ->setAncestorClass('NuanceManagementWorkflow') + ->execute(); +$workflows[] = new PhutilHelpArgumentWorkflow(); +$args->parseWorkflows($workflows); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 2a49fd0979..7e49233e5f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1434,6 +1434,8 @@ phutil_register_library_map(array( 'NuanceItemTransactionComment' => 'applications/nuance/storage/NuanceItemTransactionComment.php', 'NuanceItemTransactionQuery' => 'applications/nuance/query/NuanceItemTransactionQuery.php', 'NuanceItemViewController' => 'applications/nuance/controller/NuanceItemViewController.php', + 'NuanceManagementImportWorkflow' => 'applications/nuance/management/NuanceManagementImportWorkflow.php', + 'NuanceManagementWorkflow' => 'applications/nuance/management/NuanceManagementWorkflow.php', 'NuancePhabricatorFormSourceDefinition' => 'applications/nuance/source/NuancePhabricatorFormSourceDefinition.php', 'NuanceQuery' => 'applications/nuance/query/NuanceQuery.php', 'NuanceQueue' => 'applications/nuance/storage/NuanceQueue.php', @@ -1473,6 +1475,7 @@ phutil_register_library_map(array( 'NuanceSourceEditor' => 'applications/nuance/editor/NuanceSourceEditor.php', 'NuanceSourceListController' => 'applications/nuance/controller/NuanceSourceListController.php', 'NuanceSourceManageCapability' => 'applications/nuance/capability/NuanceSourceManageCapability.php', + 'NuanceSourceNameNgrams' => 'applications/nuance/storage/NuanceSourceNameNgrams.php', 'NuanceSourcePHIDType' => 'applications/nuance/phid/NuanceSourcePHIDType.php', 'NuanceSourceQuery' => 'applications/nuance/query/NuanceSourceQuery.php', 'NuanceSourceSearchEngine' => 'applications/nuance/query/NuanceSourceSearchEngine.php', @@ -5682,6 +5685,8 @@ phutil_register_library_map(array( 'NuanceItemTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceItemTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'NuanceItemViewController' => 'NuanceController', + 'NuanceManagementImportWorkflow' => 'NuanceManagementWorkflow', + 'NuanceManagementWorkflow' => 'PhabricatorManagementWorkflow', 'NuancePhabricatorFormSourceDefinition' => 'NuanceSourceDefinition', 'NuanceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'NuanceQueue' => array( @@ -5721,6 +5726,7 @@ phutil_register_library_map(array( 'NuanceDAO', 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', + 'PhabricatorNgramsInterface', ), 'NuanceSourceActionController' => 'NuanceController', 'NuanceSourceController' => 'NuanceController', @@ -5733,6 +5739,7 @@ phutil_register_library_map(array( 'NuanceSourceEditor' => 'PhabricatorApplicationTransactionEditor', 'NuanceSourceListController' => 'NuanceSourceController', 'NuanceSourceManageCapability' => 'PhabricatorPolicyCapability', + 'NuanceSourceNameNgrams' => 'PhabricatorSearchNgrams', 'NuanceSourcePHIDType' => 'PhabricatorPHIDType', 'NuanceSourceQuery' => 'NuanceQuery', 'NuanceSourceSearchEngine' => 'PhabricatorApplicationSearchEngine', diff --git a/src/applications/almanac/storage/AlmanacDevice.php b/src/applications/almanac/storage/AlmanacDevice.php index f46b03600f..6c7f3cb57f 100644 --- a/src/applications/almanac/storage/AlmanacDevice.php +++ b/src/applications/almanac/storage/AlmanacDevice.php @@ -246,7 +246,7 @@ final class AlmanacDevice } -/* -( PhabricatorNgramInterface )------------------------------------------ */ +/* -( PhabricatorNgramsInterface )----------------------------------------- */ public function newNgrams() { diff --git a/src/applications/almanac/storage/AlmanacNamespace.php b/src/applications/almanac/storage/AlmanacNamespace.php index 6a3baca637..4bbb4d4090 100644 --- a/src/applications/almanac/storage/AlmanacNamespace.php +++ b/src/applications/almanac/storage/AlmanacNamespace.php @@ -210,7 +210,7 @@ final class AlmanacNamespace } -/* -( PhabricatorNgramInterface )------------------------------------------ */ +/* -( PhabricatorNgramsInterface )----------------------------------------- */ public function newNgrams() { diff --git a/src/applications/almanac/storage/AlmanacNetwork.php b/src/applications/almanac/storage/AlmanacNetwork.php index 064b612999..2f37ab4778 100644 --- a/src/applications/almanac/storage/AlmanacNetwork.php +++ b/src/applications/almanac/storage/AlmanacNetwork.php @@ -116,7 +116,7 @@ final class AlmanacNetwork } -/* -( PhabricatorNgramInterface )------------------------------------------ */ +/* -( PhabricatorNgramsInterface )----------------------------------------- */ public function newNgrams() { diff --git a/src/applications/almanac/storage/AlmanacService.php b/src/applications/almanac/storage/AlmanacService.php index b280de9ba8..37c3a44ba6 100644 --- a/src/applications/almanac/storage/AlmanacService.php +++ b/src/applications/almanac/storage/AlmanacService.php @@ -251,7 +251,7 @@ final class AlmanacService } -/* -( PhabricatorNgramInterface )------------------------------------------ */ +/* -( PhabricatorNgramsInterface )----------------------------------------- */ public function newNgrams() { diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php index a7deb73cc3..87ea777f72 100644 --- a/src/applications/drydock/storage/DrydockBlueprint.php +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -350,7 +350,7 @@ final class DrydockBlueprint extends DrydockDAO } -/* -( PhabricatorNgramInterface )------------------------------------------ */ +/* -( PhabricatorNgramsInterface )----------------------------------------- */ public function newNgrams() { diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php index 8d706657ef..2a101ca355 100644 --- a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php +++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php @@ -197,7 +197,7 @@ final class HarbormasterBuildPlan extends HarbormasterDAO } -/* -( PhabricatorNgramInterface )------------------------------------------ */ +/* -( PhabricatorNgramsInterface )----------------------------------------- */ public function newNgrams() { diff --git a/src/applications/nuance/editor/NuanceSourceEditor.php b/src/applications/nuance/editor/NuanceSourceEditor.php index 233b2ae163..5fbc02b962 100644 --- a/src/applications/nuance/editor/NuanceSourceEditor.php +++ b/src/applications/nuance/editor/NuanceSourceEditor.php @@ -11,6 +11,10 @@ final class NuanceSourceEditor return pht('Nuance Sources'); } + protected function supportsSearch() { + return true; + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); diff --git a/src/applications/nuance/management/NuanceManagementImportWorkflow.php b/src/applications/nuance/management/NuanceManagementImportWorkflow.php new file mode 100644 index 0000000000..c8050ec1fc --- /dev/null +++ b/src/applications/nuance/management/NuanceManagementImportWorkflow.php @@ -0,0 +1,50 @@ +setName('import') + ->setExamples('**import** [__options__]') + ->setSynopsis(pht('Import data from a source.')) + ->setArguments( + array( + array( + 'name' => 'source', + 'param' => 'source', + 'help' => pht('Choose which source to import.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $source = $this->loadSource($args, 'source'); + + $definition = $source->getDefinition() + ->setViewer($this->getViewer()) + ->setSource($source); + + if (!$definition->hasImportCursors()) { + throw new PhutilArgumentUsageException( + pht( + 'This source ("%s") does not expose import cursors.', + $source->getName())); + } + + $cursors = $definition->getImportCursors(); + if (!$cursors) { + throw new PhutilArgumentUsageException( + pht( + 'This source ("%s") does not have any import cursors.', + $source->getName())); + } + + echo tsprintf( + "%s\n", + pht('OK, but actual importing is not implemented yet.')); + + return 0; + } + +} diff --git a/src/applications/nuance/management/NuanceManagementWorkflow.php b/src/applications/nuance/management/NuanceManagementWorkflow.php new file mode 100644 index 0000000000..f6dbfe6e09 --- /dev/null +++ b/src/applications/nuance/management/NuanceManagementWorkflow.php @@ -0,0 +1,67 @@ +getArg($key); + if (!strlen($source)) { + throw new PhutilArgumentUsageException( + pht( + 'Specify a source with %s.', + '--'.$key)); + } + + $query = id(new NuanceSourceQuery()) + ->setViewer($this->getViewer()) + ->setRaisePolicyExceptions(true); + + $type_unknown = PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN; + + if (ctype_digit($source)) { + $kind = 'id'; + $query->withIDs(array($source)); + } else if (phid_get_type($source) !== $type_unknown) { + $kind = 'phid'; + $query->withPHIDs($source); + } else { + $kind = 'name'; + $query->withNameNgrams($source); + } + + $sources = $query->execute(); + + if (!$sources) { + switch ($kind) { + case 'id': + $message = pht( + 'No source exists with ID "%s".', + $source); + break; + case 'phid': + $message = pht( + 'No source exists with PHID "%s".', + $source); + break; + default: + $message = pht( + 'No source exists with a name matching "%s".', + $source); + break; + } + + throw new PhutilArgumentUsageException($message); + } else if (count($sources) > 1) { + $message = pht( + 'More than one source matches "%s". Choose a narrower query, or '. + 'use an ID or PHID to select a source. Matching sources: %s.', + $source, + implode(', ', mpull($sources, 'getName'))); + + throw new PhutilArgumentUsageException($message); + } + + return head($sources); + } + +} diff --git a/src/applications/nuance/query/NuanceSourceQuery.php b/src/applications/nuance/query/NuanceSourceQuery.php index 7bde9440e2..907d9c314f 100644 --- a/src/applications/nuance/query/NuanceSourceQuery.php +++ b/src/applications/nuance/query/NuanceSourceQuery.php @@ -34,10 +34,20 @@ final class NuanceSourceQuery return $this; } + public function withNameNgrams($ngrams) { + return $this->withNgramsConstraint( + new NuanceSourceNameNgrams(), + $ngrams); + } + public function newResultObject() { return new NuanceSource(); } + protected function getPrimaryTableAlias() { + return 'source'; + } + protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } @@ -65,28 +75,28 @@ final class NuanceSourceQuery if ($this->types !== null) { $where[] = qsprintf( $conn, - 'type IN (%Ls)', + 'source.type IN (%Ls)', $this->types); } if ($this->ids !== null) { $where[] = qsprintf( $conn, - 'id IN (%Ld)', + 'source.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, - 'phid IN (%Ls)', + 'source.phid IN (%Ls)', $this->phids); } if ($this->isDisabled !== null) { $where[] = qsprintf( $conn, - 'isDisabled = %d', + 'source.isDisabled = %d', (int)$this->isDisabled); } @@ -106,7 +116,7 @@ final class NuanceSourceQuery } else { $where[] = qsprintf( $conn, - 'type IN (%Ls)', + 'source.type IN (%Ls)', $cursor_types); } } else { @@ -115,7 +125,7 @@ final class NuanceSourceQuery } else { $where[] = qsprintf( $conn, - 'type NOT IN (%Ls)', + 'source.type NOT IN (%Ls)', $cursor_types); } } diff --git a/src/applications/nuance/query/NuanceSourceSearchEngine.php b/src/applications/nuance/query/NuanceSourceSearchEngine.php index 2991c69c5f..44f131aa1b 100644 --- a/src/applications/nuance/query/NuanceSourceSearchEngine.php +++ b/src/applications/nuance/query/NuanceSourceSearchEngine.php @@ -18,11 +18,20 @@ final class NuanceSourceSearchEngine protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); + if ($map['match'] !== null) { + $query->withNameNgrams($map['match']); + } + return $query; } protected function buildCustomSearchFields() { - return array(); + return array( + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Name Contains')) + ->setKey('match') + ->setDescription(pht('Search for sources by name substring.')), + ); } protected function getURI($path) { diff --git a/src/applications/nuance/storage/NuanceSource.php b/src/applications/nuance/storage/NuanceSource.php index 52da800e4f..9f60d03b51 100644 --- a/src/applications/nuance/storage/NuanceSource.php +++ b/src/applications/nuance/storage/NuanceSource.php @@ -3,7 +3,8 @@ final class NuanceSource extends NuanceDAO implements PhabricatorApplicationTransactionInterface, - PhabricatorPolicyInterface { + PhabricatorPolicyInterface, + PhabricatorNgramsInterface { protected $name; protected $type; @@ -23,7 +24,7 @@ final class NuanceSource extends NuanceDAO 'data' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( - 'name' => 'text255?', + 'name' => 'sort255', 'type' => 'text32', 'mailKey' => 'bytes20', 'isDisabled' => 'bool', @@ -132,4 +133,15 @@ final class NuanceSource extends NuanceDAO return null; } + +/* -( PhabricatorNgramsInterface )----------------------------------------- */ + + + public function newNgrams() { + return array( + id(new NuanceSourceNameNgrams()) + ->setValue($this->getName()), + ); + } + } diff --git a/src/applications/nuance/storage/NuanceSourceNameNgrams.php b/src/applications/nuance/storage/NuanceSourceNameNgrams.php new file mode 100644 index 0000000000..8bff4b8a2a --- /dev/null +++ b/src/applications/nuance/storage/NuanceSourceNameNgrams.php @@ -0,0 +1,18 @@ +nuanceCursors) { $source = array_pop($this->nuanceSources); - $cursors = $source->getImportCursors(); + + $definition = $source->getDefinition() + ->setViewer($this->getViewer()) + ->setSource($source); + + $cursors = $definition->getImportCursors(); $this->nuanceCursors = array_reverse($cursors); } From fe01949a5c102b5a7fa36e3685985d98bc2bdcb6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 8 Mar 2016 07:50:43 -0800 Subject: [PATCH 32/57] Add a Nuance GitHub repository source and basic polling Summary: Ref T10537. Ref T10538. This calls GitHub, sorta? Test Plan: ``` $ ./bin/nuance import --source poem Polling GitHub Repository API endpoint "/repos/epriestley/poems/events". This key has 4,988 remaining API request(s), limit resets in 1,871 second(s). ETag for this request was ""4abdd3d66ad5ca38f5117b094e76f4ba"". array(4) { [0]=> array(7) { ["id"]=> string(10) "3733510485" ... ``` Reviewers: chad Reviewed By: chad Maniphest Tasks: T10537, T10538 Differential Revision: https://secure.phabricator.com/D15439 --- src/__phutil_library_map__.php | 4 + .../NuanceGitHubRepositoryImportCursor.php | 114 ++++++++++++++++++ .../nuance/cursor/NuanceImportCursor.php | 92 +++++++++++++- .../NuanceManagementImportWorkflow.php | 6 +- ...NuanceGitHubRepositorySourceDefinition.php | 29 +++++ .../NuancePhabricatorFormSourceDefinition.php | 9 -- .../nuance/source/NuanceSourceDefinition.php | 81 ++++++++++--- .../nuance/storage/NuanceImportCursorData.php | 9 ++ .../nuance/storage/NuanceSource.php | 11 +- 9 files changed, 325 insertions(+), 30 deletions(-) create mode 100644 src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php create mode 100644 src/applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7e49233e5f..6461b1d547 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1421,6 +1421,8 @@ phutil_register_library_map(array( 'NuanceController' => 'applications/nuance/controller/NuanceController.php', 'NuanceCreateItemConduitAPIMethod' => 'applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php', 'NuanceDAO' => 'applications/nuance/storage/NuanceDAO.php', + 'NuanceGitHubRepositoryImportCursor' => 'applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php', + 'NuanceGitHubRepositorySourceDefinition' => 'applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php', 'NuanceImportCursor' => 'applications/nuance/cursor/NuanceImportCursor.php', 'NuanceImportCursorData' => 'applications/nuance/storage/NuanceImportCursorData.php', 'NuanceImportCursorDataQuery' => 'applications/nuance/query/NuanceImportCursorDataQuery.php', @@ -5668,6 +5670,8 @@ phutil_register_library_map(array( 'NuanceController' => 'PhabricatorController', 'NuanceCreateItemConduitAPIMethod' => 'NuanceConduitAPIMethod', 'NuanceDAO' => 'PhabricatorLiskDAO', + 'NuanceGitHubRepositoryImportCursor' => 'NuanceImportCursor', + 'NuanceGitHubRepositorySourceDefinition' => 'NuanceSourceDefinition', 'NuanceImportCursor' => 'Phobject', 'NuanceImportCursorData' => 'NuanceDAO', 'NuanceImportCursorDataQuery' => 'NuanceQuery', diff --git a/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php b/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php new file mode 100644 index 0000000000..48e65bd0a1 --- /dev/null +++ b/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php @@ -0,0 +1,114 @@ +getCursorProperty('github.poll.ttl'); + if ($ttl && ($ttl >= $now)) { + $this->logInfo( + pht( + 'Respecting "%s": waiting for %s second(s) to poll GitHub.', + 'X-Poll-Interval', + new PhutilNumber(1 + ($ttl - $now)))); + + return false; + } + + // Respect GitHub's API rate limiting. If we've exceeded the rate limit, + // wait until it resets to try again. + $limit = $this->getCursorProperty('github.limit.ttl'); + if ($limit && ($limit >= $now)) { + $this->logInfo( + pht( + 'Respecting "%s": waiting for %s second(s) to poll GitHub.', + 'X-RateLimit-Reset', + new PhutilNumber(1 + ($limit - $now)))); + return false; + } + + return true; + } + + protected function pullDataFromSource() { + $source = $this->getSource(); + + $user = $source->getSourceProperty('github.user'); + $repository = $source->getSourceProperty('github.repository'); + $api_token = $source->getSourceProperty('github.token'); + + $uri = "/repos/{$user}/{$repository}/events"; + $data = array(); + + $future = id(new PhutilGitHubFuture()) + ->setAccessToken($api_token) + ->setRawGitHubQuery($uri, $data); + + $etag = $this->getCursorProperty('github.poll.etag'); + if ($etag) { + $future->addHeader('If-None-Match', $etag); + } + + $this->logInfo( + pht( + 'Polling GitHub Repository API endpoint "%s".', + $uri)); + $response = $future->resolve(); + + // Do this first: if we hit the rate limit, we get a response but the + // body isn't valid. + $this->updateRateLimits($response); + + // This means we hit a rate limit or a "Not Modified" because of the "ETag" + // header. In either case, we should bail out. + if ($response->getStatus()->isError()) { + // TODO: Save cursor data! + return false; + } + + $this->updateETag($response); + + var_dump($response->getBody()); + } + + private function updateRateLimits(PhutilGitHubResponse $response) { + $remaining = $response->getHeaderValue('X-RateLimit-Remaining'); + $limit_reset = $response->getHeaderValue('X-RateLimit-Reset'); + $now = PhabricatorTime::getNow(); + + $limit_ttl = null; + if (strlen($remaining)) { + $remaining = (int)$remaining; + if (!$remaining) { + $limit_ttl = (int)$limit_reset; + } + } + + $this->setCursorProperty('github.limit.ttl', $limit_ttl); + + $this->logInfo( + pht( + 'This key has %s remaining API request(s), '. + 'limit resets in %s second(s).', + new PhutilNumber($remaining), + new PhutilNumber($limit_reset - $now))); + } + + private function updateETag(PhutilGitHubResponse $response) { + $etag = $response->getHeaderValue('ETag'); + + $this->setCursorProperty('github.poll.etag', $etag); + + $this->logInfo( + pht( + 'ETag for this request was "%s".', + $etag)); + } + +} diff --git a/src/applications/nuance/cursor/NuanceImportCursor.php b/src/applications/nuance/cursor/NuanceImportCursor.php index 0769f7feee..6b920d182e 100644 --- a/src/applications/nuance/cursor/NuanceImportCursor.php +++ b/src/applications/nuance/cursor/NuanceImportCursor.php @@ -2,9 +2,97 @@ abstract class NuanceImportCursor extends Phobject { + private $cursorData; + private $cursorKey; + private $source; + + abstract protected function shouldPullDataFromSource(); + abstract protected function pullDataFromSource(); + + final public function getCursorType() { + return $this->getPhobjectClassConstant('CURSORTYPE', 32); + } + + public function setCursorData(NuanceImportCursorData $cursor_data) { + $this->cursorData = $cursor_data; + return $this; + } + + public function getCursorData() { + return $this->cursorData; + } + + public function setSource($source) { + $this->source = $source; + return $this; + } + + public function getSource() { + return $this->source; + } + + public function setCursorKey($cursor_key) { + $this->cursorKey = $cursor_key; + return $this; + } + + public function getCursorKey() { + return $this->cursorKey; + } + final public function importFromSource() { - // TODO: Perhaps, do something. - return false; + if (!$this->shouldPullDataFromSource()) { + return false; + } + + $source = $this->getSource(); + $key = $this->getCursorKey(); + + $parts = array( + 'nsc', + $source->getID(), + PhabricatorHash::digestToLength($key, 20), + ); + $lock_name = implode('.', $parts); + + $lock = PhabricatorGlobalLock::newLock($lock_name); + $lock->lock(1); + + try { + $more_data = $this->pullDataFromSource(); + } catch (Exception $ex) { + $lock->unlock(); + throw $ex; + } + + $lock->unlock(); + + return $more_data; + } + + final public function newEmptyCursorData(NuanceSource $source) { + return id(new NuanceImportCursorData()) + ->setCursorKey($this->getCursorKey()) + ->setCursorType($this->getCursorType()) + ->setSourcePHID($source->getPHID()); + } + + final protected function logInfo($message) { + echo tsprintf( + " %s\n", + $this->getCursorKey(), + $message); + + return $this; + } + + final protected function getCursorProperty($key, $default = null) { + return $this->getCursorData()->getCursorProperty($key, $default); + } + + final protected function setCursorProperty($key, $value) { + $this->getCursorData()->setCursorProperty($key, $value); + return $this; } } diff --git a/src/applications/nuance/management/NuanceManagementImportWorkflow.php b/src/applications/nuance/management/NuanceManagementImportWorkflow.php index c8050ec1fc..ef93ba0749 100644 --- a/src/applications/nuance/management/NuanceManagementImportWorkflow.php +++ b/src/applications/nuance/management/NuanceManagementImportWorkflow.php @@ -40,9 +40,9 @@ final class NuanceManagementImportWorkflow $source->getName())); } - echo tsprintf( - "%s\n", - pht('OK, but actual importing is not implemented yet.')); + foreach ($cursors as $cursor) { + $cursor->importFromSource(); + } return 0; } diff --git a/src/applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php b/src/applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php new file mode 100644 index 0000000000..e6684aef26 --- /dev/null +++ b/src/applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php @@ -0,0 +1,29 @@ +setCursorKey('events.repository'), + ); + } + +} diff --git a/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php b/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php index 9d8644e82a..ce7891f61e 100644 --- a/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php +++ b/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php @@ -26,15 +26,6 @@ final class NuancePhabricatorFormSourceDefinition return $actions; } - public function updateItems() { - return null; - } - - public function renderView() {} - - public function renderListView() {} - - public function handleActionRequest(AphrontRequest $request) { $viewer = $request->getViewer(); diff --git a/src/applications/nuance/source/NuanceSourceDefinition.php b/src/applications/nuance/source/NuanceSourceDefinition.php index 103ac8214b..a830d494ec 100644 --- a/src/applications/nuance/source/NuanceSourceDefinition.php +++ b/src/applications/nuance/source/NuanceSourceDefinition.php @@ -53,7 +53,66 @@ abstract class NuanceSourceDefinition extends Phobject { pht('This source has no input cursors.')); } - return $this->newImportCursors(); + $source = $this->getSource(); + $cursors = $this->newImportCursors(); + + $data = id(new NuanceImportCursorDataQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withSourcePHIDs(array($source->getPHID())) + ->execute(); + $data = mpull($data, 'getCursorKey'); + + $map = array(); + foreach ($cursors as $cursor) { + if (!($cursor instanceof NuanceImportCursor)) { + throw new Exception( + pht( + 'Source "%s" (of class "%s") returned an invalid value from '. + 'method "%s": all values must be objects of class "%s".', + $this->getName(), + get_class($this), + 'newImportCursors()', + 'NuanceImportCursor')); + } + + $key = $cursor->getCursorKey(); + if (!strlen($key)) { + throw new Exception( + pht( + 'Source "%s" (of class "%s") returned an import cursor with '. + 'a missing key from "%s". Each cursor must have a unique, '. + 'nonempty key.', + $this->getName(), + get_class($this), + 'newImportCursors()')); + } + + $other = idx($map, $key); + if ($other) { + throw new Exception( + pht( + 'Source "%s" (of class "%s") returned two cursors from method '. + '"%s" with the same key ("%s"). Each cursor must have a unique '. + 'key.', + $this->getName(), + get_class($this), + 'newImportCursors()', + $key)); + } + + $map[$key] = $cursor; + + $cursor->setSource($source); + + $cursor_data = idx($data, $key); + if (!$cursor_data) { + $cursor_data = $cursor->newEmptyCursorData($source); + } + + $cursor->setCursorData($cursor_data); + } + + return $cursors; } protected function newImportCursors() { @@ -79,21 +138,13 @@ abstract class NuanceSourceDefinition extends Phobject { */ abstract public function getSourceTypeConstant(); - /** - * Code to create and update @{class:NuanceItem}s and - * @{class:NuanceRequestor}s via daemons goes here. - * - * If that does not make sense for the @{class:NuanceSource} you are - * defining, simply return null. For example, - * @{class:NuancePhabricatorFormSourceDefinition} since these are one-way - * contact forms. - */ - abstract public function updateItems(); - - abstract public function renderView(); - - abstract public function renderListView(); + public function renderView() { + return null; + } + public function renderListView() { + return null; + } protected function newItemFromProperties( NuanceRequestor $requestor, diff --git a/src/applications/nuance/storage/NuanceImportCursorData.php b/src/applications/nuance/storage/NuanceImportCursorData.php index 8b0bae5c63..02cc00d4fc 100644 --- a/src/applications/nuance/storage/NuanceImportCursorData.php +++ b/src/applications/nuance/storage/NuanceImportCursorData.php @@ -32,4 +32,13 @@ final class NuanceImportCursorData NuanceImportCursorPHIDType::TYPECONST); } + public function getCursorProperty($key, $default = null) { + return idx($this->properties, $key, $default); + } + + public function setCursorProperty($key, $value) { + $this->properties[$key] = $value; + return $this; + } + } diff --git a/src/applications/nuance/storage/NuanceSource.php b/src/applications/nuance/storage/NuanceSource.php index 9f60d03b51..8ab934e247 100644 --- a/src/applications/nuance/storage/NuanceSource.php +++ b/src/applications/nuance/storage/NuanceSource.php @@ -8,7 +8,7 @@ final class NuanceSource extends NuanceDAO protected $name; protected $type; - protected $data; + protected $data = array(); protected $mailKey; protected $viewPolicy; protected $editPolicy; @@ -82,6 +82,15 @@ final class NuanceSource extends NuanceDAO return $this; } + public function getSourceProperty($key, $default = null) { + return idx($this->data, $key, $default); + } + + public function setSourceProperty($key, $value) { + $this->data[$key] = $value; + return $this; + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ From e3a97e31a05d91d2571895fef483b0f964a8481c Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 8 Mar 2016 19:53:44 +0000 Subject: [PATCH 33/57] Update Phurl to PHUITwoColumnView Summary: Cleaner UI, moved visit to be button. Test Plan: Make a phurl about cats, click on it. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15385 --- .../PhabricatorPhurlURLViewController.php | 86 ++++++++++--------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php b/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php index dbc7fbcc79..946e35c854 100644 --- a/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php +++ b/src/applications/phurl/controller/PhabricatorPhurlURLViewController.php @@ -24,24 +24,20 @@ final class PhabricatorPhurlURLViewController $title = $url->getMonogram(); $page_title = $title.' '.$url->getName(); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($title, $url->getURI()); + $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); $timeline = $this->buildTransactionTimeline( $url, new PhabricatorPhurlURLTransactionQuery()); $header = $this->buildHeaderView($url); - $actions = $this->buildActionView($url); - $properties = $this->buildPropertyView($url); + $curtain = $this->buildCurtain($url); + $details = $this->buildPropertySectionView($url); - $properties->setActionList($actions); $url_error = id(new PHUIInfoView()) ->setErrors(array(pht('This URL is invalid due to a bad protocol.'))) ->setIsHidden($url->isValid()); - $box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties) - ->setInfoView($url_error); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $add_comment_header = $is_serious @@ -58,71 +54,80 @@ final class PhabricatorPhurlURLViewController ->setAction($comment_uri) ->setSubmitButtonName(pht('Add Comment')); - return $this->buildApplicationPage( - array( - $crumbs, - $box, + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->setMainColumn(array( + $url_error, + $details, $timeline, $add_comment_form, - ), - array( - 'title' => $page_title, - 'pageObjects' => array($url->getPHID()), )); + + return $this->newPage() + ->setTitle($page_title) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs(array($url->getPHID())) + ->appendChild( + array( + $view, + )); + } private function buildHeaderView(PhabricatorPhurlURL $url) { $viewer = $this->getViewer(); - $icon = 'fa-compress'; - $color = 'green'; + $icon = 'fa-check'; + $color = 'bluegrey'; $status = pht('Active'); + $id = $url->getID(); + + $visit = id(new PHUIButtonView()) + ->setTag('a') + ->setText(pht('Visit URL')) + ->setIcon('fa-external-link') + ->setHref("u/{$id}") + ->setDisabled(!$url->isValid()); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($url->getDisplayName()) ->setStatus($icon, $color, $status) - ->setPolicyObject($url); + ->setPolicyObject($url) + ->setHeaderIcon('fa-compress') + ->addActionLink($visit); return $header; } - private function buildActionView(PhabricatorPhurlURL $url) { + private function buildCurtain(PhabricatorPhurlURL $url) { $viewer = $this->getViewer(); $id = $url->getID(); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer) - ->setObject($url); + $curtain = $this->newCurtainView($url); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $url, PhabricatorPolicyCapability::CAN_EDIT); - $actions + $curtain ->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit')) ->setIcon('fa-pencil') ->setHref($this->getApplicationURI("url/edit/{$id}/")) ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)) - ->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Visit URL')) - ->setIcon('fa-external-link') - ->setHref("u/{$id}") - ->setDisabled(!$url->isValid())); + ->setWorkflow(!$can_edit)); - return $actions; + return $curtain; } - private function buildPropertyView(PhabricatorPhurlURL $url) { + private function buildPropertySectionView(PhabricatorPhurlURL $url) { $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($url); + ->setUser($viewer); $properties->addProperty( pht('Original URL'), @@ -132,18 +137,17 @@ final class PhabricatorPhurlURLViewController pht('Alias'), $url->getAlias()); - $properties->invokeWillRenderEvent(); - $description = $url->getDescription(); if (strlen($description)) { $description = new PHUIRemarkupView($viewer, $description); - $properties->addSectionHeader( - pht('Description'), - PHUIPropertyListView::ICON_SUMMARY); + $properties->addSectionHeader(pht('Description')); $properties->addTextContent($description); } - return $properties; + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('DETAILS')) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($properties); } } From 5d6bb0ffebd2af22eb28310fb880aef27273c3f0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 8 Mar 2016 08:51:45 -0800 Subject: [PATCH 34/57] Import raw GitHub event data into Nuance Summary: Ref T10537. Ref T10538. This polls the GitHub events API and creates Nuance items from the raw data. It does nothing useful with them. Test Plan: - Polled GitHub. - Saw some items get created. - X-Poll-Interval seemed to work. - ETag seemed to work. - Recognizing when we hit items we've already seen seemed to work. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10537, T10538 Differential Revision: https://secure.phabricator.com/D15440 --- .../autopatches/20160308.nuance.06.label.sql | 2 + .../20160308.nuance.07.itemtype.sql | 2 + .../20160308.nuance.08.itemkey.sql | 2 + .../20160308.nuance.09.itemcontainer.sql | 2 + .../20160308.nuance.10.itemkeyu.sql | 2 + .../20160308.nuance.11.requestor.sql | 2 + .../autopatches/20160308.nuance.12.queue.sql | 2 + src/__phutil_library_map__.php | 13 +- .../PhabricatorNuanceApplication.php | 1 + .../NuanceCreateItemConduitAPIMethod.php | 73 ------- .../controller/NuanceConsoleController.php | 7 + .../controller/NuanceItemController.php | 11 + .../controller/NuanceItemEditController.php | 2 +- .../controller/NuanceItemListController.php | 12 + .../NuanceGitHubRepositoryImportCursor.php | 205 +++++++++++++++--- .../nuance/cursor/NuanceImportCursor.php | 10 + .../nuance/query/NuanceItemQuery.php | 39 ++++ .../nuance/query/NuanceItemSearchEngine.php | 81 +++++++ .../nuance/source/NuanceSourceDefinition.php | 12 +- .../nuance/storage/NuanceImportCursorData.php | 28 ++- .../nuance/storage/NuanceItem.php | 55 +++-- 21 files changed, 424 insertions(+), 139 deletions(-) create mode 100644 resources/sql/autopatches/20160308.nuance.06.label.sql create mode 100644 resources/sql/autopatches/20160308.nuance.07.itemtype.sql create mode 100644 resources/sql/autopatches/20160308.nuance.08.itemkey.sql create mode 100644 resources/sql/autopatches/20160308.nuance.09.itemcontainer.sql create mode 100644 resources/sql/autopatches/20160308.nuance.10.itemkeyu.sql create mode 100644 resources/sql/autopatches/20160308.nuance.11.requestor.sql create mode 100644 resources/sql/autopatches/20160308.nuance.12.queue.sql delete mode 100644 src/applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php create mode 100644 src/applications/nuance/controller/NuanceItemController.php create mode 100644 src/applications/nuance/controller/NuanceItemListController.php create mode 100644 src/applications/nuance/query/NuanceItemSearchEngine.php diff --git a/resources/sql/autopatches/20160308.nuance.06.label.sql b/resources/sql/autopatches/20160308.nuance.06.label.sql new file mode 100644 index 0000000000..4ba67ed3e4 --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.06.label.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_item + DROP sourceLabel; diff --git a/resources/sql/autopatches/20160308.nuance.07.itemtype.sql b/resources/sql/autopatches/20160308.nuance.07.itemtype.sql new file mode 100644 index 0000000000..d34b5c77e9 --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.07.itemtype.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_item + ADD itemType VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160308.nuance.08.itemkey.sql b/resources/sql/autopatches/20160308.nuance.08.itemkey.sql new file mode 100644 index 0000000000..12b6a88673 --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.08.itemkey.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_item + ADD itemKey VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160308.nuance.09.itemcontainer.sql b/resources/sql/autopatches/20160308.nuance.09.itemcontainer.sql new file mode 100644 index 0000000000..0b76c76827 --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.09.itemcontainer.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_item + ADD itemContainerKey VARCHAR(64) COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160308.nuance.10.itemkeyu.sql b/resources/sql/autopatches/20160308.nuance.10.itemkeyu.sql new file mode 100644 index 0000000000..b455ada156 --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.10.itemkeyu.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_nuance.nuance_item + SET itemKey = id WHERE itemKey = ''; diff --git a/resources/sql/autopatches/20160308.nuance.11.requestor.sql b/resources/sql/autopatches/20160308.nuance.11.requestor.sql new file mode 100644 index 0000000000..590b44197a --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.11.requestor.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_item + CHANGE requestorPHID requestorPHID VARBINARY(64); diff --git a/resources/sql/autopatches/20160308.nuance.12.queue.sql b/resources/sql/autopatches/20160308.nuance.12.queue.sql new file mode 100644 index 0000000000..bb0554b57d --- /dev/null +++ b/resources/sql/autopatches/20160308.nuance.12.queue.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_nuance.nuance_item + CHANGE queuePHID queuePHID VARBINARY(64); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 6461b1d547..67b5d98eb3 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1419,7 +1419,6 @@ phutil_register_library_map(array( 'NuanceConduitAPIMethod' => 'applications/nuance/conduit/NuanceConduitAPIMethod.php', 'NuanceConsoleController' => 'applications/nuance/controller/NuanceConsoleController.php', 'NuanceController' => 'applications/nuance/controller/NuanceController.php', - 'NuanceCreateItemConduitAPIMethod' => 'applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php', 'NuanceDAO' => 'applications/nuance/storage/NuanceDAO.php', 'NuanceGitHubRepositoryImportCursor' => 'applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php', 'NuanceGitHubRepositorySourceDefinition' => 'applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php', @@ -1428,10 +1427,13 @@ phutil_register_library_map(array( 'NuanceImportCursorDataQuery' => 'applications/nuance/query/NuanceImportCursorDataQuery.php', 'NuanceImportCursorPHIDType' => 'applications/nuance/phid/NuanceImportCursorPHIDType.php', 'NuanceItem' => 'applications/nuance/storage/NuanceItem.php', + 'NuanceItemController' => 'applications/nuance/controller/NuanceItemController.php', 'NuanceItemEditController' => 'applications/nuance/controller/NuanceItemEditController.php', 'NuanceItemEditor' => 'applications/nuance/editor/NuanceItemEditor.php', + 'NuanceItemListController' => 'applications/nuance/controller/NuanceItemListController.php', 'NuanceItemPHIDType' => 'applications/nuance/phid/NuanceItemPHIDType.php', 'NuanceItemQuery' => 'applications/nuance/query/NuanceItemQuery.php', + 'NuanceItemSearchEngine' => 'applications/nuance/query/NuanceItemSearchEngine.php', 'NuanceItemTransaction' => 'applications/nuance/storage/NuanceItemTransaction.php', 'NuanceItemTransactionComment' => 'applications/nuance/storage/NuanceItemTransactionComment.php', 'NuanceItemTransactionQuery' => 'applications/nuance/query/NuanceItemTransactionQuery.php', @@ -5668,12 +5670,14 @@ phutil_register_library_map(array( 'NuanceConduitAPIMethod' => 'ConduitAPIMethod', 'NuanceConsoleController' => 'NuanceController', 'NuanceController' => 'PhabricatorController', - 'NuanceCreateItemConduitAPIMethod' => 'NuanceConduitAPIMethod', 'NuanceDAO' => 'PhabricatorLiskDAO', 'NuanceGitHubRepositoryImportCursor' => 'NuanceImportCursor', 'NuanceGitHubRepositorySourceDefinition' => 'NuanceSourceDefinition', 'NuanceImportCursor' => 'Phobject', - 'NuanceImportCursorData' => 'NuanceDAO', + 'NuanceImportCursorData' => array( + 'NuanceDAO', + 'PhabricatorPolicyInterface', + ), 'NuanceImportCursorDataQuery' => 'NuanceQuery', 'NuanceImportCursorPHIDType' => 'PhabricatorPHIDType', 'NuanceItem' => array( @@ -5681,10 +5685,13 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorApplicationTransactionInterface', ), + 'NuanceItemController' => 'NuanceController', 'NuanceItemEditController' => 'NuanceController', 'NuanceItemEditor' => 'PhabricatorApplicationTransactionEditor', + 'NuanceItemListController' => 'NuanceItemController', 'NuanceItemPHIDType' => 'PhabricatorPHIDType', 'NuanceItemQuery' => 'NuanceQuery', + 'NuanceItemSearchEngine' => 'PhabricatorApplicationSearchEngine', 'NuanceItemTransaction' => 'NuanceTransaction', 'NuanceItemTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceItemTransactionQuery' => 'PhabricatorApplicationTransactionQuery', diff --git a/src/applications/nuance/application/PhabricatorNuanceApplication.php b/src/applications/nuance/application/PhabricatorNuanceApplication.php index e6cd787970..b11b23fa3a 100644 --- a/src/applications/nuance/application/PhabricatorNuanceApplication.php +++ b/src/applications/nuance/application/PhabricatorNuanceApplication.php @@ -40,6 +40,7 @@ final class PhabricatorNuanceApplication extends PhabricatorApplication { '/nuance/' => array( '' => 'NuanceConsoleController', 'item/' => array( + $this->getQueryRoutePattern() => 'NuanceItemListController', 'view/(?P[1-9]\d*)/' => 'NuanceItemViewController', 'edit/(?P[1-9]\d*)/' => 'NuanceItemEditController', 'new/' => 'NuanceItemEditController', diff --git a/src/applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php b/src/applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php deleted file mode 100644 index b05c09fec4..0000000000 --- a/src/applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php +++ /dev/null @@ -1,73 +0,0 @@ - 'required string', - 'sourcePHID' => 'required string', - 'ownerPHID' => 'optional string', - ); - } - - protected function defineReturnType() { - return 'nonempty dict'; - } - - protected function defineErrorTypes() { - return array( - 'ERR-NO-REQUESTOR-PHID' => pht('Items must have a requestor.'), - 'ERR-NO-SOURCE-PHID' => pht('Items must have a source.'), - ); - } - - protected function execute(ConduitAPIRequest $request) { - $source_phid = $request->getValue('sourcePHID'); - $owner_phid = $request->getValue('ownerPHID'); - $requestor_phid = $request->getValue('requestorPHID'); - - $user = $request->getUser(); - - $item = NuanceItem::initializeNewItem(); - $xactions = array(); - - if ($source_phid) { - $xactions[] = id(new NuanceItemTransaction()) - ->setTransactionType(NuanceItemTransaction::TYPE_SOURCE) - ->setNewValue($source_phid); - } else { - throw new ConduitException('ERR-NO-SOURCE-PHID'); - } - - if ($owner_phid) { - $xactions[] = id(new NuanceItemTransaction()) - ->setTransactionType(NuanceItemTransaction::TYPE_OWNER) - ->setNewValue($owner_phid); - } - - if ($requestor_phid) { - $xactions[] = id(new NuanceItemTransaction()) - ->setTransactionType(NuanceItemTransaction::TYPE_REQUESTOR) - ->setNewValue($requestor_phid); - } else { - throw new ConduitException('ERR-NO-REQUESTOR-PHID'); - } - - $source = PhabricatorContentSource::newFromConduitRequest($request); - $editor = id(new NuanceItemEditor()) - ->setActor($user) - ->setContentSource($source) - ->applyTransactions($item, $xactions); - - return $item->toDictionary(); - } - -} diff --git a/src/applications/nuance/controller/NuanceConsoleController.php b/src/applications/nuance/controller/NuanceConsoleController.php index ffcde21ba3..6416c19aaf 100644 --- a/src/applications/nuance/controller/NuanceConsoleController.php +++ b/src/applications/nuance/controller/NuanceConsoleController.php @@ -26,6 +26,13 @@ final class NuanceConsoleController extends NuanceController { ->setIcon('fa-filter') ->addAttribute(pht('Manage Nuance sources.'))); + $menu->addItem( + id(new PHUIObjectItemView()) + ->setHeader(pht('Items')) + ->setHref($this->getApplicationURI('item/')) + ->setIcon('fa-clone') + ->addAttribute(pht('Manage Nuance items.'))); + $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Console')); diff --git a/src/applications/nuance/controller/NuanceItemController.php b/src/applications/nuance/controller/NuanceItemController.php new file mode 100644 index 0000000000..fa5b01ddb9 --- /dev/null +++ b/src/applications/nuance/controller/NuanceItemController.php @@ -0,0 +1,11 @@ +newApplicationMenu() + ->setSearchEngine(new NuanceItemSearchEngine()); + } + +} diff --git a/src/applications/nuance/controller/NuanceItemEditController.php b/src/applications/nuance/controller/NuanceItemEditController.php index afb84e6471..85d3c1cf85 100644 --- a/src/applications/nuance/controller/NuanceItemEditController.php +++ b/src/applications/nuance/controller/NuanceItemEditController.php @@ -74,7 +74,7 @@ final class NuanceItemEditController extends NuanceController { $viewer->renderHandle($item->getQueuePHID())); $source = $item->getSource(); - $definition = $source->requireDefinition(); + $definition = $source->getDefinition(); $definition->renderItemEditProperties( $viewer, diff --git a/src/applications/nuance/controller/NuanceItemListController.php b/src/applications/nuance/controller/NuanceItemListController.php new file mode 100644 index 0000000000..2135076fee --- /dev/null +++ b/src/applications/nuance/controller/NuanceItemListController.php @@ -0,0 +1,12 @@ +setController($this) + ->buildResponse(); + } + +} diff --git a/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php b/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php index 48e65bd0a1..d9874ff495 100644 --- a/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php +++ b/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php @@ -37,44 +37,146 @@ final class NuanceGitHubRepositoryImportCursor } protected function pullDataFromSource() { + $viewer = $this->getViewer(); + $now = PhabricatorTime::getNow(); + $source = $this->getSource(); $user = $source->getSourceProperty('github.user'); $repository = $source->getSourceProperty('github.repository'); $api_token = $source->getSourceProperty('github.token'); - $uri = "/repos/{$user}/{$repository}/events"; - $data = array(); + // This API only supports fetching 10 pages of 30 events each, for a total + // of 300 events. + $etag = null; + $new_items = array(); + $hit_known_items = false; + for ($page = 1; $page <= 10; $page++) { + $uri = "/repos/{$user}/{$repository}/events"; + $data = array( + 'page' => $page, + ); - $future = id(new PhutilGitHubFuture()) - ->setAccessToken($api_token) - ->setRawGitHubQuery($uri, $data); + $future = id(new PhutilGitHubFuture()) + ->setAccessToken($api_token) + ->setRawGitHubQuery($uri, $data); - $etag = $this->getCursorProperty('github.poll.etag'); - if ($etag) { - $future->addHeader('If-None-Match', $etag); + if ($page == 1) { + $cursor_etag = $this->getCursorProperty('github.poll.etag'); + if ($cursor_etag) { + $future->addHeader('If-None-Match', $cursor_etag); + } + } + + $this->logInfo( + pht( + 'Polling GitHub Repository API endpoint "%s".', + $uri)); + $response = $future->resolve(); + + // Do this first: if we hit the rate limit, we get a response but the + // body isn't valid. + $this->updateRateLimits($response); + + if ($response->getStatus()->getStatusCode() == 304) { + $this->logInfo( + pht( + 'Received a 304 Not Modified from GitHub, no new events.')); + } + + // This means we hit a rate limit or a "Not Modified" because of the + // "ETag" header. In either case, we should bail out. + if ($response->getStatus()->isError()) { + $this->updatePolling($response, $now, false); + $this->getCursorData()->save(); + return false; + } + + if ($page == 1) { + $etag = $response->getHeaderValue('ETag'); + } + + $records = $response->getBody(); + foreach ($records as $record) { + $item = $this->newNuanceItemFromGitHubEvent($record); + $item_key = $item->getItemKey(); + + $this->logInfo( + pht( + 'Fetched event "%s".', + $item_key)); + + $new_items[$item->getItemKey()] = $item; + } + + if ($new_items) { + $existing = id(new NuanceItemQuery()) + ->setViewer($viewer) + ->withSourcePHIDs(array($source->getPHID())) + ->withItemKeys(array_keys($new_items)) + ->execute(); + $existing = mpull($existing, null, 'getItemKey'); + foreach ($new_items as $key => $new_item) { + if (isset($existing[$key])) { + unset($new_items[$key]); + $hit_known_items = true; + + $this->logInfo( + pht( + 'Event "%s" is previously known.', + $key)); + } + } + } + + if ($hit_known_items) { + break; + } + + if (count($records) < 30) { + break; + } } - $this->logInfo( - pht( - 'Polling GitHub Repository API endpoint "%s".', - $uri)); - $response = $future->resolve(); - - // Do this first: if we hit the rate limit, we get a response but the - // body isn't valid. - $this->updateRateLimits($response); - - // This means we hit a rate limit or a "Not Modified" because of the "ETag" - // header. In either case, we should bail out. - if ($response->getStatus()->isError()) { - // TODO: Save cursor data! - return false; + // TODO: When we go through the whole queue without hitting anything we + // have seen before, we should record some sort of global event so we + // can tell the user when the bridging started or was interrupted? + if (!$hit_known_items) { + $already_polled = $this->getCursorProperty('github.polled'); + if ($already_polled) { + // TODO: This is bad: we missed some items, maybe because too much + // stuff happened too fast or the daemons were broken for a long + // time. + } else { + // TODO: This is OK, we're doing the initial import. + } } - $this->updateETag($response); + if ($etag !== null) { + $this->updateETag($etag); + } - var_dump($response->getBody()); + $this->updatePolling($response, $now, true); + + $source->openTransaction(); + foreach ($new_items as $new_item) { + $new_item->save(); + } + $this->getCursorData()->save(); + $source->saveTransaction(); + + foreach ($new_items as $new_item) { + PhabricatorWorker::scheduleTask( + 'NuanceImportWorker', + array( + 'itemPHID' => $new_item->getPHID(), + ), + array( + 'objectPHID' => $new_item->getPHID(), + )); + } + + return false; } private function updateRateLimits(PhutilGitHubResponse $response) { @@ -100,8 +202,7 @@ final class NuanceGitHubRepositoryImportCursor new PhutilNumber($limit_reset - $now))); } - private function updateETag(PhutilGitHubResponse $response) { - $etag = $response->getHeaderValue('ETag'); + private function updateETag($etag) { $this->setCursorProperty('github.poll.etag', $etag); @@ -111,4 +212,54 @@ final class NuanceGitHubRepositoryImportCursor $etag)); } + private function updatePolling( + PhutilGitHubResponse $response, + $start, + $success) { + + if ($success) { + $this->setCursorProperty('github.polled', true); + } + + $poll_interval = (int)$response->getHeaderValue('X-Poll-Interval'); + $poll_ttl = $start + $poll_interval; + $this->setCursorProperty('github.poll.ttl', $poll_ttl); + + $now = PhabricatorTime::getNow(); + + $this->logInfo( + pht( + 'Set API poll TTL to +%s second(s) (%s second(s) from now).', + new PhutilNumber($poll_interval), + new PhutilNumber($poll_ttl - $now))); + } + + private function newNuanceItemFromGitHubEvent(array $record) { + $source = $this->getSource(); + + $id = $record['id']; + $item_key = "github.event.{$id}"; + + $container_key = null; + + $issue_id = idxv( + $record, + array( + 'payload', + 'issue', + 'id', + )); + if ($issue_id) { + $container_key = "github.issue.{$issue_id}"; + } + + return NuanceItem::initializeNewItem() + ->setStatus(NuanceItem::STATUS_IMPORTING) + ->setSourcePHID($source->getPHID()) + ->setItemType('github.event') + ->setItemKey($item_key) + ->setItemContainerKey($container_key) + ->setItemProperty('api.raw', $record); + } + } diff --git a/src/applications/nuance/cursor/NuanceImportCursor.php b/src/applications/nuance/cursor/NuanceImportCursor.php index 6b920d182e..03dad4d7e4 100644 --- a/src/applications/nuance/cursor/NuanceImportCursor.php +++ b/src/applications/nuance/cursor/NuanceImportCursor.php @@ -5,6 +5,7 @@ abstract class NuanceImportCursor extends Phobject { private $cursorData; private $cursorKey; private $source; + private $viewer; abstract protected function shouldPullDataFromSource(); abstract protected function pullDataFromSource(); @@ -40,6 +41,15 @@ abstract class NuanceImportCursor extends Phobject { return $this->cursorKey; } + public function setViewer($viewer) { + $this->viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + final public function importFromSource() { if (!$this->shouldPullDataFromSource()) { return false; diff --git a/src/applications/nuance/query/NuanceItemQuery.php b/src/applications/nuance/query/NuanceItemQuery.php index fbcac6e5b7..2f731920c6 100644 --- a/src/applications/nuance/query/NuanceItemQuery.php +++ b/src/applications/nuance/query/NuanceItemQuery.php @@ -6,6 +6,9 @@ final class NuanceItemQuery private $ids; private $phids; private $sourcePHIDs; + private $itemTypes; + private $itemKeys; + private $containerKeys; public function withIDs(array $ids) { $this->ids = $ids; @@ -22,6 +25,21 @@ final class NuanceItemQuery return $this; } + public function withItemTypes(array $item_types) { + $this->itemTypes = $item_types; + return $this; + } + + public function withItemKeys(array $item_keys) { + $this->itemKeys = $item_keys; + return $this; + } + + public function withItemContainerKeys(array $container_keys) { + $this->containerKeys = $container_keys; + return $this; + } + public function newResultObject() { return new NuanceItem(); } @@ -79,6 +97,27 @@ final class NuanceItemQuery $this->phids); } + if ($this->itemTypes !== null) { + $where[] = qsprintf( + $conn, + 'itemType IN (%Ls)', + $this->itemTypes); + } + + if ($this->itemKeys !== null) { + $where[] = qsprintf( + $conn, + 'itemKey IN (%Ls)', + $this->itemKeys); + } + + if ($this->containerKeys !== null) { + $where[] = qsprintf( + $conn, + 'itemContainerKey IN (%Ls)', + $this->containerKeys); + } + return $where; } diff --git a/src/applications/nuance/query/NuanceItemSearchEngine.php b/src/applications/nuance/query/NuanceItemSearchEngine.php new file mode 100644 index 0000000000..cbc0e9f321 --- /dev/null +++ b/src/applications/nuance/query/NuanceItemSearchEngine.php @@ -0,0 +1,81 @@ +newQuery(); + + return $query; + } + + protected function buildCustomSearchFields() { + return array( + ); + } + + protected function getURI($path) { + return '/nuance/item/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array( + 'all' => pht('All Items'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $items, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($items, 'NuanceItem'); + + $viewer = $this->requireViewer(); + + $list = new PHUIObjectItemListView(); + $list->setUser($viewer); + foreach ($items as $item) { + $view = id(new PHUIObjectItemView()) + ->setObjectName(pht('Item %d', $item->getID())) + ->setHeader($item->getDisplayName()) + ->setHref($item->getURI()); + + $view->addIcon('none', $item->getItemType()); + + $list->addItem($view); + } + + $result = new PhabricatorApplicationSearchResultView(); + $result->setObjectList($list); + $result->setNoDataString(pht('No items found.')); + + return $result; + } + +} diff --git a/src/applications/nuance/source/NuanceSourceDefinition.php b/src/applications/nuance/source/NuanceSourceDefinition.php index a830d494ec..e5168c3040 100644 --- a/src/applications/nuance/source/NuanceSourceDefinition.php +++ b/src/applications/nuance/source/NuanceSourceDefinition.php @@ -53,14 +53,15 @@ abstract class NuanceSourceDefinition extends Phobject { pht('This source has no input cursors.')); } + $viewer = PhabricatorUser::getOmnipotentUser(); $source = $this->getSource(); $cursors = $this->newImportCursors(); $data = id(new NuanceImportCursorDataQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->setViewer($viewer) ->withSourcePHIDs(array($source->getPHID())) ->execute(); - $data = mpull($data, 'getCursorKey'); + $data = mpull($data, null, 'getCursorKey'); $map = array(); foreach ($cursors as $cursor) { @@ -102,14 +103,15 @@ abstract class NuanceSourceDefinition extends Phobject { $map[$key] = $cursor; - $cursor->setSource($source); - $cursor_data = idx($data, $key); if (!$cursor_data) { $cursor_data = $cursor->newEmptyCursorData($source); } - $cursor->setCursorData($cursor_data); + $cursor + ->setViewer($viewer) + ->setSource($source) + ->setCursorData($cursor_data); } return $cursors; diff --git a/src/applications/nuance/storage/NuanceImportCursorData.php b/src/applications/nuance/storage/NuanceImportCursorData.php index 02cc00d4fc..01f3d56c6d 100644 --- a/src/applications/nuance/storage/NuanceImportCursorData.php +++ b/src/applications/nuance/storage/NuanceImportCursorData.php @@ -1,7 +1,8 @@ array( 'ownerPHID' => 'phid?', - 'sourceLabel' => 'text255?', - 'status' => 'uint32', + 'requestorPHID' => 'phid?', + 'queuePHID' => 'phid?', + 'itemType' => 'text64', + 'itemKey' => 'text64', + 'itemContainerKey' => 'text64?', + 'status' => 'text32', 'mailKey' => 'bytes20', ), self::CONFIG_KEY_SCHEMA => array( @@ -51,6 +58,13 @@ final class NuanceItem 'key_queue' => array( 'columns' => array('queuePHID', 'status'), ), + 'key_container' => array( + 'columns' => array('sourcePHID', 'itemContainerKey'), + ), + 'key_item' => array( + 'columns' => array('sourcePHID', 'itemKey'), + 'unique' => true, + ), ), ) + parent::getConfiguration(); } @@ -72,15 +86,7 @@ final class NuanceItem } public function getLabel(PhabricatorUser $viewer) { - // this is generated at the time the item is created based on - // the configuration from the item source. It is typically - // something like 'Twitter'. - $source_label = $this->getSourceLabel(); - - return pht( - 'Item via %s @ %s.', - $source_label, - phabricator_datetime($this->getDateCreated(), $viewer)); + return pht('TODO: An Item'); } public function getRequestor() { @@ -99,11 +105,11 @@ final class NuanceItem $this->source = $source; } - public function getNuanceProperty($key, $default = null) { + public function getItemProperty($key, $default = null) { return idx($this->data, $key, $default); } - public function setNuanceProperty($key, $value) { + public function setItemProperty($key, $value) { $this->data[$key] = $value; return $this; } @@ -135,17 +141,8 @@ final class NuanceItem return null; } - public function toDictionary() { - return array( - 'id' => $this->getID(), - 'phid' => $this->getPHID(), - 'ownerPHID' => $this->getOwnerPHID(), - 'requestorPHID' => $this->getRequestorPHID(), - 'sourcePHID' => $this->getSourcePHID(), - 'sourceLabel' => $this->getSourceLabel(), - 'dateCreated' => $this->getDateCreated(), - 'dateModified' => $this->getDateModified(), - ); + public function getDisplayName() { + return pht('An Item'); } From 3d44a5c253ade4414caab8c4b464e5b679a44b1d Mon Sep 17 00:00:00 2001 From: Chad Little Date: Tue, 8 Mar 2016 17:50:01 -0800 Subject: [PATCH 35/57] Polish up timeline for PHIUTwoColumnView Summary: This inverts colors and icons a bit, so they're not as harsh. So instead of a dark green item with white icon, its now light green with a dark green icon. I've also changed all text and comment boxes to be "grey" visually to separate out the UI from converation/actions. Give it a spin and let me know how this feels. I still need to update the comment UI. Test Plan: UIExamples, lots of various tasks and diffs. {F1163837} {F1163839} Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15442 --- resources/celerity/map.php | 15 +-- .../CelerityDefaultPostprocessor.php | 1 + .../PhabricatorContentSourceView.php | 7 ++ src/view/phui/PHUITimelineEventView.php | 17 ++- .../rsrc/css/phui/phui-property-list-view.css | 2 +- webroot/rsrc/css/phui/phui-timeline-view.css | 114 ++++++++++-------- .../rsrc/css/phui/phui-two-column-view.css | 6 +- webroot/rsrc/image/d5d8e1.png | Bin 0 -> 70 bytes 8 files changed, 96 insertions(+), 66 deletions(-) create mode 100644 webroot/rsrc/image/d5d8e1.png diff --git a/resources/celerity/map.php b/resources/celerity/map.php index df05681185..bba3e312b1 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'd3b3a609', + 'core.pkg.css' => '9c8e888d', 'core.pkg.js' => '7d8faf57', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', @@ -149,14 +149,14 @@ return array( 'rsrc/css/phui/phui-pager.css' => 'bea33d23', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', 'rsrc/css/phui/phui-profile-menu.css' => '7e92a89a', - 'rsrc/css/phui/phui-property-list-view.css' => '27b2849e', + 'rsrc/css/phui/phui-property-list-view.css' => 'b12e801c', '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' => '37309046', 'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2', - 'rsrc/css/phui/phui-timeline-view.css' => '2efceff8', - 'rsrc/css/phui/phui-two-column-view.css' => '38871c98', + 'rsrc/css/phui/phui-timeline-view.css' => 'a0173eba', + 'rsrc/css/phui/phui-two-column-view.css' => 'e6bf86b6', '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', @@ -273,6 +273,7 @@ return array( 'rsrc/image/checker_dark.png' => 'd8e65881', 'rsrc/image/checker_light.png' => 'a0155918', 'rsrc/image/checker_lighter.png' => 'd5da91b6', + 'rsrc/image/d5d8e1.png' => '0c2a1497', 'rsrc/image/darkload.gif' => '1ffd3ec6', 'rsrc/image/divot.png' => '94dded62', 'rsrc/image/examples/hero.png' => '979a86ae', @@ -837,15 +838,15 @@ return array( 'phui-pager-css' => 'bea33d23', 'phui-pinboard-view-css' => '2495140e', 'phui-profile-menu-css' => '7e92a89a', - 'phui-property-list-view-css' => '27b2849e', + 'phui-property-list-view-css' => 'b12e801c', 'phui-remarkup-preview-css' => '1a8f2591', 'phui-segment-bar-view-css' => '46342871', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => '37309046', 'phui-tag-view-css' => '6bbd83e2', 'phui-theme-css' => '027ba77e', - 'phui-timeline-view-css' => '2efceff8', - 'phui-two-column-view-css' => '38871c98', + 'phui-timeline-view-css' => 'a0173eba', + 'phui-two-column-view-css' => 'e6bf86b6', 'phui-workboard-color-css' => 'ac6fe6a7', 'phui-workboard-view-css' => 'e6d89647', 'phui-workcard-view-css' => '3646fb96', diff --git a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php index 13cef002cf..7d87d28ff2 100644 --- a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php +++ b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php @@ -71,6 +71,7 @@ final class CelerityDefaultPostprocessor 'hoverselectedgrey' => '#bbc4ca', 'hoverselectedblue' => '#e6e9ee', 'borderinset' => 'inset 0 0 0 1px rgba(55,55,55,.15)', + 'timeline' => '#d5d8e1', // Alphas 'alphawhite' => '255,255,255', diff --git a/src/applications/metamta/contentsource/PhabricatorContentSourceView.php b/src/applications/metamta/contentsource/PhabricatorContentSourceView.php index f159b2059d..5b19f41cb2 100644 --- a/src/applications/metamta/contentsource/PhabricatorContentSourceView.php +++ b/src/applications/metamta/contentsource/PhabricatorContentSourceView.php @@ -9,6 +9,13 @@ final class PhabricatorContentSourceView extends AphrontView { return $this; } + public function getSourceName() { + $map = PhabricatorContentSource::getSourceNameMap(); + $source = $this->contentSource->getSource(); + return idx($map, $source, null); + + } + public function render() { require_celerity_resource('phabricator-content-source-view-css'); diff --git a/src/view/phui/PHUITimelineEventView.php b/src/view/phui/PHUITimelineEventView.php index ecd9329860..0d9b75782f 100644 --- a/src/view/phui/PHUITimelineEventView.php +++ b/src/view/phui/PHUITimelineEventView.php @@ -232,11 +232,12 @@ final class PHUITimelineEventView extends AphrontView { $fill_classes = array(); $fill_classes[] = 'phui-timeline-icon-fill'; if ($this->color) { + $fill_classes[] = 'fill-has-color'; $fill_classes[] = 'phui-timeline-icon-fill-'.$this->color; } $icon = id(new PHUIIconView()) - ->setIcon($this->icon.' white') + ->setIcon($this->icon) ->addClass('phui-timeline-icon'); $icon = phutil_tag( @@ -504,11 +505,12 @@ final class PHUITimelineEventView extends AphrontView { } $source = $this->getContentSource(); + $content_source = null; if ($source) { - $extra[] = id(new PhabricatorContentSourceView()) + $content_source = id(new PhabricatorContentSourceView()) ->setContentSource($source) - ->setUser($this->getUser()) - ->render(); + ->setUser($this->getUser()); + $content_source = pht('Via %s', $content_source->getSourceName()); } $date_created = null; @@ -528,6 +530,7 @@ final class PHUITimelineEventView extends AphrontView { $this->getUser()); if ($this->anchor) { Javelin::initBehavior('phabricator-watch-anchor'); + Javelin::initBehavior('phabricator-tooltips'); $anchor = id(new PhabricatorAnchorView()) ->setAnchorName($this->anchor) @@ -535,10 +538,14 @@ final class PHUITimelineEventView extends AphrontView { $date = array( $anchor, - phutil_tag( + javelin_tag( 'a', array( 'href' => '#'.$this->anchor, + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => $content_source, + ), ), $date), ); diff --git a/webroot/rsrc/css/phui/phui-property-list-view.css b/webroot/rsrc/css/phui/phui-property-list-view.css index 3558bf7a9e..52e3ee0944 100644 --- a/webroot/rsrc/css/phui/phui-property-list-view.css +++ b/webroot/rsrc/css/phui/phui-property-list-view.css @@ -125,7 +125,7 @@ } .phui-property-list-text-content { - padding: 12px 4px; + padding: 16px 4px; overflow: hidden; } diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index d69df93953..ed4f77aa61 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -4,9 +4,9 @@ .phui-timeline-view { padding: 0 16px; - background-image: url('/rsrc/image/BFCFDA.png'); + background-image: url('/rsrc/image/d5d8e1.png'); background-repeat: repeat-y; - background-position: 94px; + background-position: 96px; } .device .phui-timeline-view { @@ -23,8 +23,8 @@ } .phui-timeline-major-event .phui-timeline-group { - border-left: 1px solid {$lightblueborder}; - border-right: 1px solid {$lightblueborder}; + border-left: 1px solid {$timeline}; + border-right: 1px solid {$timeline}; border-radius: 3px; } @@ -34,7 +34,7 @@ } .device-desktop .phui-timeline-event-view.phui-timeline-minor-event { - margin-left: 65px; + margin-left: 67px; } .device-desktop .phui-timeline-spacer { @@ -50,7 +50,7 @@ } .device-desktop .phui-timeline-wedge { - border-bottom: 1px solid {$lightblueborder}; + border-bottom: 1px solid {$timeline}; position: absolute; width: 12px; } @@ -61,25 +61,25 @@ } .phui-timeline-major-event .phui-timeline-content { - border-top: 1px solid {$lightblueborder}; - border-bottom: 1px solid {$lightblueborder}; + border-top: 1px solid {$timeline}; + border-bottom: 1px solid {$timeline}; border-radius: 3px; } .phui-timeline-title { - line-height: 22px; + line-height: 24px; min-height: 19px; position: relative; - color: {$bluetext}; + color: {$greytext}; } .phui-timeline-minor-event .phui-timeline-title { - padding: 4px 8px 4px 33px; + padding: 1px 8px 4px 33px; } .phui-timeline-title a { font-weight: bold; - color: {$darkbluetext}; + color: {$darkgreytext}; } .device-desktop .phui-timeline-wedge { @@ -91,7 +91,7 @@ } .device-desktop .phui-timeline-minor-event .phui-timeline-wedge { - top: 13px; + top: 12px; left: -18px; width: 20px; } @@ -100,7 +100,6 @@ background-repeat: no-repeat; position: absolute; border-radius: 3px; - box-shadow: {$borderinset}; background-size: 100%; display: block; } @@ -113,22 +112,29 @@ } .device-desktop .phui-timeline-minor-event .phui-timeline-image { - width: 28px; - height: 28px; - background-size: 28px auto; + width: 26px; + height: 26px; + background-size: 26px auto; left: -41px; } .phui-timeline-major-event .phui-timeline-title { - background: {$lightbluebackground}; + background: {$lightgreybackground}; min-height: 22px; border-top-right-radius: 3px; + border-top-left-radius: 3px; } -.phui-timeline-title + .phui-timeline-title { +.phui-timeline-major-event .phui-timeline-title + .phui-timeline-title { border-radius: 0; + padding-top: 0; } +.phui-timeline-major-event .phui-timeline-title + .phui-timeline-title + .phui-timeline-icon-fill { + margin-top: 0; + } + .phui-timeline-title { padding: 5px 8px; overflow-x: auto; @@ -136,7 +142,7 @@ } .phui-timeline-title-with-icon { - padding-left: 38px; + padding-left: 36px; } .phui-timeline-title-with-menu { @@ -170,9 +176,10 @@ .phui-timeline-major-event .phui-timeline-content .phui-timeline-core-content { - padding: 16px 12px; + padding: 16px; line-height: 18px; background: #fff; + border-top: 1px solid rgba({$alphablue},.1); border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; } @@ -203,52 +210,49 @@ border-width: 0; } -.phui-timeline-spacer.phui-timeline-spacer-bold { - border-bottom: 4px solid {$lightblueborder}; - margin: 0; -} - -.phui-timeline-spacer-bold + .phui-timeline-spacer { - background-color: #ebecee; -} - .phui-timeline-icon-fill { position: absolute; - width: 32px; - height: 32px; - background-color: {$lightblueborder}; + width: 34px; + height: 34px; top: 0; left: 0; text-align: center; } -.phui-icon-view.phui-timeline-icon:before { - font-size: 15px; +.phui-timeline-icon { + color: {$sh-blueicon}; } -.phui-timeline-minor-event .phui-timeline-icon-fill { - height: 28px; - width: 28px; +.phui-icon-view.phui-timeline-icon { + font-size: 14px; +} + +.phui-timeline-icon-fill { + height: 26px; + width: 26px; border-radius: 3px; + background-color: #E6E9F1; +} + +.phui-timeline-major-event .phui-timeline-icon-fill { + margin: 4px; } .phui-timeline-icon-fill .phui-timeline-icon { - margin-top: 8px; -} - -.phui-timeline-minor-event .phui-timeline-icon-fill .phui-timeline-icon { - margin-top: 7px; + margin-top: 6px; } .phui-timeline-extra, .phui-timeline-extra .phabricator-content-source-view { - font-size: {$smallestfontsize}; + font-size: {$smallerfontsize}; font-weight: normal; - color: {$lightbluetext}; + color: {$lightgreytext}; + margin-left: 8px; } .phui-timeline-title .phui-timeline-extra a { font-weight: normal; + color: {$lightgreytext}; } .device-desktop .phui-timeline-extra { @@ -267,6 +271,10 @@ margin: 0; } +.phui-timeline-icon-fill.fill-has-color .phui-icon-view { + color: #fff; +} + .phui-timeline-icon-fill-red { background-color: {$red}; } @@ -304,7 +312,7 @@ } .phui-timeline-icon-fill-black { - background-color: #333; + background-color: #000; } .phui-timeline-shell.anchor-target { @@ -343,7 +351,7 @@ .phui-timeline-title .phui-timeline-extra-information a { font-weight: normal; - color: {$bluetext}; + color: {$greytext}; } .phui-timeline-comment-actions .phui-icon-view { @@ -359,11 +367,11 @@ right: 3px; top: 6px; width: 28px; - height: 22px; + height: 24px; text-align: center; line-height: 22px; - font-size: 15px; - border-left: 1px solid {$lightblueborder}; + font-size: 16px; + border-left: 1px solid {$thinblueborder}; } .phui-timeline-menu:focus { @@ -379,11 +387,13 @@ a.phui-timeline-menu .phui-icon-view { } .device-desktop a.phui-timeline-menu:hover .phui-icon-view { - color: {$darkgreytext}; + color: {$sky}; } .phui-timeline-menu.phuix-dropdown-open { - background: {$hovergrey}; + background: rgba({$alphablue},0.1); + border: none; + border-radius: 3px; } .phui-timeline-view + .phui-object-box { diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index 156b061c07..090fafb63a 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -87,7 +87,7 @@ .phui-two-column-view .phui-timeline-view { padding: 0; - background-position: 78px; + background-position: 80px; } .phui-two-column-view .phui-main-column .phui-object-box + .phui-timeline-view { @@ -107,6 +107,10 @@ border-top: 1px solid {$thinblueborder}; } +.device-phone .phui-main-column .phui-timeline-older-transactions-are-hidden { + margin: 0; +} + /* Main Column Properties */ .device-desktop .phui-main-column .phui-property-list-key { diff --git a/webroot/rsrc/image/d5d8e1.png b/webroot/rsrc/image/d5d8e1.png new file mode 100644 index 0000000000000000000000000000000000000000..352aeb61dc89c07e2ae3e42da39a7fe24f3bf21b GIT binary patch literal 70 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k92}1TpU9 Date: Tue, 8 Mar 2016 13:07:22 -0800 Subject: [PATCH 36/57] Add Nuance daemons and item types Summary: Ref T10537. This adds an update daemon for pulling item data (e.g., figuring out who the author of a GitHub comment is) and routing items (e.g., sending them to a queue or applying them directly to a task). Also adds `bin/nuance update --item X` for doing this manually for debugging. And adds item types, for specializing item behavior. Previously, sources completely dictated item behavior, but I think we want something a little more flexible. Test Plan: - This still does nothing. - Ran `bin/nuance update --item 15`. - Saw an item route to a default queue. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10537 Differential Revision: https://secure.phabricator.com/D15441 --- src/__phutil_library_map__.php | 10 ++++ .../NuanceGitHubRepositoryImportCursor.php | 11 +--- .../nuance/item/NuanceGitHubEventItemType.php | 22 ++++++++ .../nuance/item/NuanceItemType.php | 37 ++++++++++++++ .../NuanceManagementImportWorkflow.php | 2 +- .../NuanceManagementUpdateWorkflow.php | 30 +++++++++++ .../management/NuanceManagementWorkflow.php | 50 +++++++++++++++++++ .../nuance/query/NuanceItemQuery.php | 11 ++++ .../nuance/storage/NuanceItem.php | 22 ++++++++ .../nuance/worker/NuanceItemUpdateWorker.php | 49 ++++++++++++++++++ .../nuance/worker/NuanceWorker.php | 25 ++++++++++ 11 files changed, 259 insertions(+), 10 deletions(-) create mode 100644 src/applications/nuance/item/NuanceGitHubEventItemType.php create mode 100644 src/applications/nuance/item/NuanceItemType.php create mode 100644 src/applications/nuance/management/NuanceManagementUpdateWorkflow.php create mode 100644 src/applications/nuance/worker/NuanceItemUpdateWorker.php create mode 100644 src/applications/nuance/worker/NuanceWorker.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 67b5d98eb3..b0f42aa6fb 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1420,6 +1420,7 @@ phutil_register_library_map(array( 'NuanceConsoleController' => 'applications/nuance/controller/NuanceConsoleController.php', 'NuanceController' => 'applications/nuance/controller/NuanceController.php', 'NuanceDAO' => 'applications/nuance/storage/NuanceDAO.php', + 'NuanceGitHubEventItemType' => 'applications/nuance/item/NuanceGitHubEventItemType.php', 'NuanceGitHubRepositoryImportCursor' => 'applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php', 'NuanceGitHubRepositorySourceDefinition' => 'applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php', 'NuanceImportCursor' => 'applications/nuance/cursor/NuanceImportCursor.php', @@ -1437,8 +1438,11 @@ phutil_register_library_map(array( 'NuanceItemTransaction' => 'applications/nuance/storage/NuanceItemTransaction.php', 'NuanceItemTransactionComment' => 'applications/nuance/storage/NuanceItemTransactionComment.php', 'NuanceItemTransactionQuery' => 'applications/nuance/query/NuanceItemTransactionQuery.php', + 'NuanceItemType' => 'applications/nuance/item/NuanceItemType.php', + 'NuanceItemUpdateWorker' => 'applications/nuance/worker/NuanceItemUpdateWorker.php', 'NuanceItemViewController' => 'applications/nuance/controller/NuanceItemViewController.php', 'NuanceManagementImportWorkflow' => 'applications/nuance/management/NuanceManagementImportWorkflow.php', + 'NuanceManagementUpdateWorkflow' => 'applications/nuance/management/NuanceManagementUpdateWorkflow.php', 'NuanceManagementWorkflow' => 'applications/nuance/management/NuanceManagementWorkflow.php', 'NuancePhabricatorFormSourceDefinition' => 'applications/nuance/source/NuancePhabricatorFormSourceDefinition.php', 'NuanceQuery' => 'applications/nuance/query/NuanceQuery.php', @@ -1488,6 +1492,7 @@ phutil_register_library_map(array( 'NuanceSourceTransactionQuery' => 'applications/nuance/query/NuanceSourceTransactionQuery.php', 'NuanceSourceViewController' => 'applications/nuance/controller/NuanceSourceViewController.php', 'NuanceTransaction' => 'applications/nuance/storage/NuanceTransaction.php', + 'NuanceWorker' => 'applications/nuance/worker/NuanceWorker.php', 'OwnersConduitAPIMethod' => 'applications/owners/conduit/OwnersConduitAPIMethod.php', 'OwnersEditConduitAPIMethod' => 'applications/owners/conduit/OwnersEditConduitAPIMethod.php', 'OwnersPackageReplyHandler' => 'applications/owners/mail/OwnersPackageReplyHandler.php', @@ -5671,6 +5676,7 @@ phutil_register_library_map(array( 'NuanceConsoleController' => 'NuanceController', 'NuanceController' => 'PhabricatorController', 'NuanceDAO' => 'PhabricatorLiskDAO', + 'NuanceGitHubEventItemType' => 'NuanceItemType', 'NuanceGitHubRepositoryImportCursor' => 'NuanceImportCursor', 'NuanceGitHubRepositorySourceDefinition' => 'NuanceSourceDefinition', 'NuanceImportCursor' => 'Phobject', @@ -5695,8 +5701,11 @@ phutil_register_library_map(array( 'NuanceItemTransaction' => 'NuanceTransaction', 'NuanceItemTransactionComment' => 'PhabricatorApplicationTransactionComment', 'NuanceItemTransactionQuery' => 'PhabricatorApplicationTransactionQuery', + 'NuanceItemType' => 'Phobject', + 'NuanceItemUpdateWorker' => 'NuanceWorker', 'NuanceItemViewController' => 'NuanceController', 'NuanceManagementImportWorkflow' => 'NuanceManagementWorkflow', + 'NuanceManagementUpdateWorkflow' => 'NuanceManagementWorkflow', 'NuanceManagementWorkflow' => 'PhabricatorManagementWorkflow', 'NuancePhabricatorFormSourceDefinition' => 'NuanceSourceDefinition', 'NuanceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', @@ -5759,6 +5768,7 @@ phutil_register_library_map(array( 'NuanceSourceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'NuanceSourceViewController' => 'NuanceSourceController', 'NuanceTransaction' => 'PhabricatorApplicationTransaction', + 'NuanceWorker' => 'PhabricatorWorker', 'OwnersConduitAPIMethod' => 'ConduitAPIMethod', 'OwnersEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'OwnersPackageReplyHandler' => 'PhabricatorMailReplyHandler', diff --git a/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php b/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php index d9874ff495..e65863d4af 100644 --- a/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php +++ b/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php @@ -166,14 +166,7 @@ final class NuanceGitHubRepositoryImportCursor $source->saveTransaction(); foreach ($new_items as $new_item) { - PhabricatorWorker::scheduleTask( - 'NuanceImportWorker', - array( - 'itemPHID' => $new_item->getPHID(), - ), - array( - 'objectPHID' => $new_item->getPHID(), - )); + $new_item->scheduleUpdate(); } return false; @@ -256,7 +249,7 @@ final class NuanceGitHubRepositoryImportCursor return NuanceItem::initializeNewItem() ->setStatus(NuanceItem::STATUS_IMPORTING) ->setSourcePHID($source->getPHID()) - ->setItemType('github.event') + ->setItemType(NuanceGitHubEventItemType::ITEMTYPE) ->setItemKey($item_key) ->setItemContainerKey($container_key) ->setItemProperty('api.raw', $record); diff --git a/src/applications/nuance/item/NuanceGitHubEventItemType.php b/src/applications/nuance/item/NuanceGitHubEventItemType.php new file mode 100644 index 0000000000..d292846e83 --- /dev/null +++ b/src/applications/nuance/item/NuanceGitHubEventItemType.php @@ -0,0 +1,22 @@ +getStatus() == NuanceItem::STATUS_IMPORTING) { + $item + ->setStatus(NuanceItem::STATUS_ROUTING) + ->save(); + } + } + +} diff --git a/src/applications/nuance/item/NuanceItemType.php b/src/applications/nuance/item/NuanceItemType.php new file mode 100644 index 0000000000..e0e0c739f1 --- /dev/null +++ b/src/applications/nuance/item/NuanceItemType.php @@ -0,0 +1,37 @@ +canUpdateItems()) { + throw new Exception( + pht( + 'This item type ("%s", of class "%s") can not update items.', + $this->getItemTypeConstant(), + get_class($this))); + } + + $this->updateItemFromSource($item); + } + + protected function updateItemFromSource(NuanceItem $item) { + throw new PhutilMethodNotImplementedException(); + } + + final public function getItemTypeConstant() { + return $this->getPhobjectClassConstant('ITEMTYPE', 64); + } + + final public static function getAllItemTypes() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getItemTypeConstant') + ->execute(); + } + +} diff --git a/src/applications/nuance/management/NuanceManagementImportWorkflow.php b/src/applications/nuance/management/NuanceManagementImportWorkflow.php index ef93ba0749..f20436dfd7 100644 --- a/src/applications/nuance/management/NuanceManagementImportWorkflow.php +++ b/src/applications/nuance/management/NuanceManagementImportWorkflow.php @@ -6,7 +6,7 @@ final class NuanceManagementImportWorkflow protected function didConstruct() { $this ->setName('import') - ->setExamples('**import** [__options__]') + ->setExamples('**import** --source __source__ [__options__]') ->setSynopsis(pht('Import data from a source.')) ->setArguments( array( diff --git a/src/applications/nuance/management/NuanceManagementUpdateWorkflow.php b/src/applications/nuance/management/NuanceManagementUpdateWorkflow.php new file mode 100644 index 0000000000..10878770f6 --- /dev/null +++ b/src/applications/nuance/management/NuanceManagementUpdateWorkflow.php @@ -0,0 +1,30 @@ +setName('update') + ->setExamples('**update** --item __item__ [__options__]') + ->setSynopsis(pht('Update or route an item.')) + ->setArguments( + array( + array( + 'name' => 'item', + 'param' => 'item', + 'help' => pht('Choose which item to route.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $item = $this->loadItem($args, 'item'); + + PhabricatorWorker::setRunAllTasksInProcess(true); + $item->scheduleUpdate(); + + return 0; + } + +} diff --git a/src/applications/nuance/management/NuanceManagementWorkflow.php b/src/applications/nuance/management/NuanceManagementWorkflow.php index f6dbfe6e09..2c7eb6b662 100644 --- a/src/applications/nuance/management/NuanceManagementWorkflow.php +++ b/src/applications/nuance/management/NuanceManagementWorkflow.php @@ -64,4 +64,54 @@ abstract class NuanceManagementWorkflow return head($sources); } + protected function loadITem(PhutilArgumentParser $argv, $key) { + $item = $argv->getArg($key); + if (!strlen($item)) { + throw new PhutilArgumentUsageException( + pht( + 'Specify a item with %s.', + '--'.$key)); + } + + $query = id(new NuanceItemQuery()) + ->setViewer($this->getViewer()) + ->setRaisePolicyExceptions(true); + + $type_unknown = PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN; + + if (ctype_digit($item)) { + $kind = 'id'; + $query->withIDs(array($item)); + } else if (phid_get_type($item) !== $type_unknown) { + $kind = 'phid'; + $query->withPHIDs($item); + } else { + throw new PhutilArgumentUsageException( + pht( + 'Specify the ID or PHID of an item to update. Parameter "%s" '. + 'is not an ID or PHID.', + $item)); + } + + $items = $query->execute(); + + if (!$items) { + switch ($kind) { + case 'id': + $message = pht( + 'No item exists with ID "%s".', + $item); + break; + case 'phid': + $message = pht( + 'No item exists with PHID "%s".', + $item); + break; + } + + throw new PhutilArgumentUsageException($message); + } + + return head($items); + } } diff --git a/src/applications/nuance/query/NuanceItemQuery.php b/src/applications/nuance/query/NuanceItemQuery.php index 2f731920c6..5bb0ec70ec 100644 --- a/src/applications/nuance/query/NuanceItemQuery.php +++ b/src/applications/nuance/query/NuanceItemQuery.php @@ -70,6 +70,17 @@ final class NuanceItemQuery $item->attachSource($source); } + $type_map = NuanceItemType::getAllItemTypes(); + foreach ($items as $key => $item) { + $type = idx($type_map, $item->getItemType()); + if (!$type) { + $this->didRejectResult($items[$key]); + unset($items[$key]); + continue; + } + $item->attachImplementation($type); + } + return $items; } diff --git a/src/applications/nuance/storage/NuanceItem.php b/src/applications/nuance/storage/NuanceItem.php index a51d24366d..1e2380822b 100644 --- a/src/applications/nuance/storage/NuanceItem.php +++ b/src/applications/nuance/storage/NuanceItem.php @@ -7,6 +7,7 @@ final class NuanceItem PhabricatorApplicationTransactionInterface { const STATUS_IMPORTING = 'importing'; + const STATUS_ROUTING = 'routing'; const STATUS_OPEN = 'open'; const STATUS_ASSIGNED = 'assigned'; const STATUS_CLOSED = 'closed'; @@ -23,6 +24,7 @@ final class NuanceItem protected $mailKey; private $source = self::ATTACHABLE; + private $implementation = self::ATTACHABLE; public static function initializeNewItem() { return id(new NuanceItem()) @@ -145,6 +147,26 @@ final class NuanceItem return pht('An Item'); } + public function scheduleUpdate() { + PhabricatorWorker::scheduleTask( + 'NuanceItemUpdateWorker', + array( + 'itemPHID' => $this->getPHID(), + ), + array( + 'objectPHID' => $this->getPHID(), + )); + } + + public function getImplementation() { + return $this->assertAttached($this->implementation); + } + + public function attachImplementation(NuanceItemType $type) { + $this->implementation = $type; + return $this; + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/nuance/worker/NuanceItemUpdateWorker.php b/src/applications/nuance/worker/NuanceItemUpdateWorker.php new file mode 100644 index 0000000000..919c9040d7 --- /dev/null +++ b/src/applications/nuance/worker/NuanceItemUpdateWorker.php @@ -0,0 +1,49 @@ +getTaskDataValue('itemPHID'); + + $hash = PhabricatorHash::digestForIndex($item_phid); + $lock_key = "nuance.item.{$hash}"; + $lock = PhabricatorGlobalLock::newLock($lock_key); + + $lock->lock(1); + try { + $item = $this->loadItem($item_phid); + $this->updateItem($item); + $this->routeItem($item); + } catch (Exception $ex) { + $lock->unlock(); + throw $ex; + } + + $lock->unlock(); + } + + private function updateItem(NuanceItem $item) { + $impl = $item->getImplementation(); + if ($impl->canUpdateItems()) { + $impl->updateItem($item); + } + } + + private function routeItem(NuanceItem $item) { + $status = $item->getStatus(); + if ($status != NuanceItem::STATUS_ROUTING) { + return; + } + + $source = $item->getSource(); + + // For now, always route items into the source's default queue. + + $item + ->setQueuePHID($source->getDefaultQueuePHID()) + ->setStatus(NuanceItem::STATUS_OPEN) + ->save(); + } + +} diff --git a/src/applications/nuance/worker/NuanceWorker.php b/src/applications/nuance/worker/NuanceWorker.php new file mode 100644 index 0000000000..5d09de9e69 --- /dev/null +++ b/src/applications/nuance/worker/NuanceWorker.php @@ -0,0 +1,25 @@ +setViewer($this->getViewer()) + ->withPHIDs(array($item_phid)) + ->executeOne(); + + if (!$item) { + throw new PhabricatorWorkerPermanentFailureException( + pht( + 'There is no Nuance item with PHID "%s".', + $item_phid)); + } + + return $item; + } + +} From 1392872c5c03216b65220158e97d00a91d963610 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 9 Mar 2016 08:57:02 -0800 Subject: [PATCH 37/57] Convert people manage page to two column Summary: Ref T10545, this brings flags back? and converts the layout to two column w/curtain Test Plan: View a few manage pages. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T10545 Differential Revision: https://secure.phabricator.com/D15449 --- ...abricatorPeopleProfileManageController.php | 48 ++++++++++--------- ...PhabricatorPeopleProfileViewController.php | 1 - 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/applications/people/controller/PhabricatorPeopleProfileManageController.php b/src/applications/people/controller/PhabricatorPeopleProfileManageController.php index 204fc7abda..51cf79ecff 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileManageController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileManageController.php @@ -35,17 +35,13 @@ final class PhabricatorPeopleProfileManageController $header = id(new PHUIHeaderView()) ->setHeader($user->getFullName()) ->setSubheader(array($profile_icon, $profile_title)) - ->setImage($picture); + ->setImage($picture) + ->setProfileHeader(true); - $actions = $this->buildActionList($user); + $curtain = $this->buildCurtain($user); $properties = $this->buildPropertyView($user); - $properties->setActionList($actions); $name = $user->getUsername(); - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); - $nav = $this->getProfileMenu(); $nav->selectFilter(PhabricatorPeopleProfilePanelEngine::PANEL_MANAGE); @@ -56,6 +52,16 @@ final class PhabricatorPeopleProfileManageController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Manage')); + $crumbs->setBorder(true); + + $manage = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('DETAILS'), $properties) + ->setMainColumn( + array( + $timeline, + )); return $this->newPage() ->setTitle( @@ -67,8 +73,7 @@ final class PhabricatorPeopleProfileManageController ->setCrumbs($crumbs) ->appendChild( array( - $object_box, - $timeline, + $manage, )); } @@ -87,18 +92,17 @@ final class PhabricatorPeopleProfileManageController return $view; } - private function buildActionList(PhabricatorUser $user) { + private function buildCurtain(PhabricatorUser $user) { $viewer = $this->getViewer(); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $user, PhabricatorPolicyCapability::CAN_EDIT); - $actions->addAction( + $curtain = $this->newCurtainView($user); + + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Profile')) @@ -106,7 +110,7 @@ final class PhabricatorPeopleProfileManageController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-picture-o') ->setName(pht('Edit Profile Picture')) @@ -114,7 +118,7 @@ final class PhabricatorPeopleProfileManageController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-wrench') ->setName(pht('Edit Settings')) @@ -134,7 +138,7 @@ final class PhabricatorPeopleProfileManageController $is_self = ($user->getPHID() === $viewer->getPHID()); $can_admin = ($is_admin && !$is_self); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon($empower_icon) ->setName($empower_name) @@ -142,7 +146,7 @@ final class PhabricatorPeopleProfileManageController ->setWorkflow(true) ->setHref($this->getApplicationURI('empower/'.$user->getID().'/'))); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-tag') ->setName(pht('Change Username')) @@ -158,7 +162,7 @@ final class PhabricatorPeopleProfileManageController $disable_name = pht('Disable User'); } - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon($disable_icon) ->setName($disable_name) @@ -166,7 +170,7 @@ final class PhabricatorPeopleProfileManageController ->setWorkflow(true) ->setHref($this->getApplicationURI('disable/'.$user->getID().'/'))); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-times') ->setName(pht('Delete User')) @@ -176,7 +180,7 @@ final class PhabricatorPeopleProfileManageController $can_welcome = ($is_admin && $user->canEstablishWebSessions()); - $actions->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setIcon('fa-envelope') ->setName(pht('Send Welcome Email')) @@ -184,7 +188,7 @@ final class PhabricatorPeopleProfileManageController ->setDisabled(!$can_welcome) ->setHref($this->getApplicationURI('welcome/'.$user->getID().'/'))); - return $actions; + return $curtain; } diff --git a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php index 60b02b01f4..d8bb1d4f4d 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php @@ -63,7 +63,6 @@ final class PhabricatorPeopleProfileViewController $home = id(new PHUITwoColumnView()) ->setHeader($header) - ->setFluid(true) ->addClass('project-view-home') ->setMainColumn( array( From 2da9fcafbf7dad1fc4c21bf4d35f7c516450bbb6 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 9 Mar 2016 09:09:16 -0800 Subject: [PATCH 38/57] Update project manage page for two column Summary: Fixes T10545. Converts layout to two column. Test Plan: Review a few project manage pages, see new layout and flag ability. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T10545 Differential Revision: https://secure.phabricator.com/D15450 --- .../PhabricatorProjectManageController.php | 53 +++++++++---------- .../PhabricatorProjectProfileController.php | 1 - 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/src/applications/project/controller/PhabricatorProjectManageController.php b/src/applications/project/controller/PhabricatorProjectManageController.php index 87420d1aa3..d84df87e93 100644 --- a/src/applications/project/controller/PhabricatorProjectManageController.php +++ b/src/applications/project/controller/PhabricatorProjectManageController.php @@ -30,12 +30,8 @@ final class PhabricatorProjectManageController $header->setStatus('fa-ban', 'red', pht('Archived')); } - $actions = $this->buildActionListView($project); - $properties = $this->buildPropertyListView($project, $actions); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); + $curtain = $this->buildCurtain($project); + $properties = $this->buildPropertyListView($project); $timeline = $this->buildTransactionTimeline( $project, @@ -47,6 +43,16 @@ final class PhabricatorProjectManageController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Manage')); + $crumbs->setBorder(true); + + $manage = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('DETAILS'), $properties) + ->setMainColumn( + array( + $timeline, + )); return $this->newPage() ->setNavigation($nav) @@ -58,26 +64,22 @@ final class PhabricatorProjectManageController )) ->appendChild( array( - $object_box, - $timeline, + $manage, )); } - private function buildActionListView(PhabricatorProject $project) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + private function buildCurtain(PhabricatorProject $project) { + $viewer = $this->getViewer(); $id = $project->getID(); - - $view = id(new PhabricatorActionListView()) - ->setUser($viewer); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $project, PhabricatorPolicyCapability::CAN_EDIT); - $view->addAction( + $curtain = $this->newCurtainView($project); + + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Details')) ->setIcon('fa-pencil') @@ -85,7 +87,7 @@ final class PhabricatorProjectManageController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Menu')) ->setIcon('fa-th-list') @@ -93,7 +95,7 @@ final class PhabricatorProjectManageController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Picture')) ->setIcon('fa-picture-o') @@ -102,7 +104,7 @@ final class PhabricatorProjectManageController ->setWorkflow(!$can_edit)); if ($project->isArchived()) { - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Activate Project')) ->setIcon('fa-check') @@ -110,7 +112,7 @@ final class PhabricatorProjectManageController ->setDisabled(!$can_edit) ->setWorkflow(true)); } else { - $view->addAction( + $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Archive Project')) ->setIcon('fa-ban') @@ -119,18 +121,15 @@ final class PhabricatorProjectManageController ->setWorkflow(true)); } - return $view; + return $curtain; } private function buildPropertyListView( - PhabricatorProject $project, - PhabricatorActionListView $actions) { - $request = $this->getRequest(); - $viewer = $request->getUser(); + PhabricatorProject $project) { + $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setActionList($actions); + ->setUser($viewer); $view->addProperty( pht('Looks Like'), diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index f61ebd28d2..c9f3c2b9d3 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -91,7 +91,6 @@ final class PhabricatorProjectProfileController $home = id(new PHUITwoColumnView()) ->setHeader($header) - ->setFluid(true) ->addClass('project-view-home') ->setMainColumn( array( From ee155ce8d28df8b3540a04faeb45304d8b195aaf Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 9 Mar 2016 04:01:17 -0800 Subject: [PATCH 39/57] Move Nuance Items to two-column views Summary: Ref T10537. Test Plan: {F1164796} {F1164797} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10537 Differential Revision: https://secure.phabricator.com/D15444 --- src/__phutil_library_map__.php | 4 +- .../PhabricatorNuanceApplication.php | 3 +- .../controller/NuanceItemEditController.php | 104 ----------------- .../controller/NuanceItemManageController.php | 109 ++++++++++++++++++ .../controller/NuanceItemViewController.php | 53 +++++---- 5 files changed, 138 insertions(+), 135 deletions(-) delete mode 100644 src/applications/nuance/controller/NuanceItemEditController.php create mode 100644 src/applications/nuance/controller/NuanceItemManageController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b0f42aa6fb..8f0f2db307 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1429,9 +1429,9 @@ phutil_register_library_map(array( 'NuanceImportCursorPHIDType' => 'applications/nuance/phid/NuanceImportCursorPHIDType.php', 'NuanceItem' => 'applications/nuance/storage/NuanceItem.php', 'NuanceItemController' => 'applications/nuance/controller/NuanceItemController.php', - 'NuanceItemEditController' => 'applications/nuance/controller/NuanceItemEditController.php', 'NuanceItemEditor' => 'applications/nuance/editor/NuanceItemEditor.php', 'NuanceItemListController' => 'applications/nuance/controller/NuanceItemListController.php', + 'NuanceItemManageController' => 'applications/nuance/controller/NuanceItemManageController.php', 'NuanceItemPHIDType' => 'applications/nuance/phid/NuanceItemPHIDType.php', 'NuanceItemQuery' => 'applications/nuance/query/NuanceItemQuery.php', 'NuanceItemSearchEngine' => 'applications/nuance/query/NuanceItemSearchEngine.php', @@ -5692,9 +5692,9 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', ), 'NuanceItemController' => 'NuanceController', - 'NuanceItemEditController' => 'NuanceController', 'NuanceItemEditor' => 'PhabricatorApplicationTransactionEditor', 'NuanceItemListController' => 'NuanceItemController', + 'NuanceItemManageController' => 'NuanceController', 'NuanceItemPHIDType' => 'PhabricatorPHIDType', 'NuanceItemQuery' => 'NuanceQuery', 'NuanceItemSearchEngine' => 'PhabricatorApplicationSearchEngine', diff --git a/src/applications/nuance/application/PhabricatorNuanceApplication.php b/src/applications/nuance/application/PhabricatorNuanceApplication.php index b11b23fa3a..92e89f8255 100644 --- a/src/applications/nuance/application/PhabricatorNuanceApplication.php +++ b/src/applications/nuance/application/PhabricatorNuanceApplication.php @@ -42,8 +42,7 @@ final class PhabricatorNuanceApplication extends PhabricatorApplication { 'item/' => array( $this->getQueryRoutePattern() => 'NuanceItemListController', 'view/(?P[1-9]\d*)/' => 'NuanceItemViewController', - 'edit/(?P[1-9]\d*)/' => 'NuanceItemEditController', - 'new/' => 'NuanceItemEditController', + 'manage/(?P[1-9]\d*)/' => 'NuanceItemManageController', ), 'source/' => array( $this->getQueryRoutePattern() => 'NuanceSourceListController', diff --git a/src/applications/nuance/controller/NuanceItemEditController.php b/src/applications/nuance/controller/NuanceItemEditController.php deleted file mode 100644 index 85d3c1cf85..0000000000 --- a/src/applications/nuance/controller/NuanceItemEditController.php +++ /dev/null @@ -1,104 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - $item = id(new NuanceItemQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->requireCapabilities( - array( - PhabricatorPolicyCapability::CAN_VIEW, - PhabricatorPolicyCapability::CAN_EDIT, - )) - ->executeOne(); - if (!$item) { - return new Aphront404Response(); - } - - $title = pht('Item %d', $item->getID()); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($title); - $crumbs->addTextCrumb(pht('Edit')); - - $properties = $this->buildPropertyView($item); - $actions = $this->buildActionView($item); - $properties->setActionList($actions); - - $box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->addPropertyList($properties); - - $timeline = $this->buildTransactionTimeline( - $item, - new NuanceItemTransactionQuery()); - - $timeline->setShouldTerminate(true); - - return $this->buildApplicationPage( - array( - $crumbs, - $box, - $timeline, - ), - array( - 'title' => $title, - )); - } - - private function buildPropertyView(NuanceItem $item) { - $viewer = $this->getViewer(); - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($item); - - $properties->addProperty( - pht('Date Created'), - phabricator_datetime($item->getDateCreated(), $viewer)); - - $properties->addProperty( - pht('Requestor'), - $viewer->renderHandle($item->getRequestorPHID())); - - $properties->addProperty( - pht('Source'), - $viewer->renderHandle($item->getSourcePHID())); - - $properties->addProperty( - pht('Queue'), - $viewer->renderHandle($item->getQueuePHID())); - - $source = $item->getSource(); - $definition = $source->getDefinition(); - - $definition->renderItemEditProperties( - $viewer, - $item, - $properties); - - return $properties; - } - - private function buildActionView(NuanceItem $item) { - $viewer = $this->getViewer(); - $id = $item->getID(); - - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); - - $actions->addAction( - id(new PhabricatorActionView()) - ->setName(pht('View Item')) - ->setIcon('fa-eye') - ->setHref($this->getApplicationURI("item/view/{$id}/"))); - - return $actions; - } - - -} diff --git a/src/applications/nuance/controller/NuanceItemManageController.php b/src/applications/nuance/controller/NuanceItemManageController.php new file mode 100644 index 0000000000..c86d2cd985 --- /dev/null +++ b/src/applications/nuance/controller/NuanceItemManageController.php @@ -0,0 +1,109 @@ +getViewer(); + $id = $request->getURIData('id'); + + $item = id(new NuanceItemQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->executeOne(); + if (!$item) { + return new Aphront404Response(); + } + + $title = pht('Item %d', $item->getID()); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb( + pht('Items'), + $this->getApplicationURI('item/')); + $crumbs->addTextCrumb( + $title, + $item->getURI()); + $crumbs->addTextCrumb(pht('Manage')); + $crumbs->setBorder(true); + + $properties = $this->buildPropertyView($item); + $curtain = $this->buildCurtain($item); + + $header = id(new PHUIHeaderView()) + ->setHeader($title); + + $timeline = $this->buildTransactionTimeline( + $item, + new NuanceItemTransactionQuery()); + $timeline->setShouldTerminate(true); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('DETAILS'), $properties) + ->setMainColumn($timeline); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } + + private function buildPropertyView(NuanceItem $item) { + $viewer = $this->getViewer(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $properties->addProperty( + pht('Date Created'), + phabricator_datetime($item->getDateCreated(), $viewer)); + + $requestor_phid = $item->getRequestorPHID(); + if ($requestor_phid) { + $requestor_view = $viewer->renderHandle($requestor_phid); + } else { + $requestor_view = phutil_tag('em', array(), pht('None')); + } + $properties->addProperty(pht('Requestor'), $requestor_view); + + $properties->addProperty( + pht('Source'), + $viewer->renderHandle($item->getSourcePHID())); + + $queue_phid = $item->getQueuePHID(); + if ($queue_phid) { + $queue_view = $viewer->renderHandle($queue_phid); + } else { + $queue_view = phutil_tag('em', array(), pht('None')); + } + $properties->addProperty(pht('Queue'), $queue_view); + + $source = $item->getSource(); + $definition = $source->getDefinition(); + + $definition->renderItemEditProperties( + $viewer, + $item, + $properties); + + return $properties; + } + + private function buildCurtain(NuanceItem $item) { + $viewer = $this->getViewer(); + $id = $item->getID(); + + $curtain = $this->newCurtainView($item); + + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('View Item')) + ->setIcon('fa-eye') + ->setHref($item->getURI())); + + return $curtain; + } + + +} diff --git a/src/applications/nuance/controller/NuanceItemViewController.php b/src/applications/nuance/controller/NuanceItemViewController.php index 5f50ea1aaf..0d20c03cda 100644 --- a/src/applications/nuance/controller/NuanceItemViewController.php +++ b/src/applications/nuance/controller/NuanceItemViewController.php @@ -17,32 +17,34 @@ final class NuanceItemViewController extends NuanceController { $title = pht('Item %d', $item->getID()); $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb( + pht('Items'), + $this->getApplicationURI('item/')); $crumbs->addTextCrumb($title); + $crumbs->setBorder(true); $properties = $this->buildPropertyView($item); - $actions = $this->buildActionView($item); - $properties->setActionList($actions); + $curtain = $this->buildCurtain($item); - $box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->addPropertyList($properties); + $header = id(new PHUIHeaderView()) + ->setHeader($title); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - ), - array( - 'title' => $title, - )); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setCurtain($curtain) + ->addPropertySection(pht('DETAILS'), $properties); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); } private function buildPropertyView(NuanceItem $item) { $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($item); + ->setUser($viewer); $properties->addProperty( pht('Date Created'), @@ -59,27 +61,24 @@ final class NuanceItemViewController extends NuanceController { return $properties; } - private function buildActionView(NuanceItem $item) { + private function buildCurtain(NuanceItem $item) { $viewer = $this->getViewer(); $id = $item->getID(); - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); - $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $item, PhabricatorPolicyCapability::CAN_EDIT); - $actions->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Edit Item')) - ->setIcon('fa-pencil') - ->setHref($this->getApplicationURI("item/edit/{$id}/")) - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit)); + $curtain = $this->newCurtainView($item); - return $actions; + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Manage Item')) + ->setIcon('fa-cogs') + ->setHref($this->getApplicationURI("item/manage/{$id}/"))); + + return $curtain; } From 1e83aef8808098655a9440ee1b004718c1a73045 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 9 Mar 2016 04:42:25 -0800 Subject: [PATCH 40/57] Give Nuance items some basic descriptive text Summary: Ref T10537. Ref T10538. Test Plan: {F1164858} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10537, T10538 Differential Revision: https://secure.phabricator.com/D15445 --- .../controller/NuanceItemViewController.php | 35 +++++---------- .../NuanceGitHubRepositoryImportCursor.php | 3 ++ .../nuance/item/NuanceGitHubEventItemType.php | 43 +++++++++++++++++++ .../nuance/item/NuanceItemType.php | 26 +++++++++++ .../nuance/phid/NuanceItemPHIDType.php | 2 +- .../nuance/query/NuanceItemSearchEngine.php | 6 ++- .../NuancePhabricatorFormSourceDefinition.php | 7 --- .../nuance/source/NuanceSourceDefinition.php | 7 --- .../nuance/storage/NuanceItem.php | 6 +-- 9 files changed, 90 insertions(+), 45 deletions(-) diff --git a/src/applications/nuance/controller/NuanceItemViewController.php b/src/applications/nuance/controller/NuanceItemViewController.php index 0d20c03cda..fd8fbc4563 100644 --- a/src/applications/nuance/controller/NuanceItemViewController.php +++ b/src/applications/nuance/controller/NuanceItemViewController.php @@ -15,6 +15,7 @@ final class NuanceItemViewController extends NuanceController { } $title = pht('Item %d', $item->getID()); + $name = $item->getDisplayName(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( @@ -23,16 +24,16 @@ final class NuanceItemViewController extends NuanceController { $crumbs->addTextCrumb($title); $crumbs->setBorder(true); - $properties = $this->buildPropertyView($item); $curtain = $this->buildCurtain($item); + $content = $this->buildContent($item); $header = id(new PHUIHeaderView()) - ->setHeader($title); + ->setHeader($name); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) - ->addPropertySection(pht('DETAILS'), $properties); + ->setMainColumn($content); return $this->newPage() ->setTitle($title) @@ -40,27 +41,6 @@ final class NuanceItemViewController extends NuanceController { ->appendChild($view); } - private function buildPropertyView(NuanceItem $item) { - $viewer = $this->getViewer(); - - $properties = id(new PHUIPropertyListView()) - ->setUser($viewer); - - $properties->addProperty( - pht('Date Created'), - phabricator_datetime($item->getDateCreated(), $viewer)); - - $source = $item->getSource(); - $definition = $source->getDefinition(); - - $definition->renderItemViewProperties( - $viewer, - $item, - $properties); - - return $properties; - } - private function buildCurtain(NuanceItem $item) { $viewer = $this->getViewer(); $id = $item->getID(); @@ -81,5 +61,12 @@ final class NuanceItemViewController extends NuanceController { return $curtain; } + private function buildContent(NuanceItem $item) { + $viewer = $this->getViewer(); + $impl = $item->getImplementation(); + + $impl->setViewer($viewer); + return $impl->buildItemView($item); + } } diff --git a/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php b/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php index e65863d4af..69e53b8dd0 100644 --- a/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php +++ b/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php @@ -158,6 +158,9 @@ final class NuanceGitHubRepositoryImportCursor $this->updatePolling($response, $now, true); + // Reverse the new items so we insert them in chronological order. + $new_items = array_reverse($new_items); + $source->openTransaction(); foreach ($new_items as $new_item) { $new_item->save(); diff --git a/src/applications/nuance/item/NuanceGitHubEventItemType.php b/src/applications/nuance/item/NuanceGitHubEventItemType.php index d292846e83..41e1e40a27 100644 --- a/src/applications/nuance/item/NuanceGitHubEventItemType.php +++ b/src/applications/nuance/item/NuanceGitHubEventItemType.php @@ -5,6 +5,49 @@ final class NuanceGitHubEventItemType const ITEMTYPE = 'github.event'; + public function getItemTypeDisplayName() { + return pht('GitHub Event'); + } + + public function getItemTypeDisplayIcon() { + return 'fa-github'; + } + + public function getItemDisplayName(NuanceItem $item) { + $raw = $item->getItemProperty('api.raw', array()); + + $repo = idxv($raw, array('repo', 'name'), pht('')); + + $type = idx($raw, 'type'); + switch ($type) { + case 'PushEvent': + $head = idxv($raw, array('payload', 'head')); + $head = substr($head, 0, 8); + $name = pht('Push %s', $head); + break; + case 'IssuesEvent': + $action = idxv($raw, array('payload', 'action')); + $number = idxv($raw, array('payload', 'issue', 'number')); + $name = pht('Issue #%d (%s)', $number, $action); + break; + case 'IssueCommentEvent': + $action = idxv($raw, array('payload', 'action')); + $number = idxv($raw, array('payload', 'issue', 'number')); + $name = pht('Issue #%d (Comment, %s)', $number, $action); + break; + case 'PullRequestEvent': + $action = idxv($raw, array('payload', 'action')); + $number = idxv($raw, array('payload', 'pull_request', 'number')); + $name = pht('Pull Request #%d (%s)', $number, $action); + break; + default: + $name = pht('Unknown Event ("%s")', $type); + break; + } + + return pht('GitHub %s %s', $repo, $name); + } + public function canUpdateItems() { return true; } diff --git a/src/applications/nuance/item/NuanceItemType.php b/src/applications/nuance/item/NuanceItemType.php index e0e0c739f1..1e0cc7a95c 100644 --- a/src/applications/nuance/item/NuanceItemType.php +++ b/src/applications/nuance/item/NuanceItemType.php @@ -3,10 +3,36 @@ abstract class NuanceItemType extends Phobject { + private $viewer; + + public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + public function canUpdateItems() { return false; } + final public function buildItemView(NuanceItem $item) { + return $this->newItemView($item); + } + + protected function newItemView() { + return null; + } + + public function getItemTypeDisplayIcon() { + return null; + } + + abstract public function getItemTypeDisplayName(); + abstract public function getItemDisplayName(NuanceItem $item); + final public function updateItem(NuanceItem $item) { if (!$this->canUpdateItems()) { throw new Exception( diff --git a/src/applications/nuance/phid/NuanceItemPHIDType.php b/src/applications/nuance/phid/NuanceItemPHIDType.php index f401c63594..ee51633ea9 100644 --- a/src/applications/nuance/phid/NuanceItemPHIDType.php +++ b/src/applications/nuance/phid/NuanceItemPHIDType.php @@ -33,7 +33,7 @@ final class NuanceItemPHIDType extends PhabricatorPHIDType { foreach ($handles as $phid => $handle) { $item = $objects[$phid]; - $handle->setName($item->getLabel($viewer)); + $handle->setName($item->getItemDisplayName()); $handle->setURI($item->getURI()); } } diff --git a/src/applications/nuance/query/NuanceItemSearchEngine.php b/src/applications/nuance/query/NuanceItemSearchEngine.php index cbc0e9f321..2f0951b4e0 100644 --- a/src/applications/nuance/query/NuanceItemSearchEngine.php +++ b/src/applications/nuance/query/NuanceItemSearchEngine.php @@ -61,12 +61,16 @@ final class NuanceItemSearchEngine $list = new PHUIObjectItemListView(); $list->setUser($viewer); foreach ($items as $item) { + $impl = $item->getImplementation(); + $view = id(new PHUIObjectItemView()) ->setObjectName(pht('Item %d', $item->getID())) ->setHeader($item->getDisplayName()) ->setHref($item->getURI()); - $view->addIcon('none', $item->getItemType()); + $view->addIcon( + $impl->getItemTypeDisplayIcon(), + $impl->getItemTypeDisplayName()); $list->addItem($view); } diff --git a/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php b/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php index ce7891f61e..a54211ee95 100644 --- a/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php +++ b/src/applications/nuance/source/NuancePhabricatorFormSourceDefinition.php @@ -72,13 +72,6 @@ final class NuancePhabricatorFormSourceDefinition return $box; } - public function renderItemViewProperties( - PhabricatorUser $viewer, - NuanceItem $item, - PHUIPropertyListView $view) { - $this->renderItemCommonProperties($viewer, $item, $view); - } - public function renderItemEditProperties( PhabricatorUser $viewer, NuanceItem $item, diff --git a/src/applications/nuance/source/NuanceSourceDefinition.php b/src/applications/nuance/source/NuanceSourceDefinition.php index e5168c3040..3552c8fe28 100644 --- a/src/applications/nuance/source/NuanceSourceDefinition.php +++ b/src/applications/nuance/source/NuanceSourceDefinition.php @@ -193,13 +193,6 @@ abstract class NuanceSourceDefinition extends Phobject { return $item; } - public function renderItemViewProperties( - PhabricatorUser $viewer, - NuanceItem $item, - PHUIPropertyListView $view) { - return; - } - public function renderItemEditProperties( PhabricatorUser $viewer, NuanceItem $item, diff --git a/src/applications/nuance/storage/NuanceItem.php b/src/applications/nuance/storage/NuanceItem.php index 1e2380822b..a83db3ec70 100644 --- a/src/applications/nuance/storage/NuanceItem.php +++ b/src/applications/nuance/storage/NuanceItem.php @@ -87,10 +87,6 @@ final class NuanceItem return '/nuance/item/view/'.$this->getID().'/'; } - public function getLabel(PhabricatorUser $viewer) { - return pht('TODO: An Item'); - } - public function getRequestor() { return $this->assertAttached($this->requestor); } @@ -144,7 +140,7 @@ final class NuanceItem } public function getDisplayName() { - return pht('An Item'); + return $this->getImplementation()->getItemDisplayName($this); } public function scheduleUpdate() { From 72889c09bf08a9b5b9166b2a9e7de95ee93fb398 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 9 Mar 2016 05:35:08 -0800 Subject: [PATCH 41/57] Split the GitHub import cursor into separate repository and issues event importers Summary: Ref T10538. The primary GitHub event activity stream does not report minor events (labels, milestones, etc). GitHub has a second, similar activity stream which does report these events (the "Issues Events API"). Use two separate cursors: one consumes the primary stream; the second consumes the events stream. One possible issue with this is that we may write events in a different order than they occurred, so GitHub shows "comment, label, close" but we show "comment, close, label" or similar. This is probably OK because the secondary API doesn't seem to have any very important events (e.g., it's probably fine if label changes are out-of-order), but we can conceivably put some buffer stage in between the two if it's an issue. Test Plan: {F1164894} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10538 Differential Revision: https://secure.phabricator.com/D15446 --- src/__phutil_library_map__.php | 6 +- .../cursor/NuanceGitHubImportCursor.php | 258 ++++++++++++++++++ .../cursor/NuanceGitHubIssuesImportCursor.php | 30 ++ .../NuanceGitHubRepositoryImportCursor.php | 230 +--------------- .../nuance/item/NuanceGitHubEventItemType.php | 21 ++ .../NuanceManagementImportWorkflow.php | 35 +++ ...NuanceGitHubRepositorySourceDefinition.php | 2 + .../nuance/source/NuanceSourceDefinition.php | 2 +- 8 files changed, 361 insertions(+), 223 deletions(-) create mode 100644 src/applications/nuance/cursor/NuanceGitHubImportCursor.php create mode 100644 src/applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 8f0f2db307..7a93f3390d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1421,6 +1421,8 @@ phutil_register_library_map(array( 'NuanceController' => 'applications/nuance/controller/NuanceController.php', 'NuanceDAO' => 'applications/nuance/storage/NuanceDAO.php', 'NuanceGitHubEventItemType' => 'applications/nuance/item/NuanceGitHubEventItemType.php', + 'NuanceGitHubImportCursor' => 'applications/nuance/cursor/NuanceGitHubImportCursor.php', + 'NuanceGitHubIssuesImportCursor' => 'applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php', 'NuanceGitHubRepositoryImportCursor' => 'applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php', 'NuanceGitHubRepositorySourceDefinition' => 'applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php', 'NuanceImportCursor' => 'applications/nuance/cursor/NuanceImportCursor.php', @@ -5677,7 +5679,9 @@ phutil_register_library_map(array( 'NuanceController' => 'PhabricatorController', 'NuanceDAO' => 'PhabricatorLiskDAO', 'NuanceGitHubEventItemType' => 'NuanceItemType', - 'NuanceGitHubRepositoryImportCursor' => 'NuanceImportCursor', + 'NuanceGitHubImportCursor' => 'NuanceImportCursor', + 'NuanceGitHubIssuesImportCursor' => 'NuanceGitHubImportCursor', + 'NuanceGitHubRepositoryImportCursor' => 'NuanceGitHubImportCursor', 'NuanceGitHubRepositorySourceDefinition' => 'NuanceSourceDefinition', 'NuanceImportCursor' => 'Phobject', 'NuanceImportCursorData' => array( diff --git a/src/applications/nuance/cursor/NuanceGitHubImportCursor.php b/src/applications/nuance/cursor/NuanceGitHubImportCursor.php new file mode 100644 index 0000000000..fc21e2f1d6 --- /dev/null +++ b/src/applications/nuance/cursor/NuanceGitHubImportCursor.php @@ -0,0 +1,258 @@ +getCursorProperty('github.poll.ttl'); + if ($ttl && ($ttl >= $now)) { + $this->logInfo( + pht( + 'Respecting "%s" or minimum poll delay: waiting for %s second(s) '. + 'to poll GitHub.', + 'X-Poll-Interval', + new PhutilNumber(1 + ($ttl - $now)))); + + return false; + } + + // Respect GitHub's API rate limiting. If we've exceeded the rate limit, + // wait until it resets to try again. + $limit = $this->getCursorProperty('github.limit.ttl'); + if ($limit && ($limit >= $now)) { + $this->logInfo( + pht( + 'Respecting "%s": waiting for %s second(s) to poll GitHub.', + 'X-RateLimit-Reset', + new PhutilNumber(1 + ($limit - $now)))); + return false; + } + + return true; + } + + final protected function pullDataFromSource() { + $viewer = $this->getViewer(); + $now = PhabricatorTime::getNow(); + + $source = $this->getSource(); + + $user = $source->getSourceProperty('github.user'); + $repository = $source->getSourceProperty('github.repository'); + $api_token = $source->getSourceProperty('github.token'); + + // This API only supports fetching 10 pages of 30 events each, for a total + // of 300 events. + $etag = null; + $new_items = array(); + $hit_known_items = false; + + $max_page = $this->getMaximumPage(); + $page_size = $this->getPageSize(); + + for ($page = 1; $page <= $max_page; $page++) { + $uri = $this->getGitHubAPIEndpointURI($user, $repository); + + $data = array( + 'page' => $page, + 'per_page' => $page_size, + ); + + $future = id(new PhutilGitHubFuture()) + ->setAccessToken($api_token) + ->setRawGitHubQuery($uri, $data); + + if ($page == 1) { + $cursor_etag = $this->getCursorProperty('github.poll.etag'); + if ($cursor_etag) { + $future->addHeader('If-None-Match', $cursor_etag); + } + } + + $this->logInfo( + pht( + 'Polling GitHub Repository API endpoint "%s".', + $uri)); + $response = $future->resolve(); + + // Do this first: if we hit the rate limit, we get a response but the + // body isn't valid. + $this->updateRateLimits($response); + + if ($response->getStatus()->getStatusCode() == 304) { + $this->logInfo( + pht( + 'Received a 304 Not Modified from GitHub, no new events.')); + } + + // This means we hit a rate limit or a "Not Modified" because of the + // "ETag" header. In either case, we should bail out. + if ($response->getStatus()->isError()) { + $this->updatePolling($response, $now, false); + $this->getCursorData()->save(); + return false; + } + + if ($page == 1) { + $etag = $response->getHeaderValue('ETag'); + } + + $records = $response->getBody(); + foreach ($records as $record) { + $item = $this->newNuanceItemFromGitHubRecord($record); + $item_key = $item->getItemKey(); + + $this->logInfo( + pht( + 'Fetched event "%s".', + $item_key)); + + $new_items[$item->getItemKey()] = $item; + } + + if ($new_items) { + $existing = id(new NuanceItemQuery()) + ->setViewer($viewer) + ->withSourcePHIDs(array($source->getPHID())) + ->withItemKeys(array_keys($new_items)) + ->execute(); + $existing = mpull($existing, null, 'getItemKey'); + foreach ($new_items as $key => $new_item) { + if (isset($existing[$key])) { + unset($new_items[$key]); + $hit_known_items = true; + + $this->logInfo( + pht( + 'Event "%s" is previously known.', + $key)); + } + } + } + + if ($hit_known_items) { + break; + } + + if (count($records) < $page_size) { + break; + } + } + + // TODO: When we go through the whole queue without hitting anything we + // have seen before, we should record some sort of global event so we + // can tell the user when the bridging started or was interrupted? + if (!$hit_known_items) { + $already_polled = $this->getCursorProperty('github.polled'); + if ($already_polled) { + // TODO: This is bad: we missed some items, maybe because too much + // stuff happened too fast or the daemons were broken for a long + // time. + } else { + // TODO: This is OK, we're doing the initial import. + } + } + + if ($etag !== null) { + $this->updateETag($etag); + } + + $this->updatePolling($response, $now, true); + + // Reverse the new items so we insert them in chronological order. + $new_items = array_reverse($new_items); + + $source->openTransaction(); + foreach ($new_items as $new_item) { + $new_item->save(); + } + $this->getCursorData()->save(); + $source->saveTransaction(); + + foreach ($new_items as $new_item) { + $new_item->scheduleUpdate(); + } + + return false; + } + + private function updateRateLimits(PhutilGitHubResponse $response) { + $remaining = $response->getHeaderValue('X-RateLimit-Remaining'); + $limit_reset = $response->getHeaderValue('X-RateLimit-Reset'); + $now = PhabricatorTime::getNow(); + + $limit_ttl = null; + if (strlen($remaining)) { + $remaining = (int)$remaining; + if (!$remaining) { + $limit_ttl = (int)$limit_reset; + } + } + + $this->setCursorProperty('github.limit.ttl', $limit_ttl); + + $this->logInfo( + pht( + 'This key has %s remaining API request(s), '. + 'limit resets in %s second(s).', + new PhutilNumber($remaining), + new PhutilNumber($limit_reset - $now))); + } + + private function updateETag($etag) { + + $this->setCursorProperty('github.poll.etag', $etag); + + $this->logInfo( + pht( + 'ETag for this request was "%s".', + $etag)); + } + + private function updatePolling( + PhutilGitHubResponse $response, + $start, + $success) { + + if ($success) { + $this->setCursorProperty('github.polled', true); + } + + $poll_interval = (int)$response->getHeaderValue('X-Poll-Interval'); + $poll_interval = max($this->getMinimumDelayBetweenPolls(), $poll_interval); + + $poll_ttl = $start + $poll_interval; + $this->setCursorProperty('github.poll.ttl', $poll_ttl); + + $now = PhabricatorTime::getNow(); + + $this->logInfo( + pht( + 'Set API poll TTL to +%s second(s) (%s second(s) from now).', + new PhutilNumber($poll_interval), + new PhutilNumber($poll_ttl - $now))); + } + +} diff --git a/src/applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php b/src/applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php new file mode 100644 index 0000000000..d250b1d02d --- /dev/null +++ b/src/applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php @@ -0,0 +1,30 @@ +getSource(); + + $id = $record['id']; + $item_key = "github.issueevent.{$id}"; + + $container_key = null; + + return NuanceItem::initializeNewItem() + ->setStatus(NuanceItem::STATUS_IMPORTING) + ->setSourcePHID($source->getPHID()) + ->setItemType(NuanceGitHubEventItemType::ITEMTYPE) + ->setItemKey($item_key) + ->setItemContainerKey($container_key) + ->setItemProperty('api.type', 'issue') + ->setItemProperty('api.raw', $record); + } + +} diff --git a/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php b/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php index 69e53b8dd0..72aca276d2 100644 --- a/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php +++ b/src/applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php @@ -1,236 +1,23 @@ getCursorProperty('github.poll.ttl'); - if ($ttl && ($ttl >= $now)) { - $this->logInfo( - pht( - 'Respecting "%s": waiting for %s second(s) to poll GitHub.', - 'X-Poll-Interval', - new PhutilNumber(1 + ($ttl - $now)))); - - return false; - } - - // Respect GitHub's API rate limiting. If we've exceeded the rate limit, - // wait until it resets to try again. - $limit = $this->getCursorProperty('github.limit.ttl'); - if ($limit && ($limit >= $now)) { - $this->logInfo( - pht( - 'Respecting "%s": waiting for %s second(s) to poll GitHub.', - 'X-RateLimit-Reset', - new PhutilNumber(1 + ($limit - $now)))); - return false; - } - - return true; + protected function getGitHubAPIEndpointURI($user, $repository) { + return "/repos/{$user}/{$repository}/events"; } - protected function pullDataFromSource() { - $viewer = $this->getViewer(); - $now = PhabricatorTime::getNow(); - - $source = $this->getSource(); - - $user = $source->getSourceProperty('github.user'); - $repository = $source->getSourceProperty('github.repository'); - $api_token = $source->getSourceProperty('github.token'); - - // This API only supports fetching 10 pages of 30 events each, for a total - // of 300 events. - $etag = null; - $new_items = array(); - $hit_known_items = false; - for ($page = 1; $page <= 10; $page++) { - $uri = "/repos/{$user}/{$repository}/events"; - $data = array( - 'page' => $page, - ); - - $future = id(new PhutilGitHubFuture()) - ->setAccessToken($api_token) - ->setRawGitHubQuery($uri, $data); - - if ($page == 1) { - $cursor_etag = $this->getCursorProperty('github.poll.etag'); - if ($cursor_etag) { - $future->addHeader('If-None-Match', $cursor_etag); - } - } - - $this->logInfo( - pht( - 'Polling GitHub Repository API endpoint "%s".', - $uri)); - $response = $future->resolve(); - - // Do this first: if we hit the rate limit, we get a response but the - // body isn't valid. - $this->updateRateLimits($response); - - if ($response->getStatus()->getStatusCode() == 304) { - $this->logInfo( - pht( - 'Received a 304 Not Modified from GitHub, no new events.')); - } - - // This means we hit a rate limit or a "Not Modified" because of the - // "ETag" header. In either case, we should bail out. - if ($response->getStatus()->isError()) { - $this->updatePolling($response, $now, false); - $this->getCursorData()->save(); - return false; - } - - if ($page == 1) { - $etag = $response->getHeaderValue('ETag'); - } - - $records = $response->getBody(); - foreach ($records as $record) { - $item = $this->newNuanceItemFromGitHubEvent($record); - $item_key = $item->getItemKey(); - - $this->logInfo( - pht( - 'Fetched event "%s".', - $item_key)); - - $new_items[$item->getItemKey()] = $item; - } - - if ($new_items) { - $existing = id(new NuanceItemQuery()) - ->setViewer($viewer) - ->withSourcePHIDs(array($source->getPHID())) - ->withItemKeys(array_keys($new_items)) - ->execute(); - $existing = mpull($existing, null, 'getItemKey'); - foreach ($new_items as $key => $new_item) { - if (isset($existing[$key])) { - unset($new_items[$key]); - $hit_known_items = true; - - $this->logInfo( - pht( - 'Event "%s" is previously known.', - $key)); - } - } - } - - if ($hit_known_items) { - break; - } - - if (count($records) < 30) { - break; - } - } - - // TODO: When we go through the whole queue without hitting anything we - // have seen before, we should record some sort of global event so we - // can tell the user when the bridging started or was interrupted? - if (!$hit_known_items) { - $already_polled = $this->getCursorProperty('github.polled'); - if ($already_polled) { - // TODO: This is bad: we missed some items, maybe because too much - // stuff happened too fast or the daemons were broken for a long - // time. - } else { - // TODO: This is OK, we're doing the initial import. - } - } - - if ($etag !== null) { - $this->updateETag($etag); - } - - $this->updatePolling($response, $now, true); - - // Reverse the new items so we insert them in chronological order. - $new_items = array_reverse($new_items); - - $source->openTransaction(); - foreach ($new_items as $new_item) { - $new_item->save(); - } - $this->getCursorData()->save(); - $source->saveTransaction(); - - foreach ($new_items as $new_item) { - $new_item->scheduleUpdate(); - } - - return false; + protected function getMaximumPage() { + return 10; } - private function updateRateLimits(PhutilGitHubResponse $response) { - $remaining = $response->getHeaderValue('X-RateLimit-Remaining'); - $limit_reset = $response->getHeaderValue('X-RateLimit-Reset'); - $now = PhabricatorTime::getNow(); - - $limit_ttl = null; - if (strlen($remaining)) { - $remaining = (int)$remaining; - if (!$remaining) { - $limit_ttl = (int)$limit_reset; - } - } - - $this->setCursorProperty('github.limit.ttl', $limit_ttl); - - $this->logInfo( - pht( - 'This key has %s remaining API request(s), '. - 'limit resets in %s second(s).', - new PhutilNumber($remaining), - new PhutilNumber($limit_reset - $now))); + protected function getPageSize() { + return 30; } - private function updateETag($etag) { - - $this->setCursorProperty('github.poll.etag', $etag); - - $this->logInfo( - pht( - 'ETag for this request was "%s".', - $etag)); - } - - private function updatePolling( - PhutilGitHubResponse $response, - $start, - $success) { - - if ($success) { - $this->setCursorProperty('github.polled', true); - } - - $poll_interval = (int)$response->getHeaderValue('X-Poll-Interval'); - $poll_ttl = $start + $poll_interval; - $this->setCursorProperty('github.poll.ttl', $poll_ttl); - - $now = PhabricatorTime::getNow(); - - $this->logInfo( - pht( - 'Set API poll TTL to +%s second(s) (%s second(s) from now).', - new PhutilNumber($poll_interval), - new PhutilNumber($poll_ttl - $now))); - } - - private function newNuanceItemFromGitHubEvent(array $record) { + protected function newNuanceItemFromGitHubRecord(array $record) { $source = $this->getSource(); $id = $record['id']; @@ -255,6 +42,7 @@ final class NuanceGitHubRepositoryImportCursor ->setItemType(NuanceGitHubEventItemType::ITEMTYPE) ->setItemKey($item_key) ->setItemContainerKey($container_key) + ->setItemProperty('api.type', 'repository') ->setItemProperty('api.raw', $record); } diff --git a/src/applications/nuance/item/NuanceGitHubEventItemType.php b/src/applications/nuance/item/NuanceGitHubEventItemType.php index 41e1e40a27..85894eda93 100644 --- a/src/applications/nuance/item/NuanceGitHubEventItemType.php +++ b/src/applications/nuance/item/NuanceGitHubEventItemType.php @@ -14,6 +14,27 @@ final class NuanceGitHubEventItemType } public function getItemDisplayName(NuanceItem $item) { + $api_type = $item->getItemProperty('api.type'); + switch ($api_type) { + case 'issue': + return $this->getGitHubIssueAPIEventDisplayName($item); + case 'repository': + return $this->getGitHubRepositoryAPIEventDisplayName($item); + default: + return pht('GitHub Event (Unknown API Type "%s")', $api_type); + } + } + + private function getGitHubIssueAPIEventDisplayName(NuanceItem $item) { + $raw = $item->getItemProperty('api.raw', array()); + + $action = idxv($raw, array('event')); + $number = idxv($raw, array('issue', 'number')); + + return pht('GitHub Issue #%d (%s)', $number, $action); + } + + private function getGitHubRepositoryAPIEventDisplayName(NuanceItem $item) { $raw = $item->getItemProperty('api.raw', array()); $repo = idxv($raw, array('repo', 'name'), pht('')); diff --git a/src/applications/nuance/management/NuanceManagementImportWorkflow.php b/src/applications/nuance/management/NuanceManagementImportWorkflow.php index f20436dfd7..b14537f885 100644 --- a/src/applications/nuance/management/NuanceManagementImportWorkflow.php +++ b/src/applications/nuance/management/NuanceManagementImportWorkflow.php @@ -15,6 +15,11 @@ final class NuanceManagementImportWorkflow 'param' => 'source', 'help' => pht('Choose which source to import.'), ), + array( + 'name' => 'cursor', + 'param' => 'cursor', + 'help' => pht('Import only a particular cursor.'), + ), )); } @@ -40,6 +45,36 @@ final class NuanceManagementImportWorkflow $source->getName())); } + $select = $args->getArg('cursor'); + if (strlen($select)) { + if (empty($cursors[$select])) { + throw new PhutilArgumentUsageException( + pht( + 'This source ("%s") does not have a "%s" cursor. Available '. + 'cursors: %s.', + $source->getName(), + $select, + implode(', ', array_keys($cursors)))); + } else { + echo tsprintf( + "%s\n", + pht( + 'Importing cursor "%s" only.', + $select)); + $cursors = array_select_keys($cursors, array($select)); + } + } else { + echo tsprintf( + "%s\n", + pht( + 'Importing all cursors: %s.', + implode(', ', array_keys($cursors)))); + + echo tsprintf( + "%s\n", + pht('(Use --cursor to import only a particular cursor.)')); + } + foreach ($cursors as $cursor) { $cursor->importFromSource(); } diff --git a/src/applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php b/src/applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php index e6684aef26..8768e35864 100644 --- a/src/applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php +++ b/src/applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php @@ -23,6 +23,8 @@ final class NuanceGitHubRepositorySourceDefinition return array( id(new NuanceGitHubRepositoryImportCursor()) ->setCursorKey('events.repository'), + id(new NuanceGitHubIssuesImportCursor()) + ->setCursorKey('events.issues'), ); } diff --git a/src/applications/nuance/source/NuanceSourceDefinition.php b/src/applications/nuance/source/NuanceSourceDefinition.php index 3552c8fe28..742837ef82 100644 --- a/src/applications/nuance/source/NuanceSourceDefinition.php +++ b/src/applications/nuance/source/NuanceSourceDefinition.php @@ -114,7 +114,7 @@ abstract class NuanceSourceDefinition extends Phobject { ->setCursorData($cursor_data); } - return $cursors; + return $map; } protected function newImportCursors() { From 638ccf9dcbd8e317d3d78c4ded0e952c3e41544a Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 9 Mar 2016 06:30:22 -0800 Subject: [PATCH 42/57] Begin bridging GitHub objects through Doorkeeper Summary: Ref T10538. This sets up a Doorkeeper bridge for GitHub issues, and pulls issues from GitHub to create ExternalObject references. Broadly, does nothing useful. Test Plan: Put a `var_dump()` in there somewhere and saw it probably do something when running `bin/nuance update --item 44`. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10538 Differential Revision: https://secure.phabricator.com/D15447 --- src/__phutil_library_map__.php | 6 ++ .../doorkeeper/bridge/DoorkeeperBridge.php | 10 ++ .../bridge/DoorkeeperBridgeGitHub.php | 50 ++++++++++ .../bridge/DoorkeeperBridgeGitHubIssue.php | 97 +++++++++++++++++++ .../engine/DoorkeeperImportEngine.php | 6 ++ .../nuance/github/NuanceGitHubRawEvent.php | 72 ++++++++++++++ .../nuance/item/NuanceGitHubEventItemType.php | 67 ++++++++++++- .../nuance/worker/NuanceItemUpdateWorker.php | 9 +- 8 files changed, 312 insertions(+), 5 deletions(-) create mode 100644 src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php create mode 100644 src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubIssue.php create mode 100644 src/applications/nuance/github/NuanceGitHubRawEvent.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7a93f3390d..a40dc5fc4e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -840,6 +840,8 @@ phutil_register_library_map(array( 'DoorkeeperAsanaRemarkupRule' => 'applications/doorkeeper/remarkup/DoorkeeperAsanaRemarkupRule.php', 'DoorkeeperBridge' => 'applications/doorkeeper/bridge/DoorkeeperBridge.php', 'DoorkeeperBridgeAsana' => 'applications/doorkeeper/bridge/DoorkeeperBridgeAsana.php', + 'DoorkeeperBridgeGitHub' => 'applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php', + 'DoorkeeperBridgeGitHubIssue' => 'applications/doorkeeper/bridge/DoorkeeperBridgeGitHubIssue.php', 'DoorkeeperBridgeJIRA' => 'applications/doorkeeper/bridge/DoorkeeperBridgeJIRA.php', 'DoorkeeperBridgeJIRATestCase' => 'applications/doorkeeper/bridge/__tests__/DoorkeeperBridgeJIRATestCase.php', 'DoorkeeperDAO' => 'applications/doorkeeper/storage/DoorkeeperDAO.php', @@ -1423,6 +1425,7 @@ phutil_register_library_map(array( 'NuanceGitHubEventItemType' => 'applications/nuance/item/NuanceGitHubEventItemType.php', 'NuanceGitHubImportCursor' => 'applications/nuance/cursor/NuanceGitHubImportCursor.php', 'NuanceGitHubIssuesImportCursor' => 'applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php', + 'NuanceGitHubRawEvent' => 'applications/nuance/github/NuanceGitHubRawEvent.php', 'NuanceGitHubRepositoryImportCursor' => 'applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php', 'NuanceGitHubRepositorySourceDefinition' => 'applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php', 'NuanceImportCursor' => 'applications/nuance/cursor/NuanceImportCursor.php', @@ -4970,6 +4973,8 @@ phutil_register_library_map(array( 'DoorkeeperAsanaRemarkupRule' => 'DoorkeeperRemarkupRule', 'DoorkeeperBridge' => 'Phobject', 'DoorkeeperBridgeAsana' => 'DoorkeeperBridge', + 'DoorkeeperBridgeGitHub' => 'DoorkeeperBridge', + 'DoorkeeperBridgeGitHubIssue' => 'DoorkeeperBridgeGitHub', 'DoorkeeperBridgeJIRA' => 'DoorkeeperBridge', 'DoorkeeperBridgeJIRATestCase' => 'PhabricatorTestCase', 'DoorkeeperDAO' => 'PhabricatorLiskDAO', @@ -5681,6 +5686,7 @@ phutil_register_library_map(array( 'NuanceGitHubEventItemType' => 'NuanceItemType', 'NuanceGitHubImportCursor' => 'NuanceImportCursor', 'NuanceGitHubIssuesImportCursor' => 'NuanceGitHubImportCursor', + 'NuanceGitHubRawEvent' => 'Phobject', 'NuanceGitHubRepositoryImportCursor' => 'NuanceGitHubImportCursor', 'NuanceGitHubRepositorySourceDefinition' => 'NuanceSourceDefinition', 'NuanceImportCursor' => 'Phobject', diff --git a/src/applications/doorkeeper/bridge/DoorkeeperBridge.php b/src/applications/doorkeeper/bridge/DoorkeeperBridge.php index 2061150aba..b09a1933c1 100644 --- a/src/applications/doorkeeper/bridge/DoorkeeperBridge.php +++ b/src/applications/doorkeeper/bridge/DoorkeeperBridge.php @@ -3,6 +3,7 @@ abstract class DoorkeeperBridge extends Phobject { private $viewer; + private $context = array(); private $throwOnMissingLink; public function setThrowOnMissingLink($throw_on_missing_link) { @@ -19,6 +20,15 @@ abstract class DoorkeeperBridge extends Phobject { return $this->viewer; } + final public function setContext($context) { + $this->context = $context; + return $this; + } + + final public function getContextProperty($key, $default = null) { + return idx($this->context, $key, $default); + } + public function isEnabled() { return true; } diff --git a/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php new file mode 100644 index 0000000000..19ad54d977 --- /dev/null +++ b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHub.php @@ -0,0 +1,50 @@ +getApplicationType() != self::APPTYPE_GITHUB) { + return false; + } + + if ($ref->getApplicationDomain() != self::APPDOMAIN_GITHUB) { + return false; + } + + return true; + } + + protected function getGitHubAccessToken() { + $context_token = $this->getContextProperty('github.token'); + if ($context_token) { + return $context_token->openEnvelope(); + } + + // TODO: Do a bunch of work to fetch the viewer's linked account if + // they have one. + + return $this->didFailOnMissingLink(); + } + + protected function parseGitHubIssueID($id) { + $matches = null; + if (!preg_match('(^([^/]+)/([^/]+)#([1-9]\d*)\z)', $id, $matches)) { + throw new Exception( + pht( + 'GitHub Issue ID "%s" is not properly formatted. Expected an ID '. + 'in the form "owner/repository#123".', + $id)); + } + + return array( + $matches[1], + $matches[2], + (int)$matches[3], + ); + } + + +} diff --git a/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubIssue.php b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubIssue.php new file mode 100644 index 0000000000..50dc28bf9d --- /dev/null +++ b/src/applications/doorkeeper/bridge/DoorkeeperBridgeGitHubIssue.php @@ -0,0 +1,97 @@ +getObjectType() !== self::OBJTYPE_GITHUB_ISSUE) { + return false; + } + + return true; + } + + public function pullRefs(array $refs) { + $token = $this->getGitHubAccessToken(); + if (!strlen($token)) { + return null; + } + + $template = id(new PhutilGitHubFuture()) + ->setAccessToken($token); + + $futures = array(); + $id_map = mpull($refs, 'getObjectID', 'getObjectKey'); + foreach ($id_map as $key => $id) { + list($user, $repository, $number) = $this->parseGitHubIssueID($id); + $uri = "/repos/{$user}/{$repository}/issues/{$number}"; + $data = array(); + $futures[$key] = id(clone $template) + ->setRawGitHubQuery($uri, $data); + } + + $results = array(); + $failed = array(); + foreach (new FutureIterator($futures) as $key => $future) { + try { + $results[$key] = $future->resolve(); + } catch (Exception $ex) { + if (($ex instanceof HTTPFutureResponseStatus) && + ($ex->getStatusCode() == 404)) { + // TODO: Do we end up here for deleted objects and invisible + // objects? + } else { + phlog($ex); + $failed[$key] = $ex; + } + } + } + + $viewer = $this->getViewer(); + + foreach ($refs as $ref) { + $ref->setAttribute('name', pht('GitHub Issue %s', $ref->getObjectID())); + + $did_fail = idx($failed, $ref->getObjectKey()); + if ($did_fail) { + $ref->setSyncFailed(true); + continue; + } + + $result = idx($results, $ref->getObjectKey()); + if (!$result) { + continue; + } + + $body = $result->getBody(); + + $ref->setIsVisible(true); + $ref->setAttribute('api.raw', $body); + $ref->setAttribute('name', $body['title']); + + $obj = $ref->getExternalObject(); + if ($obj->getID()) { + continue; + } + + $this->fillObjectFromData($obj, $result); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $obj->save(); + unset($unguarded); + } + } + + public function fillObjectFromData(DoorkeeperExternalObject $obj, $result) { + $body = $result->getBody(); + $uri = $body['html_url']; + $obj->setObjectURI($uri); + } + +} diff --git a/src/applications/doorkeeper/engine/DoorkeeperImportEngine.php b/src/applications/doorkeeper/engine/DoorkeeperImportEngine.php index e4e55b2528..8c58f5bd88 100644 --- a/src/applications/doorkeeper/engine/DoorkeeperImportEngine.php +++ b/src/applications/doorkeeper/engine/DoorkeeperImportEngine.php @@ -7,6 +7,7 @@ final class DoorkeeperImportEngine extends Phobject { private $phids = array(); private $localOnly; private $throwOnMissingLink; + private $context = array(); public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -37,6 +38,10 @@ final class DoorkeeperImportEngine extends Phobject { return $this; } + public function setContextProperty($key, $value) { + $this->context[$key] = $value; + return $this; + } /** * Configure behavior if remote refs can not be retrieved because an @@ -96,6 +101,7 @@ final class DoorkeeperImportEngine extends Phobject { foreach ($bridges as $key => $bridge) { $bridge->setViewer($viewer); $bridge->setThrowOnMissingLink($this->throwOnMissingLink); + $bridge->setContext($this->context); } $working_set = $refs; diff --git a/src/applications/nuance/github/NuanceGitHubRawEvent.php b/src/applications/nuance/github/NuanceGitHubRawEvent.php new file mode 100644 index 0000000000..fa7a79d70f --- /dev/null +++ b/src/applications/nuance/github/NuanceGitHubRawEvent.php @@ -0,0 +1,72 @@ +type = $type; + $event->raw = $raw; + return $event; + } + + public function getRepositoryFullName() { + return $this->getRepositoryFullRawName(); + } + + public function isIssueEvent() { + if ($this->isPullRequestEvent()) { + return false; + } + + if ($this->type == self::TYPE_ISSUE) { + return true; + } + + switch ($this->getIssueRawKind()) { + case 'IssuesEvent': + case 'IssuesCommentEvent': + return true; + } + + return false; + } + + public function isPullRequestEvent() { + return false; + } + + public function getIssueNumber() { + if (!$this->isIssueEvent()) { + return null; + } + + $raw = $this->raw; + + if ($this->type == self::TYPE_ISSUE) { + return idxv($raw, array('issue', 'number')); + } + + if ($this->type == self::TYPE_REPOSITORY) { + return idxv($raw, array('payload', 'issue', 'number')); + } + + return null; + } + + private function getRepositoryFullRawName() { + $raw = $this->raw; + return idxv($raw, array('repo', 'name')); + } + + private function getIssueRawKind() { + $raw = $this->raw; + return idxv($raw, array('type')); + } + +} diff --git a/src/applications/nuance/item/NuanceGitHubEventItemType.php b/src/applications/nuance/item/NuanceGitHubEventItemType.php index 85894eda93..1e6a72b3b3 100644 --- a/src/applications/nuance/item/NuanceGitHubEventItemType.php +++ b/src/applications/nuance/item/NuanceGitHubEventItemType.php @@ -74,13 +74,74 @@ final class NuanceGitHubEventItemType } protected function updateItemFromSource(NuanceItem $item) { + $viewer = $this->getViewer(); + $is_dirty = false; + // TODO: Link up the requestor, etc. + $source = $item->getSource(); + $token = $source->getSourceProperty('github.token'); + $token = new PhutilOpaqueEnvelope($token); + + $ref = $this->getDoorkeeperRef($item); + if ($ref) { + $ref = id(new DoorkeeperImportEngine()) + ->setViewer($viewer) + ->setRefs(array($ref)) + ->setThrowOnMissingLink(true) + ->setContextProperty('github.token', $token) + ->executeOne(); + + if ($ref->getSyncFailed()) { + $xobj = null; + } else { + $xobj = $ref->getExternalObject(); + } + + if ($xobj) { + $item->setItemProperty('doorkeeper.xobj.phid', $xobj->getPHID()); + $is_dirty = true; + } + } + if ($item->getStatus() == NuanceItem::STATUS_IMPORTING) { - $item - ->setStatus(NuanceItem::STATUS_ROUTING) - ->save(); + $item->setStatus(NuanceItem::STATUS_ROUTING); + $is_dirty = true; + } + + if ($is_dirty) { + $item->save(); } } + private function getDoorkeeperRef(NuanceItem $item) { + $raw = $this->newRawEvent($item); + + $full_repository = $raw->getRepositoryFullName(); + if (!strlen($full_repository)) { + return null; + } + + if ($raw->isIssueEvent()) { + $ref_type = DoorkeeperBridgeGitHubIssue::OBJTYPE_GITHUB_ISSUE; + $issue_number = $raw->getIssueNumber(); + $full_ref = "{$full_repository}#{$issue_number}"; + } else { + return null; + } + + return id(new DoorkeeperObjectRef()) + ->setApplicationType(DoorkeeperBridgeGitHub::APPTYPE_GITHUB) + ->setApplicationDomain(DoorkeeperBridgeGitHub::APPDOMAIN_GITHUB) + ->setObjectType($ref_type) + ->setObjectID($full_ref); + } + + private function newRawEvent(NuanceItem $item) { + $type = $item->getItemProperty('api.type'); + $raw = $item->getItemProperty('api.raw', array()); + + return NuanceGitHubRawEvent::newEvent($type, $raw); + } + } diff --git a/src/applications/nuance/worker/NuanceItemUpdateWorker.php b/src/applications/nuance/worker/NuanceItemUpdateWorker.php index 919c9040d7..c8c65e33ea 100644 --- a/src/applications/nuance/worker/NuanceItemUpdateWorker.php +++ b/src/applications/nuance/worker/NuanceItemUpdateWorker.php @@ -25,9 +25,14 @@ final class NuanceItemUpdateWorker private function updateItem(NuanceItem $item) { $impl = $item->getImplementation(); - if ($impl->canUpdateItems()) { - $impl->updateItem($item); + if (!$impl->canUpdateItems()) { + return null; } + + $viewer = $this->getViewer(); + + $impl->setViewer($viewer); + $impl->updateItem($item); } private function routeItem(NuanceItem $item) { From d7cd2a9b9c92b298b719ece611158c069037d2ee Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 9 Mar 2016 07:01:23 -0800 Subject: [PATCH 43/57] Begin adding test coverage to GitHub Events API parsers Summary: Ref T10538. This is a tiny fraction of the API. GitHub has 25 primary event types; we currently partially parse 3 of them. GitHub has 17 issue event types; we currently partially parse 12. Test Plan: Ran `arc unit`. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10538 Differential Revision: https://secure.phabricator.com/D15448 --- .arclint | 14 +- src/__phutil_library_map__.php | 2 + .../nuance/github/NuanceGitHubRawEvent.php | 85 ++++- .../NuanceGitHubRawEventTestCase.php | 108 ++++++ .../github/__tests__/issueevents/assigned.txt | 114 ++++++ .../github/__tests__/issueevents/closed.txt | 76 ++++ .../__tests__/issueevents/demilestoned.txt | 79 +++++ .../github/__tests__/issueevents/labeled.txt | 80 +++++ .../github/__tests__/issueevents/locked.txt | 76 ++++ .../__tests__/issueevents/milestoned.txt | 79 +++++ .../github/__tests__/issueevents/renamed.txt | 80 +++++ .../github/__tests__/issueevents/reopened.txt | 76 ++++ .../__tests__/issueevents/unassigned.txt | 114 ++++++ .../__tests__/issueevents/unlabeled.txt | 80 +++++ .../github/__tests__/issueevents/unlocked.txt | 76 ++++ .../IssueCommentEvent.created.pull.txt | 161 +++++++++ .../IssueCommentEvent.created.txt | 98 +++++ .../repositoryevents/IssuesEvent.closed.txt | 70 ++++ .../repositoryevents/IssuesEvent.opened.txt | 70 ++++ .../repositoryevents/IssuesEvent.reopened.txt | 70 ++++ .../PullRequestEvent.opened.txt | 334 ++++++++++++++++++ .../__tests__/repositoryevents/PushEvent.txt | 45 +++ .../repositoryevents/WatchEvent.started.txt | 29 ++ 23 files changed, 2005 insertions(+), 11 deletions(-) create mode 100644 src/applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php create mode 100644 src/applications/nuance/github/__tests__/issueevents/assigned.txt create mode 100644 src/applications/nuance/github/__tests__/issueevents/closed.txt create mode 100644 src/applications/nuance/github/__tests__/issueevents/demilestoned.txt create mode 100644 src/applications/nuance/github/__tests__/issueevents/labeled.txt create mode 100644 src/applications/nuance/github/__tests__/issueevents/locked.txt create mode 100644 src/applications/nuance/github/__tests__/issueevents/milestoned.txt create mode 100644 src/applications/nuance/github/__tests__/issueevents/renamed.txt create mode 100644 src/applications/nuance/github/__tests__/issueevents/reopened.txt create mode 100644 src/applications/nuance/github/__tests__/issueevents/unassigned.txt create mode 100644 src/applications/nuance/github/__tests__/issueevents/unlabeled.txt create mode 100644 src/applications/nuance/github/__tests__/issueevents/unlocked.txt create mode 100644 src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.pull.txt create mode 100644 src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.txt create mode 100644 src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.closed.txt create mode 100644 src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.opened.txt create mode 100644 src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.reopened.txt create mode 100644 src/applications/nuance/github/__tests__/repositoryevents/PullRequestEvent.opened.txt create mode 100644 src/applications/nuance/github/__tests__/repositoryevents/PushEvent.txt create mode 100644 src/applications/nuance/github/__tests__/repositoryevents/WatchEvent.started.txt diff --git a/.arclint b/.arclint index 1b5b63976a..6047e2ccb6 100644 --- a/.arclint +++ b/.arclint @@ -61,7 +61,19 @@ "type": "spelling" }, "text": { - "type": "text" + "type": "text", + "exclude": [ + "(^src/(.*/)?__tests__/[^/]+/.*\\.(txt|json))" + ] + }, + "text-without-length": { + "type": "text", + "include": [ + "(^src/(.*/)?__tests__/[^/]+/.*\\.(txt|json))" + ], + "severity": { + "3": "disabled" + } }, "xhpast": { "type": "xhpast", diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index a40dc5fc4e..0d86575126 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1426,6 +1426,7 @@ phutil_register_library_map(array( 'NuanceGitHubImportCursor' => 'applications/nuance/cursor/NuanceGitHubImportCursor.php', 'NuanceGitHubIssuesImportCursor' => 'applications/nuance/cursor/NuanceGitHubIssuesImportCursor.php', 'NuanceGitHubRawEvent' => 'applications/nuance/github/NuanceGitHubRawEvent.php', + 'NuanceGitHubRawEventTestCase' => 'applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php', 'NuanceGitHubRepositoryImportCursor' => 'applications/nuance/cursor/NuanceGitHubRepositoryImportCursor.php', 'NuanceGitHubRepositorySourceDefinition' => 'applications/nuance/source/NuanceGitHubRepositorySourceDefinition.php', 'NuanceImportCursor' => 'applications/nuance/cursor/NuanceImportCursor.php', @@ -5687,6 +5688,7 @@ phutil_register_library_map(array( 'NuanceGitHubImportCursor' => 'NuanceImportCursor', 'NuanceGitHubIssuesImportCursor' => 'NuanceGitHubImportCursor', 'NuanceGitHubRawEvent' => 'Phobject', + 'NuanceGitHubRawEventTestCase' => 'PhabricatorTestCase', 'NuanceGitHubRepositoryImportCursor' => 'NuanceGitHubImportCursor', 'NuanceGitHubRepositorySourceDefinition' => 'NuanceSourceDefinition', 'NuanceImportCursor' => 'Phobject', diff --git a/src/applications/nuance/github/NuanceGitHubRawEvent.php b/src/applications/nuance/github/NuanceGitHubRawEvent.php index fa7a79d70f..50ab9020ef 100644 --- a/src/applications/nuance/github/NuanceGitHubRawEvent.php +++ b/src/applications/nuance/github/NuanceGitHubRawEvent.php @@ -30,14 +30,35 @@ final class NuanceGitHubRawEvent extends Phobject { switch ($this->getIssueRawKind()) { case 'IssuesEvent': - case 'IssuesCommentEvent': return true; + case 'IssueCommentEvent': + if (!$this->getRawPullRequestData()) { + return true; + } + break; } return false; } public function isPullRequestEvent() { + if ($this->type == self::TYPE_ISSUE) { + // TODO: This is wrong, some of these are pull events. + return false; + } + + $raw = $this->raw; + + switch ($this->getIssueRawKind()) { + case 'PullRequestEvent': + return true; + case 'IssueCommentEvent': + if ($this->getRawPullRequestData()) { + return true; + } + break; + } + return false; } @@ -46,22 +67,39 @@ final class NuanceGitHubRawEvent extends Phobject { return null; } - $raw = $this->raw; + return $this->getRawIssueNumber(); + } - if ($this->type == self::TYPE_ISSUE) { - return idxv($raw, array('issue', 'number')); + public function getPullRequestNumber() { + if (!$this->isPullRequestEvent()) { + return null; } - if ($this->type == self::TYPE_REPOSITORY) { - return idxv($raw, array('payload', 'issue', 'number')); - } - - return null; + return $this->getRawIssueNumber(); } private function getRepositoryFullRawName() { $raw = $this->raw; - return idxv($raw, array('repo', 'name')); + + $full = idxv($raw, array('repo', 'name')); + if (strlen($full)) { + return $full; + } + + // For issue events, the repository is not identified explicitly in the + // response body. Parse it out of the URI. + + $matches = null; + $ok = preg_match( + '(/repos/((?:[^/]+)/(?:[^/]+))/issues/events/)', + idx($raw, 'url'), + $matches); + + if ($ok) { + return $matches[1]; + } + + return null; } private function getIssueRawKind() { @@ -69,4 +107,31 @@ final class NuanceGitHubRawEvent extends Phobject { return idxv($raw, array('type')); } + private function getRawIssueNumber() { + $raw = $this->raw; + + if ($this->type == self::TYPE_ISSUE) { + return idxv($raw, array('issue', 'number')); + } + + if ($this->type == self::TYPE_REPOSITORY) { + $issue_number = idxv($raw, array('payload', 'issue', 'number')); + if ($issue_number) { + return $issue_number; + } + + $pull_number = idxv($raw, array('payload', 'number')); + if ($pull_number) { + return $pull_number; + } + } + + return null; + } + + private function getRawPullRequestData() { + $raw = $this->raw; + return idxv($raw, array('payload', 'issue', 'pull_request')); + } + } diff --git a/src/applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php b/src/applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php new file mode 100644 index 0000000000..f956079b6a --- /dev/null +++ b/src/applications/nuance/github/__tests__/NuanceGitHubRawEventTestCase.php @@ -0,0 +1,108 @@ +readTestCases($path); + + foreach ($cases as $name => $info) { + $input = $info['input']; + $expect = $info['expect']; + + $event = NuanceGitHubRawEvent::newEvent( + NuanceGitHubRawEvent::TYPE_ISSUE, + $input); + + $this->assertGitHubRawEventParse($expect, $event, $name); + } + } + + public function testRepositoryEvents() { + $path = dirname(__FILE__).'/repositoryevents/'; + + $cases = $this->readTestCases($path); + + foreach ($cases as $name => $info) { + $input = $info['input']; + $expect = $info['expect']; + + $event = NuanceGitHubRawEvent::newEvent( + NuanceGitHubRawEvent::TYPE_REPOSITORY, + $input); + + $this->assertGitHubRawEventParse($expect, $event, $name); + } + } + + private function assertGitHubRawEventParse( + array $expect, + NuanceGitHubRawEvent $event, + $name) { + + $actual = array( + 'repository.name.full' => $event->getRepositoryFullName(), + 'is.issue' => $event->isIssueEvent(), + 'is.pull' => $event->isPullRequestEvent(), + 'issue.number' => $event->getIssueNumber(), + 'pull.number' => $event->getPullRequestNumber(), + ); + + // Only verify the keys which are actually present in the test. This + // allows tests to specify only relevant keys. + $actual = array_select_keys($actual, array_keys($expect)); + + ksort($expect); + ksort($actual); + + $this->assertEqual($expect, $actual, $name); + } + + private function readTestCases($path) { + $files = Filesystem::listDirectory($path, $include_hidden = false); + + $tests = array(); + foreach ($files as $file) { + $data = Filesystem::readFile($path.$file); + + $parts = preg_split('/^~{5,}$/m', $data); + if (count($parts) < 2) { + throw new Exception( + pht( + 'Expected test file "%s" to contain an input section in JSON, '. + 'then an expected result section in JSON, with the two sections '. + 'separated by a line of "~~~~~", but the divider is not present '. + 'in the file.', + $file)); + } else if (count($parts) > 2) { + throw new Exception( + pht( + 'Expected test file "%s" to contain exactly two sections, '. + 'but it has more than two sections.')); + } + + list($input, $expect) = $parts; + + try { + $input = phutil_json_decode($input); + $expect = phutil_json_decode($expect); + } catch (Exception $ex) { + throw new PhutilProxyException( + pht( + 'Exception while decoding test data for test "%s".', + $file), + $ex); + } + + $tests[$file] = array( + 'input' => $input, + 'expect' => $expect, + ); + } + + return $tests; + } + +} diff --git a/src/applications/nuance/github/__tests__/issueevents/assigned.txt b/src/applications/nuance/github/__tests__/issueevents/assigned.txt new file mode 100644 index 0000000000..bdf5046e00 --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/assigned.txt @@ -0,0 +1,114 @@ +{ + "id": 583217900, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583217900", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "assigned", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:42:53Z", + "assignee": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "assigner": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1 +} diff --git a/src/applications/nuance/github/__tests__/issueevents/closed.txt b/src/applications/nuance/github/__tests__/issueevents/closed.txt new file mode 100644 index 0000000000..7651d07172 --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/closed.txt @@ -0,0 +1,76 @@ +{ + "id": 583218864, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583218864", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "closed", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:43:53Z", + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1 +} diff --git a/src/applications/nuance/github/__tests__/issueevents/demilestoned.txt b/src/applications/nuance/github/__tests__/issueevents/demilestoned.txt new file mode 100644 index 0000000000..bbde0e7d24 --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/demilestoned.txt @@ -0,0 +1,79 @@ +{ + "id": 583218613, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583218613", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "demilestoned", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:43:36Z", + "milestone": { + "title": "b" + }, + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1 +} diff --git a/src/applications/nuance/github/__tests__/issueevents/labeled.txt b/src/applications/nuance/github/__tests__/issueevents/labeled.txt new file mode 100644 index 0000000000..bf41262ac4 --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/labeled.txt @@ -0,0 +1,80 @@ +{ + "id": 583217784, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583217784", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "labeled", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:42:44Z", + "label": { + "name": "bug", + "color": "fc2929" + }, + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1 +} diff --git a/src/applications/nuance/github/__tests__/issueevents/locked.txt b/src/applications/nuance/github/__tests__/issueevents/locked.txt new file mode 100644 index 0000000000..440eec0d95 --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/locked.txt @@ -0,0 +1,76 @@ +{ + "id": 583218006, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583218006", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "locked", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:42:58Z", + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1 +} diff --git a/src/applications/nuance/github/__tests__/issueevents/milestoned.txt b/src/applications/nuance/github/__tests__/issueevents/milestoned.txt new file mode 100644 index 0000000000..e8b32b1113 --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/milestoned.txt @@ -0,0 +1,79 @@ +{ + "id": 583217866, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583217866", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "milestoned", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:42:50Z", + "milestone": { + "title": "b" + }, + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1 +} diff --git a/src/applications/nuance/github/__tests__/issueevents/renamed.txt b/src/applications/nuance/github/__tests__/issueevents/renamed.txt new file mode 100644 index 0000000000..0cbbd1ebb8 --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/renamed.txt @@ -0,0 +1,80 @@ +{ + "id": 583218162, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583218162", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "renamed", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:43:07Z", + "rename": { + "from": "Enforce haiku in commit messages", + "to": "Enforce haiku in commit messages edit" + }, + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1 +} diff --git a/src/applications/nuance/github/__tests__/issueevents/reopened.txt b/src/applications/nuance/github/__tests__/issueevents/reopened.txt new file mode 100644 index 0000000000..bc778728d8 --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/reopened.txt @@ -0,0 +1,76 @@ +{ + "id": 583218814, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583218814", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "reopened", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:43:50Z", + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1 +} diff --git a/src/applications/nuance/github/__tests__/issueevents/unassigned.txt b/src/applications/nuance/github/__tests__/issueevents/unassigned.txt new file mode 100644 index 0000000000..bc8b9e1df9 --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/unassigned.txt @@ -0,0 +1,114 @@ +{ + "id": 583218511, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583218511", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "unassigned", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:43:29Z", + "assignee": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "assigner": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1 +} diff --git a/src/applications/nuance/github/__tests__/issueevents/unlabeled.txt b/src/applications/nuance/github/__tests__/issueevents/unlabeled.txt new file mode 100644 index 0000000000..e3435605e0 --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/unlabeled.txt @@ -0,0 +1,80 @@ +{ + "id": 583218703, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583218703", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "unlabeled", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:43:42Z", + "label": { + "name": "bug", + "color": "fc2929" + }, + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1 +} diff --git a/src/applications/nuance/github/__tests__/issueevents/unlocked.txt b/src/applications/nuance/github/__tests__/issueevents/unlocked.txt new file mode 100644 index 0000000000..e59ba6e93f --- /dev/null +++ b/src/applications/nuance/github/__tests__/issueevents/unlocked.txt @@ -0,0 +1,76 @@ +{ + "id": 583218062, + "url": "https://api.github.com/repos/epriestley/poems/issues/events/583218062", + "actor": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "event": "unlocked", + "commit_id": null, + "commit_url": null, + "created_at": "2016-03-09T12:43:01Z", + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 5, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T14:34:46Z", + "closed_at": null, + "body": "OK" + } +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1 +} diff --git a/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.pull.txt b/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.pull.txt new file mode 100644 index 0000000000..0068f7c092 --- /dev/null +++ b/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.pull.txt @@ -0,0 +1,161 @@ +{ + "id": "3740938746", + "type": "IssueCommentEvent", + "actor": { + "id": 102631, + "login": "epriestley", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "avatar_url": "https://avatars.githubusercontent.com/u/102631?" + }, + "repo": { + "id": 14627834, + "name": "epriestley/poems", + "url": "https://api.github.com/repos/epriestley/poems" + }, + "payload": { + "action": "created", + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/2", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/2/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/2/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/2/events", + "html_url": "https://github.com/epriestley/poems/pull/2", + "id": 139568860, + "number": 2, + "title": "Please Merge Quack2 into Feature", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "url": "https://api.github.com/repos/epriestley/poems/labels/bug", + "name": "bug", + "color": "fc2929" + } + ], + "state": "open", + "locked": false, + "assignee": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "milestone": { + "url": "https://api.github.com/repos/epriestley/poems/milestones/1", + "html_url": "https://github.com/epriestley/poems/milestones/b", + "labels_url": "https://api.github.com/repos/epriestley/poems/milestones/1/labels", + "id": 1633589, + "number": 1, + "title": "b", + "description": null, + "creator": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 1, + "closed_issues": 0, + "state": "open", + "created_at": "2016-03-09T12:42:50Z", + "updated_at": "2016-03-09T12:52:41Z", + "due_on": null, + "closed_at": null + }, + "comments": 1, + "created_at": "2016-03-09T12:52:31Z", + "updated_at": "2016-03-09T12:53:06Z", + "closed_at": null, + "pull_request": { + "url": "https://api.github.com/repos/epriestley/poems/pulls/2", + "html_url": "https://github.com/epriestley/poems/pull/2", + "diff_url": "https://github.com/epriestley/poems/pull/2.diff", + "patch_url": "https://github.com/epriestley/poems/pull/2.patch" + }, + "body": "" + }, + "comment": { + "url": "https://api.github.com/repos/epriestley/poems/issues/comments/194282800", + "html_url": "https://github.com/epriestley/poems/pull/2#issuecomment-194282800", + "issue_url": "https://api.github.com/repos/epriestley/poems/issues/2", + "id": 194282800, + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "created_at": "2016-03-09T12:53:06Z", + "updated_at": "2016-03-09T12:53:06Z", + "body": "wub wub" + } + }, + "public": true, + "created_at": "2016-03-09T12:53:06Z" +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": false, + "is.pull": true, + "issue.number": null, + "pull.number": 2 +} diff --git a/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.txt b/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.txt new file mode 100644 index 0000000000..c6fa5ccb54 --- /dev/null +++ b/src/applications/nuance/github/__tests__/repositoryevents/IssueCommentEvent.created.txt @@ -0,0 +1,98 @@ +{ + "id": "3733510485", + "type": "IssueCommentEvent", + "actor": { + "id": 102631, + "login": "epriestley", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "avatar_url": "https://avatars.githubusercontent.com/u/102631?" + }, + "repo": { + "id": 14627834, + "name": "epriestley/poems", + "url": "https://api.github.com/repos/epriestley/poems" + }, + "payload": { + "action": "created", + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 1, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-08T00:41:22Z", + "closed_at": null, + "body": "OK" + }, + "comment": { + "url": "https://api.github.com/repos/epriestley/poems/issues/comments/193528669", + "html_url": "https://github.com/epriestley/poems/issues/1#issuecomment-193528669", + "issue_url": "https://api.github.com/repos/epriestley/poems/issues/1", + "id": 193528669, + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "created_at": "2016-03-08T00:41:22Z", + "updated_at": "2016-03-08T00:41:22Z", + "body": "comment on issue" + } + }, + "public": true, + "created_at": "2016-03-08T00:41:22Z" +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1 +} diff --git a/src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.closed.txt b/src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.closed.txt new file mode 100644 index 0000000000..8373e2ee52 --- /dev/null +++ b/src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.closed.txt @@ -0,0 +1,70 @@ +{ + "id": "3740905151", + "type": "IssuesEvent", + "actor": { + "id": 102631, + "login": "epriestley", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "avatar_url": "https://avatars.githubusercontent.com/u/102631?" + }, + "repo": { + "id": 14627834, + "name": "epriestley/poems", + "url": "https://api.github.com/repos/epriestley/poems" + }, + "payload": { + "action": "closed", + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "closed", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 2, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T12:43:48Z", + "closed_at": "2016-03-09T12:43:48Z", + "body": "OK" + } + }, + "public": true, + "created_at": "2016-03-09T12:43:48Z" +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1 +} diff --git a/src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.opened.txt b/src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.opened.txt new file mode 100644 index 0000000000..91068727e7 --- /dev/null +++ b/src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.opened.txt @@ -0,0 +1,70 @@ +{ + "id": "3733509737", + "type": "IssuesEvent", + "actor": { + "id": 102631, + "login": "epriestley", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "avatar_url": "https://avatars.githubusercontent.com/u/102631?" + }, + "repo": { + "id": 14627834, + "name": "epriestley/poems", + "url": "https://api.github.com/repos/epriestley/poems" + }, + "payload": { + "action": "opened", + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 0, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-08T00:41:08Z", + "closed_at": null, + "body": "OK" + } + }, + "public": true, + "created_at": "2016-03-08T00:41:08Z" +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1 +} diff --git a/src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.reopened.txt b/src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.reopened.txt new file mode 100644 index 0000000000..6ab81e1028 --- /dev/null +++ b/src/applications/nuance/github/__tests__/repositoryevents/IssuesEvent.reopened.txt @@ -0,0 +1,70 @@ +{ + "id": "3740908680", + "type": "IssuesEvent", + "actor": { + "id": 102631, + "login": "epriestley", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "avatar_url": "https://avatars.githubusercontent.com/u/102631?" + }, + "repo": { + "id": 14627834, + "name": "epriestley/poems", + "url": "https://api.github.com/repos/epriestley/poems" + }, + "payload": { + "action": "reopened", + "issue": { + "url": "https://api.github.com/repos/epriestley/poems/issues/1", + "repository_url": "https://api.github.com/repos/epriestley/poems", + "labels_url": "https://api.github.com/repos/epriestley/poems/issues/1/labels{/name}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/1/comments", + "events_url": "https://api.github.com/repos/epriestley/poems/issues/1/events", + "html_url": "https://github.com/epriestley/poems/issues/1", + "id": 139138813, + "number": 1, + "title": "Enforce haiku in commit messages edit", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + + ], + "state": "open", + "locked": false, + "assignee": null, + "milestone": null, + "comments": 3, + "created_at": "2016-03-08T00:41:08Z", + "updated_at": "2016-03-09T12:44:49Z", + "closed_at": null, + "body": "OK" + } + }, + "public": true, + "created_at": "2016-03-09T12:44:49Z" +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": true, + "is.pull": false, + "issue.number": 1 +} diff --git a/src/applications/nuance/github/__tests__/repositoryevents/PullRequestEvent.opened.txt b/src/applications/nuance/github/__tests__/repositoryevents/PullRequestEvent.opened.txt new file mode 100644 index 0000000000..848ed63afe --- /dev/null +++ b/src/applications/nuance/github/__tests__/repositoryevents/PullRequestEvent.opened.txt @@ -0,0 +1,334 @@ +{ + "id": "3740936638", + "type": "PullRequestEvent", + "actor": { + "id": 102631, + "login": "epriestley", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "avatar_url": "https://avatars.githubusercontent.com/u/102631?" + }, + "repo": { + "id": 14627834, + "name": "epriestley/poems", + "url": "https://api.github.com/repos/epriestley/poems" + }, + "payload": { + "action": "opened", + "number": 2, + "pull_request": { + "url": "https://api.github.com/repos/epriestley/poems/pulls/2", + "id": 62223852, + "html_url": "https://github.com/epriestley/poems/pull/2", + "diff_url": "https://github.com/epriestley/poems/pull/2.diff", + "patch_url": "https://github.com/epriestley/poems/pull/2.patch", + "issue_url": "https://api.github.com/repos/epriestley/poems/issues/2", + "number": 2, + "state": "open", + "locked": false, + "title": "Please Merge Quack2 into Feature", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "body": "", + "created_at": "2016-03-09T12:52:31Z", + "updated_at": "2016-03-09T12:52:31Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": null, + "assignee": null, + "milestone": null, + "commits_url": "https://api.github.com/repos/epriestley/poems/pulls/2/commits", + "review_comments_url": "https://api.github.com/repos/epriestley/poems/pulls/2/comments", + "review_comment_url": "https://api.github.com/repos/epriestley/poems/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/epriestley/poems/issues/2/comments", + "statuses_url": "https://api.github.com/repos/epriestley/poems/statuses/6cf5f6d0c8c06c4c73b8783666d9b3ecce138244", + "head": { + "label": "epriestley:feature", + "ref": "feature", + "sha": "6cf5f6d0c8c06c4c73b8783666d9b3ecce138244", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 14627834, + "name": "poems", + "full_name": "epriestley/poems", + "owner": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/epriestley/poems", + "description": "Poems (Mirror)", + "fork": false, + "url": "https://api.github.com/repos/epriestley/poems", + "forks_url": "https://api.github.com/repos/epriestley/poems/forks", + "keys_url": "https://api.github.com/repos/epriestley/poems/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/epriestley/poems/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/epriestley/poems/teams", + "hooks_url": "https://api.github.com/repos/epriestley/poems/hooks", + "issue_events_url": "https://api.github.com/repos/epriestley/poems/issues/events{/number}", + "events_url": "https://api.github.com/repos/epriestley/poems/events", + "assignees_url": "https://api.github.com/repos/epriestley/poems/assignees{/user}", + "branches_url": "https://api.github.com/repos/epriestley/poems/branches{/branch}", + "tags_url": "https://api.github.com/repos/epriestley/poems/tags", + "blobs_url": "https://api.github.com/repos/epriestley/poems/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/epriestley/poems/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/epriestley/poems/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/epriestley/poems/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/epriestley/poems/statuses/{sha}", + "languages_url": "https://api.github.com/repos/epriestley/poems/languages", + "stargazers_url": "https://api.github.com/repos/epriestley/poems/stargazers", + "contributors_url": "https://api.github.com/repos/epriestley/poems/contributors", + "subscribers_url": "https://api.github.com/repos/epriestley/poems/subscribers", + "subscription_url": "https://api.github.com/repos/epriestley/poems/subscription", + "commits_url": "https://api.github.com/repos/epriestley/poems/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/epriestley/poems/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/epriestley/poems/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/epriestley/poems/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/epriestley/poems/contents/{+path}", + "compare_url": "https://api.github.com/repos/epriestley/poems/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/epriestley/poems/merges", + "archive_url": "https://api.github.com/repos/epriestley/poems/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/epriestley/poems/downloads", + "issues_url": "https://api.github.com/repos/epriestley/poems/issues{/number}", + "pulls_url": "https://api.github.com/repos/epriestley/poems/pulls{/number}", + "milestones_url": "https://api.github.com/repos/epriestley/poems/milestones{/number}", + "notifications_url": "https://api.github.com/repos/epriestley/poems/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/epriestley/poems/labels{/name}", + "releases_url": "https://api.github.com/repos/epriestley/poems/releases{/id}", + "deployments_url": "https://api.github.com/repos/epriestley/poems/deployments", + "created_at": "2013-11-22T19:47:42Z", + "updated_at": "2016-01-21T17:10:27Z", + "pushed_at": "2016-01-21T17:10:21Z", + "git_url": "git://github.com/epriestley/poems.git", + "ssh_url": "git@github.com:epriestley/poems.git", + "clone_url": "https://github.com/epriestley/poems.git", + "svn_url": "https://github.com/epriestley/poems", + "homepage": null, + "size": 715, + "stargazers_count": 9, + "watchers_count": 9, + "language": "PHP", + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 2, + "forks": 0, + "open_issues": 2, + "watchers": 9, + "default_branch": "master" + } + }, + "base": { + "label": "epriestley:quack2", + "ref": "quack2", + "sha": "5a9c51e86615f6e1097b2a4a73ef0fe75981c1dd", + "user": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 14627834, + "name": "poems", + "full_name": "epriestley/poems", + "owner": { + "login": "epriestley", + "id": 102631, + "avatar_url": "https://avatars.githubusercontent.com/u/102631?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "html_url": "https://github.com/epriestley", + "followers_url": "https://api.github.com/users/epriestley/followers", + "following_url": "https://api.github.com/users/epriestley/following{/other_user}", + "gists_url": "https://api.github.com/users/epriestley/gists{/gist_id}", + "starred_url": "https://api.github.com/users/epriestley/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/epriestley/subscriptions", + "organizations_url": "https://api.github.com/users/epriestley/orgs", + "repos_url": "https://api.github.com/users/epriestley/repos", + "events_url": "https://api.github.com/users/epriestley/events{/privacy}", + "received_events_url": "https://api.github.com/users/epriestley/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/epriestley/poems", + "description": "Poems (Mirror)", + "fork": false, + "url": "https://api.github.com/repos/epriestley/poems", + "forks_url": "https://api.github.com/repos/epriestley/poems/forks", + "keys_url": "https://api.github.com/repos/epriestley/poems/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/epriestley/poems/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/epriestley/poems/teams", + "hooks_url": "https://api.github.com/repos/epriestley/poems/hooks", + "issue_events_url": "https://api.github.com/repos/epriestley/poems/issues/events{/number}", + "events_url": "https://api.github.com/repos/epriestley/poems/events", + "assignees_url": "https://api.github.com/repos/epriestley/poems/assignees{/user}", + "branches_url": "https://api.github.com/repos/epriestley/poems/branches{/branch}", + "tags_url": "https://api.github.com/repos/epriestley/poems/tags", + "blobs_url": "https://api.github.com/repos/epriestley/poems/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/epriestley/poems/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/epriestley/poems/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/epriestley/poems/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/epriestley/poems/statuses/{sha}", + "languages_url": "https://api.github.com/repos/epriestley/poems/languages", + "stargazers_url": "https://api.github.com/repos/epriestley/poems/stargazers", + "contributors_url": "https://api.github.com/repos/epriestley/poems/contributors", + "subscribers_url": "https://api.github.com/repos/epriestley/poems/subscribers", + "subscription_url": "https://api.github.com/repos/epriestley/poems/subscription", + "commits_url": "https://api.github.com/repos/epriestley/poems/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/epriestley/poems/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/epriestley/poems/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/epriestley/poems/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/epriestley/poems/contents/{+path}", + "compare_url": "https://api.github.com/repos/epriestley/poems/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/epriestley/poems/merges", + "archive_url": "https://api.github.com/repos/epriestley/poems/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/epriestley/poems/downloads", + "issues_url": "https://api.github.com/repos/epriestley/poems/issues{/number}", + "pulls_url": "https://api.github.com/repos/epriestley/poems/pulls{/number}", + "milestones_url": "https://api.github.com/repos/epriestley/poems/milestones{/number}", + "notifications_url": "https://api.github.com/repos/epriestley/poems/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/epriestley/poems/labels{/name}", + "releases_url": "https://api.github.com/repos/epriestley/poems/releases{/id}", + "deployments_url": "https://api.github.com/repos/epriestley/poems/deployments", + "created_at": "2013-11-22T19:47:42Z", + "updated_at": "2016-01-21T17:10:27Z", + "pushed_at": "2016-01-21T17:10:21Z", + "git_url": "git://github.com/epriestley/poems.git", + "ssh_url": "git@github.com:epriestley/poems.git", + "clone_url": "https://github.com/epriestley/poems.git", + "svn_url": "https://github.com/epriestley/poems", + "homepage": null, + "size": 715, + "stargazers_count": 9, + "watchers_count": 9, + "language": "PHP", + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 2, + "forks": 0, + "open_issues": 2, + "watchers": 9, + "default_branch": "master" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/epriestley/poems/pulls/2" + }, + "html": { + "href": "https://github.com/epriestley/poems/pull/2" + }, + "issue": { + "href": "https://api.github.com/repos/epriestley/poems/issues/2" + }, + "comments": { + "href": "https://api.github.com/repos/epriestley/poems/issues/2/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/epriestley/poems/pulls/2/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/epriestley/poems/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/epriestley/poems/pulls/2/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/epriestley/poems/statuses/6cf5f6d0c8c06c4c73b8783666d9b3ecce138244" + } + }, + "merged": false, + "mergeable": null, + "mergeable_state": "unknown", + "merged_by": null, + "comments": 0, + "review_comments": 0, + "commits": 26, + "additions": 26, + "deletions": 0, + "changed_files": 1 + } + }, + "public": true, + "created_at": "2016-03-09T12:52:31Z" +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": false, + "is.pull": true, + "issue.number": null, + "pull.number": 2 +} diff --git a/src/applications/nuance/github/__tests__/repositoryevents/PushEvent.txt b/src/applications/nuance/github/__tests__/repositoryevents/PushEvent.txt new file mode 100644 index 0000000000..f36fed2f52 --- /dev/null +++ b/src/applications/nuance/github/__tests__/repositoryevents/PushEvent.txt @@ -0,0 +1,45 @@ +{ + "id": "3498724127", + "type": "PushEvent", + "actor": { + "id": 102631, + "login": "epriestley", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "avatar_url": "https://avatars.githubusercontent.com/u/102631?" + }, + "repo": { + "id": 14627834, + "name": "epriestley/poems", + "url": "https://api.github.com/repos/epriestley/poems" + }, + "payload": { + "push_id": 924333172, + "size": 1, + "distinct_size": 1, + "ref": "refs/heads/master", + "head": "c829132d37c4c1da80d319942a5a1e500632b52f", + "before": "d8262dc45f0bd79c06571c6851d47efaeb6b599b", + "commits": [ + { + "sha": "c829132d37c4c1da80d319942a5a1e500632b52f", + "author": { + "email": "git@epriestley.com", + "name": "epriestley" + }, + "message": "Put 16K files in a single directory", + "distinct": true, + "url": "https://api.github.com/repos/epriestley/poems/commits/c829132d37c4c1da80d319942a5a1e500632b52f" + } + ] + }, + "public": true, + "created_at": "2016-01-06T11:21:59Z" +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": false, + "is.pull": false, + "issue.number": null +} diff --git a/src/applications/nuance/github/__tests__/repositoryevents/WatchEvent.started.txt b/src/applications/nuance/github/__tests__/repositoryevents/WatchEvent.started.txt new file mode 100644 index 0000000000..7cc6ed8164 --- /dev/null +++ b/src/applications/nuance/github/__tests__/repositoryevents/WatchEvent.started.txt @@ -0,0 +1,29 @@ +{ + "id": "3740950917", + "type": "WatchEvent", + "actor": { + "id": 102631, + "login": "epriestley", + "gravatar_id": "", + "url": "https://api.github.com/users/epriestley", + "avatar_url": "https://avatars.githubusercontent.com/u/102631?" + }, + "repo": { + "id": 14627834, + "name": "epriestley/poems", + "url": "https://api.github.com/repos/epriestley/poems" + }, + "payload": { + "action": "started" + }, + "public": true, + "created_at": "2016-03-09T12:56:28Z" +} +~~~~~ +{ + "repository.name.full": "epriestley/poems", + "is.issue": false, + "is.pull": false, + "issue.number": null, + "pull.number": null +} From 27ce6918390088fbd6f786fb1a856d740bcf57b0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 9 Mar 2016 10:17:40 -0800 Subject: [PATCH 44/57] Fix an issue with the Herald engine field value cache Summary: To improve the performance of Herald, we attempt to generate the value for each field (e.g., a task title) only once. For most field values this is cheap, but for some (like a commit's branches) it can be quite expensive. We only want to pay this cost once, so we cache field values. However, D12957 accidentally added a check where we bypass the cache and generate the value for every field, before reading the cache. This causes us to generate each field for every rule that uses it, plus one extra time. Instead, use the cache for this check, too. Also allow the cache to cache `null`, since it can be expensive to generate `null` even though the value isn't too interesting. The value of this early hit isn't even used (we only care if it throws or not). Test Plan: - Wrote a rule like "if any condition matches: branches contain a, branches contain b, branches contain c". - Put `phlog(new Exception())` in `DiffusionCommitBranchesHeraldField`. - Before patch, saw `bin/repository reparse --herald ` compute branches three times. - After patch, saw only one computation. - Verified field values in the transcript view Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D15451 --- src/applications/herald/engine/HeraldEngine.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/applications/herald/engine/HeraldEngine.php b/src/applications/herald/engine/HeraldEngine.php index 00c063131a..df81117b80 100644 --- a/src/applications/herald/engine/HeraldEngine.php +++ b/src/applications/herald/engine/HeraldEngine.php @@ -274,7 +274,7 @@ final class HeraldEngine extends Phobject { } else { foreach ($conditions as $condition) { try { - $object->getHeraldField($condition->getFieldName()); + $this->getConditionObjectValue($condition, $object); } catch (Exception $ex) { $reason = pht( 'Field "%s" does not exist!', @@ -366,14 +366,11 @@ final class HeraldEngine extends Phobject { } public function getObjectFieldValue($field) { - if (isset($this->fieldCache[$field])) { - return $this->fieldCache[$field]; + if (!array_key_exists($field, $this->fieldCache)) { + $this->fieldCache[$field] = $this->object->getHeraldField($field); } - $result = $this->object->getHeraldField($field); - - $this->fieldCache[$field] = $result; - return $result; + return $this->fieldCache[$field]; } protected function getRuleEffects( From 683fb7101e68862255dc53520916c6af098066cf Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 9 Mar 2016 18:46:54 -0800 Subject: [PATCH 45/57] Wrap long project tags in curtain-body Summary: Until we have a new treatment, wrap the really long tags. Fixes T10550 Test Plan: Make a really long tag and assign it to a task. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T10550 Differential Revision: https://secure.phabricator.com/D15453 --- resources/celerity/map.php | 4 ++-- webroot/rsrc/css/phui/phui-curtain-view.css | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index bba3e312b1..e65ff440f2 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -127,7 +127,7 @@ return array( '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-curtain-view.css' => 'd590da33', + 'rsrc/css/phui/phui-curtain-view.css' => '7148ae25', 'rsrc/css/phui/phui-document-pro.css' => '92d5b648', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => '9c71d2bf', @@ -813,7 +813,7 @@ return array( 'phui-calendar-month-css' => '476be7e0', 'phui-chart-css' => '6bf6f78e', 'phui-crumbs-view-css' => '79d536e5', - 'phui-curtain-view-css' => 'd590da33', + 'phui-curtain-view-css' => '7148ae25', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => '9c71d2bf', 'phui-document-view-pro-css' => '92d5b648', diff --git a/webroot/rsrc/css/phui/phui-curtain-view.css b/webroot/rsrc/css/phui/phui-curtain-view.css index 811aaf11c7..b33369abe0 100644 --- a/webroot/rsrc/css/phui/phui-curtain-view.css +++ b/webroot/rsrc/css/phui/phui-curtain-view.css @@ -29,3 +29,13 @@ .device .phui-curtain-panel-body { padding: 0; } + +/* Project tags */ + +.phui-curtain-panel-body .phabricator-handle-tag-list-item { + line-height: 21px; +} + +.phui-side-column .phui-curtain-panel-body .phui-tag-view { + white-space: pre-wrap; +} From e351eba7446969a6c600f57cd164d4f86596a0f1 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 9 Mar 2016 19:15:02 -0800 Subject: [PATCH 46/57] Add a footer to PHUITwoColumnView Summary: This allows setting of full-width content underneath the two column, or full column all by itself. Maybe these names are bad. Test Plan: Using these in Differential / Diffusion conversions. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15455 --- src/view/phui/PHUITwoColumnView.php | 56 +++++++++++++---------------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/src/view/phui/PHUITwoColumnView.php b/src/view/phui/PHUITwoColumnView.php index 1977ddce5e..d174547559 100644 --- a/src/view/phui/PHUITwoColumnView.php +++ b/src/view/phui/PHUITwoColumnView.php @@ -8,9 +8,8 @@ final class PHUITwoColumnView extends AphrontTagView { private $fluid; private $header; private $subheader; + private $footer; private $propertySection = array(); - private $actionList; - private $propertyList; private $curtain; const DISPLAY_LEFT = 'phui-side-column-left'; @@ -36,21 +35,16 @@ final class PHUITwoColumnView extends AphrontTagView { return $this; } + public function setFooter($footer) { + $this->footer = $footer; + return $this; + } + public function addPropertySection($title, $section) { $this->propertySection[] = array($title, $section); return $this; } - public function setActionList(PhabricatorActionListView $list) { - $this->actionList = $list; - return $this; - } - - public function setPropertyList(PHUIPropertyListView $list) { - $this->propertyList = $list; - return $this; - } - public function setCurtain(PHUICurtainView $curtain) { $this->curtain = $curtain; return $this; @@ -101,6 +95,8 @@ final class PHUITwoColumnView extends AphrontTagView { $main = $this->buildMainColumn(); $side = $this->buildSideColumn(); + $footer = $this->buildFooter(); + $order = array($side, $main); $inner = phutil_tag_div('phui-two-column-row grouped', $order); @@ -111,11 +107,6 @@ final class PHUITwoColumnView extends AphrontTagView { $curtain = $this->getCurtain(); if ($curtain) { $action_list = $curtain->getActionList(); - } else { - $action_list = $this->actionList; - } - - if ($action_list) { $this->header->setActionList($action_list); } @@ -138,6 +129,7 @@ final class PHUITwoColumnView extends AphrontTagView { $header, $subheader, $table, + $footer, )); } @@ -169,20 +161,6 @@ final class PHUITwoColumnView extends AphrontTagView { } 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'); - } $curtain = $this->getCurtain(); @@ -192,9 +170,23 @@ final class PHUITwoColumnView extends AphrontTagView { 'class' => 'phui-side-column', ), array( - $properties, $curtain, $this->sideColumn, )); } + + private function buildFooter() { + + $footer = $this->footer; + + return phutil_tag( + 'div', + array( + 'class' => 'phui-two-column-content phui-two-column-footer', + ), + array( + $footer, + )); + + } } From 31984a78eeeb8d8a8ef5abe549a1566f1dbf4870 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 10 Mar 2016 08:58:23 -0800 Subject: [PATCH 47/57] Add date to author panel in Maniphest Summary: Adds a date with the author name on the Authored By panel in Maniphest. A basic treatment, will see how it feels. Test Plan: Look at a few tasks Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15456 --- .../maniphest/controller/ManiphestTaskDetailController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php index d944ffb0f4..ceda169489 100644 --- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php +++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php @@ -244,6 +244,8 @@ final class ManiphestTaskDetailController extends ManiphestController { $author_href = $handles[$author_phid]->getURI(); $author = $viewer->renderHandle($author_phid)->render(); $content = phutil_tag('strong', array(), $author); + $date = phabricator_date($task->getDateCreated(), $viewer); + $content = pht('%s, %s', $content, $date); $authored_by = id(new PHUIHeadThingView()) ->setImage($author_uri) ->setImageHref($author_href) From 7b8da999142914ef685c4f4f109d60aba7c06fb6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 10 Mar 2016 13:18:58 -0800 Subject: [PATCH 48/57] Move DifferentialRevisionViewController to newPage() Summary: I think this works? Test Plan: i am wizard {F1168808} Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D15457 --- .../DifferentialRevisionViewController.php | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index ccd24220b5..afd5875a1a 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -481,19 +481,21 @@ final class DifferentialRevisionViewController extends DifferentialController { ->setBaseURI(new PhutilURI('/D'.$revision->getID())) ->setCollapsed((bool)$collapsed) ->build($changesets); - $nav->appendChild($content); - $nav->setCrumbs($crumbs); - $content = $nav; } else { - array_unshift($content, $crumbs); + $nav = null; } - return $this->buildApplicationPage( - $content, - array( - 'title' => $object_id.' '.$revision->getTitle(), - 'pageObjects' => array($revision->getPHID()), - )); + $page = $this->newPage() + ->setTitle($object_id.' '.$revision->getTitle()) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs(array($revision->getPHID())) + ->appendChild($content); + + if ($nav) { + $page->setNavigation($nav); + } + + return $page; } private function getRevisionActions(DifferentialRevision $revision) { From 8858b6cf8d178a5835a86f1598345ca01ed30e22 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 10 Mar 2016 16:05:08 -0800 Subject: [PATCH 49/57] When replying to a ghost comment, attach the reply to the same place Summary: Fixes T10562. I left this behavior sort of ambiguous in the original implementation because I didn't anticipate or stumble across this situation. It's easy to fix: when you reply to a ghost, just put the reply in the exact same place as the ghost (even if it's a different diff), so they always move/ghost/port/thread together. Test Plan: See T10562 for reproduction steps and a "before" picture. Here's the after picture: {F1168983} The two comments at the bottom are pre-fix, and exhibit the bug. The comment at the top is post-fix, and appears adjacent to the original correctly. Reviewers: chad Reviewed By: chad Subscribers: eadler Maniphest Tasks: T10562 Differential Revision: https://secure.phabricator.com/D15458 --- .../diff/PhabricatorInlineCommentController.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/infrastructure/diff/PhabricatorInlineCommentController.php b/src/infrastructure/diff/PhabricatorInlineCommentController.php index aa37d29c72..1c92b167b0 100644 --- a/src/infrastructure/diff/PhabricatorInlineCommentController.php +++ b/src/infrastructure/diff/PhabricatorInlineCommentController.php @@ -305,13 +305,18 @@ abstract class PhabricatorInlineCommentController pht('Failed to load comment "%s".', $reply_phid)); } - // NOTE: It's fine to reply to a comment from a different changeset, so - // the reply comment may not appear on the same changeset that the new - // comment appears on. This is expected in the case of ghost comments. - // We currently put the new comment on the visible changeset, not the - // original comment's changeset. + // When replying, force the new comment into the same location as the + // old comment. If we don't do this, replying to a ghost comment from + // diff A while viewing diff B can end up placing the two comments in + // different places while viewing diff C, because the porting algorithm + // makes a different decision. Forcing the comments to bind to the same + // place makes sure they stick together no matter which diff is being + // viewed. See T10562 for discussion. + $this->changesetID = $reply_comment->getChangesetID(); $this->isNewFile = $reply_comment->getIsNewFile(); + $this->lineNumber = $reply_comment->getLineNumber(); + $this->lineLength = $reply_comment->getLineLength(); } } From ca4c0db2c1858c636d26936ae52cdbc666f68263 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 10 Mar 2016 17:33:45 -0800 Subject: [PATCH 50/57] Add a key to improve Diffusion's cache fill history query Summary: Ref T10560. I don't fully understand what MySQL is doing here, but it looks like this key improves the problematic dataset in practice. (It makes sense that this key helps, I'm just not sure why the two separate keys and the UNION ALL are so bad.) This key isn't hugely expensive to add, so we can try it and see if there are still issues. Test Plan: Ran `bin/storage adjust`, saw key added to table. Used `SHOW CREATE TABLE ...` to verify the key exists. Used `EXPLAIN SELECT ...` to make sure MySQL actually uses it. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10560 Differential Revision: https://secure.phabricator.com/D15460 --- .../repository/storage/PhabricatorRepositorySchemaSpec.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/applications/repository/storage/PhabricatorRepositorySchemaSpec.php b/src/applications/repository/storage/PhabricatorRepositorySchemaSpec.php index 8ff2ed0c9b..4471151d4e 100644 --- a/src/applications/repository/storage/PhabricatorRepositorySchemaSpec.php +++ b/src/applications/repository/storage/PhabricatorRepositorySchemaSpec.php @@ -155,6 +155,9 @@ final class PhabricatorRepositorySchemaSpec 'repositoryID' => array( 'columns' => array('repositoryID', 'pathID', 'commitSequence'), ), + 'key_history' => array( + 'columns' => array('commitID', 'isDirect', 'changeType'), + ), )); $this->buildRawSchema( From 99bc1b05d70100ff5bce17a890e6cad998a62943 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 9 Mar 2016 18:46:47 -0800 Subject: [PATCH 51/57] Use more explicit language for unassigning tasks Summary: Ref T10493. - Call this action "Unassigned" instead of "Up For Grabs", since the latter implies that it's OK for anyone to grab it, which is a social/context thing that we probably shouldn't assume. - Show who a task was previously assigned to in the transaction. The text is a little clunky, yell if you've got a better wording? Or maybe I'll come up with something. Test Plan: {F1166299} Reviewers: chad Reviewed By: chad Subscribers: cburroughs Maniphest Tasks: T10493 Differential Revision: https://secure.phabricator.com/D15454 --- .../maniphest/storage/ManiphestTransaction.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/applications/maniphest/storage/ManiphestTransaction.php b/src/applications/maniphest/storage/ManiphestTransaction.php index 55d14083e6..caf57a3f71 100644 --- a/src/applications/maniphest/storage/ManiphestTransaction.php +++ b/src/applications/maniphest/storage/ManiphestTransaction.php @@ -301,7 +301,7 @@ final class ManiphestTransaction if ($this->getAuthorPHID() == $new) { return pht('Claimed'); } else if (!$new) { - return pht('Up For Grabs'); + return pht('Unassigned'); } else if (!$old) { return pht('Assigned'); } else { @@ -547,8 +547,9 @@ final class ManiphestTransaction $this->renderHandleLink($author_phid)); } else if (!$new) { return pht( - '%s placed this task up for grabs.', - $this->renderHandleLink($author_phid)); + '%s removed %s as the assignee of this task.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($old)); } else if (!$old) { return pht( '%s assigned this task to %s.', From 68b468a846c1e6e57063f96089d384ae1fe3a185 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 10 Mar 2016 16:33:44 -0800 Subject: [PATCH 52/57] Partially improve threading UI for adjacent inline comments Summary: Ref T10563. This isn't a complete fix, but should make viewing complex inline threads a little more manageable. This just tries to put stuff in thread order instead of in pure chronological order. We can likely improve the display treatment -- this is a pretty minimal approach, but should improve clarity. Test Plan: T10563 has a "before" shot. Here's the "after": {F1169018} This makes it a bit easier to follow the conversations. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10563 Differential Revision: https://secure.phabricator.com/D15459 --- resources/celerity/map.php | 6 +- .../parser/DifferentialChangesetParser.php | 66 ++++++++++++++++++- .../view/PHUIDiffInlineCommentDetailView.php | 2 +- .../differential/phui-inline-comment.css | 2 +- 4 files changed, 70 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e65ff440f2..52fb90f332 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,7 +10,7 @@ return array( 'core.pkg.css' => '9c8e888d', 'core.pkg.js' => '7d8faf57', 'darkconsole.pkg.js' => 'e7393ebb', - 'differential.pkg.css' => '2de124c9', + 'differential.pkg.css' => '7d0a63a7', 'differential.pkg.js' => 'd0cd0df6', 'diffusion.pkg.css' => 'f45955ed', 'diffusion.pkg.js' => '3a9a8bfa', @@ -59,7 +59,7 @@ return array( 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', 'rsrc/css/application/differential/changeset-view.css' => 'b6b0d1bb', 'rsrc/css/application/differential/core.css' => '7ac3cabc', - 'rsrc/css/application/differential/phui-inline-comment.css' => '0fdb3667', + 'rsrc/css/application/differential/phui-inline-comment.css' => '5953c28e', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', 'rsrc/css/application/differential/revision-list.css' => 'f3c47d33', @@ -831,7 +831,7 @@ return array( 'phui-image-mask-css' => 'a8498f9c', 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => '6d7c3509', - 'phui-inline-comment-view-css' => '0fdb3667', + 'phui-inline-comment-view-css' => '5953c28e', 'phui-list-view-css' => '9da2aa00', 'phui-object-box-css' => '91628842', 'phui-object-item-list-view-css' => '18b2ce8e', diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php index b5e4545d85..f6c7309ec7 100644 --- a/src/applications/differential/parser/DifferentialChangesetParser.php +++ b/src/applications/differential/parser/DifferentialChangesetParser.php @@ -1000,7 +1000,8 @@ final class DifferentialChangesetParser extends Phobject { } } - $this->comments = msort($this->comments, 'getID'); + $this->comments = $this->reorderAndThreadComments($this->comments); + foreach ($this->comments as $comment) { $final = $comment->getLineNumber() + $comment->getLineLength(); @@ -1569,4 +1570,67 @@ final class DifferentialChangesetParser extends Phobject { return array($old_back, $new_back); } + private function reorderAndThreadComments(array $comments) { + $comments = msort($comments, 'getID'); + + // Build an empty map of all the comments we actually have. If a comment + // is a reply but the parent has gone missing, we don't want it to vanish + // completely. + $comment_phids = mpull($comments, 'getPHID'); + $replies = array_fill_keys($comment_phids, array()); + + // Now, remove all comments which are replies, leaving only the top-level + // comments. + foreach ($comments as $key => $comment) { + $reply_phid = $comment->getReplyToCommentPHID(); + if (isset($replies[$reply_phid])) { + $replies[$reply_phid][] = $comment; + unset($comments[$key]); + } + } + + // For each top level comment, add the comment, then add any replies + // to it. Do this recursively so threads are shown in threaded order. + $results = array(); + foreach ($comments as $comment) { + $results[] = $comment; + $phid = $comment->getPHID(); + $descendants = $this->getInlineReplies($replies, $phid, 1); + foreach ($descendants as $descendant) { + $results[] = $descendant; + } + } + + // If we have anything left, they were cyclic references. Just dump + // them in a the end. This should be impossible, but users are very + // creative. + foreach ($replies as $phid => $comments) { + foreach ($comments as $comment) { + $results[] = $comment; + } + } + + return $results; + } + + private function getInlineReplies(array &$replies, $phid, $depth) { + $comments = idx($replies, $phid, array()); + unset($replies[$phid]); + + $results = array(); + foreach ($comments as $comment) { + $results[] = $comment; + $descendants = $this->getInlineReplies( + $replies, + $comment->getPHID(), + $depth + 1); + foreach ($descendants as $descendant) { + $results[] = $descendant; + } + } + + return $results; + } + + } diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index b58b1764a8..808fb5d067 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -176,7 +176,7 @@ final class PHUIDiffInlineCommentDetailView if ($inline->getHasReplies()) { $classes[] = 'inline-comment-has-reply'; } - // I think this is unused + if ($inline->getReplyToCommentPHID()) { $classes[] = 'inline-comment-is-reply'; } diff --git a/webroot/rsrc/css/application/differential/phui-inline-comment.css b/webroot/rsrc/css/application/differential/phui-inline-comment.css index 0055f25699..f93b2c8d6c 100644 --- a/webroot/rsrc/css/application/differential/phui-inline-comment.css +++ b/webroot/rsrc/css/application/differential/phui-inline-comment.css @@ -61,7 +61,7 @@ /* Tighten up spacing on replies */ .differential-inline-comment.inline-comment-is-reply { - margin-top: -4px; + margin-top: -12px; } .differential-inline-comment .inline-head-right { From 60b42750b64123a9fcfc66bdb0e03992fa467a8f Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Thu, 10 Mar 2016 18:14:22 -0800 Subject: [PATCH 53/57] Cast old duration values for unit tests to float in DifferentialController Summary: Fixes T10549. Test Plan: N/A Reviewers: #blessed_reviewers, avivey, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin Maniphest Tasks: T10549 Differential Revision: https://secure.phabricator.com/D15452 --- .../differential/controller/DifferentialController.php | 6 ++++++ .../controller/DifferentialRevisionViewController.php | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/src/applications/differential/controller/DifferentialController.php b/src/applications/differential/controller/DifferentialController.php index 46ac290c09..1aba876c68 100644 --- a/src/applications/differential/controller/DifferentialController.php +++ b/src/applications/differential/controller/DifferentialController.php @@ -190,6 +190,12 @@ abstract class DifferentialController extends PhabricatorController { } } + // Cast duration to a float since it used to be a string in some + // cases. + if (isset($map['duration'])) { + $map['duration'] = (double)$map['duration']; + } + return $map; } diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index afd5875a1a..cad2932a0c 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -1078,6 +1078,10 @@ final class DifferentialRevisionViewController extends DifferentialController { return null; } + if (!$diff->getBuildable()) { + return null; + } + $interesting_messages = array(); foreach ($diff->getUnitMessages() as $message) { switch ($message->getResult()) { From d511308a79a58111c63d15e14416c23283616f32 Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 10 Mar 2016 18:11:12 -0800 Subject: [PATCH 54/57] Add a Phrequent curtain extension Summary: Fixes T10546. Some day, decades from now, we can revisit this when we iterate on Phrequent. Just don't regress for no real reason in the meantime, since it's easy enough to keep it working in reasonable shape. Test Plan: {F1169096} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10546 Differential Revision: https://secure.phabricator.com/D15461 --- src/__phutil_library_map__.php | 2 + .../PhrequentCurtainExtension.php | 87 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 src/applications/phrequent/engineextension/PhrequentCurtainExtension.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 0d86575126..bfad24494a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3794,6 +3794,7 @@ phutil_register_library_map(array( 'PhragmentZIPController' => 'applications/phragment/controller/PhragmentZIPController.php', 'PhrequentConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentConduitAPIMethod.php', 'PhrequentController' => 'applications/phrequent/controller/PhrequentController.php', + 'PhrequentCurtainExtension' => 'applications/phrequent/engineextension/PhrequentCurtainExtension.php', 'PhrequentDAO' => 'applications/phrequent/storage/PhrequentDAO.php', 'PhrequentListController' => 'applications/phrequent/controller/PhrequentListController.php', 'PhrequentPopConduitAPIMethod' => 'applications/phrequent/conduit/PhrequentPopConduitAPIMethod.php', @@ -8498,6 +8499,7 @@ phutil_register_library_map(array( 'PhragmentZIPController' => 'PhragmentController', 'PhrequentConduitAPIMethod' => 'ConduitAPIMethod', 'PhrequentController' => 'PhabricatorController', + 'PhrequentCurtainExtension' => 'PHUICurtainExtension', 'PhrequentDAO' => 'PhabricatorLiskDAO', 'PhrequentListController' => 'PhrequentController', 'PhrequentPopConduitAPIMethod' => 'PhrequentConduitAPIMethod', diff --git a/src/applications/phrequent/engineextension/PhrequentCurtainExtension.php b/src/applications/phrequent/engineextension/PhrequentCurtainExtension.php new file mode 100644 index 0000000000..25d0e424a6 --- /dev/null +++ b/src/applications/phrequent/engineextension/PhrequentCurtainExtension.php @@ -0,0 +1,87 @@ +getViewer(); + + $events = id(new PhrequentUserTimeQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($object->getPHID())) + ->needPreemptingEvents(true) + ->execute(); + $event_groups = mgroup($events, 'getUserPHID'); + + if (!$events) { + return; + } + + $handles = $viewer->loadHandles(array_keys($event_groups)); + $status_view = new PHUIStatusListView(); + + foreach ($event_groups as $user_phid => $event_group) { + $item = new PHUIStatusItemView(); + $item->setTarget($handles[$user_phid]->renderLink()); + + $state = 'stopped'; + foreach ($event_group as $event) { + if ($event->getDateEnded() === null) { + if ($event->isPreempted()) { + $state = 'suspended'; + } else { + $state = 'active'; + break; + } + } + } + + switch ($state) { + case 'active': + $item->setIcon( + PHUIStatusItemView::ICON_CLOCK, + 'green', + pht('Working Now')); + break; + case 'suspended': + $item->setIcon( + PHUIStatusItemView::ICON_CLOCK, + 'yellow', + pht('Interrupted')); + break; + case 'stopped': + $item->setIcon( + PHUIStatusItemView::ICON_CLOCK, + 'bluegrey', + pht('Not Working Now')); + break; + } + + $block = new PhrequentTimeBlock($event_group); + $item->setNote( + phutil_format_relative_time( + $block->getTimeSpentOnObject( + $object->getPHID(), + time()))); + + $status_view->addItem($item); + } + + + return $this->newPanel() + ->setHeaderText(pht('Time Spent')) + ->setOrder(40000) + ->appendChild($status_view); + } + +} From 5df5f339096220c9a952bea1dc458f4945656c9d Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 10 Mar 2016 18:54:17 -0800 Subject: [PATCH 55/57] Add a task count to workboards Summary: See Q335. This might need some tweaking, but the JS works now so we can move/style the node now. Test Plan: {F1169168} - Also dragged stuff around, saw counts update properly. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D15462 --- resources/celerity/map.php | 12 ++++++------ .../rsrc/js/application/projects/WorkboardColumn.js | 7 +++++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 52fb90f332..fdc6357f60 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -421,7 +421,7 @@ return array( 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c', 'rsrc/js/application/projects/WorkboardBoard.js' => '52291776', 'rsrc/js/application/projects/WorkboardCard.js' => 'c587b80f', - 'rsrc/js/application/projects/WorkboardColumn.js' => 'f05d6e5d', + 'rsrc/js/application/projects/WorkboardColumn.js' => 'bae58312', 'rsrc/js/application/projects/WorkboardController.js' => '55baf5ed', 'rsrc/js/application/projects/behavior-project-boards.js' => '14a1faae', 'rsrc/js/application/projects/behavior-project-create.js' => '065227cc', @@ -732,7 +732,7 @@ return array( 'javelin-websocket' => 'e292eaf4', 'javelin-workboard-board' => '52291776', 'javelin-workboard-card' => 'c587b80f', - 'javelin-workboard-column' => 'f05d6e5d', + 'javelin-workboard-column' => 'bae58312', 'javelin-workboard-controller' => '55baf5ed', 'javelin-workflow' => '5b2e3e2b', 'lightbox-attachment-css' => '7acac05d', @@ -1794,6 +1794,10 @@ return array( 'b6b0d1bb' => array( 'phui-inline-comment-view-css', ), + 'bae58312' => array( + 'javelin-install', + 'javelin-workboard-card', + ), 'bcaccd64' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -2062,10 +2066,6 @@ return array( 'javelin-workflow', 'javelin-json', ), - 'f05d6e5d' => array( - 'javelin-install', - 'javelin-workboard-card', - ), 'f411b6ae' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/webroot/rsrc/js/application/projects/WorkboardColumn.js b/webroot/rsrc/js/application/projects/WorkboardColumn.js index 738cf151c2..ced39dcd73 100644 --- a/webroot/rsrc/js/application/projects/WorkboardColumn.js +++ b/webroot/rsrc/js/application/projects/WorkboardColumn.js @@ -221,6 +221,7 @@ JX.install('WorkboardColumn', { var board = this.getBoard(); var points = {}; + var count = 0; for (var phid in cards) { var card = cards[phid]; @@ -238,6 +239,8 @@ JX.install('WorkboardColumn', { } points[status] += card_points; } + + count++; } var total_points = 0; @@ -254,6 +257,10 @@ JX.install('WorkboardColumn', { display_value = total_points; } + if (board.getPointsEnabled()) { + display_value = count + ' | ' + display_value; + } + var over_limit = ((limit !== null) && (total_points > limit)); var content_node = this.getPointsContentNode(); From de23ba0002f878fab7a80e45b84c406221a7dcd0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 11 Mar 2016 15:45:17 -0800 Subject: [PATCH 56/57] Fix a minor issue in Nuance which could cause the trigger daemon to poll too often Summary: Ref T10537. Currently, when you have at least two cursors, the daemon can poll too frequently when processing the last source because it never hits the end-of-list condition. Test Plan: - Ran `bin/phd debug trigger`. - Observed huge volumes of output before change as triggers fired as fast as possible. - Observed reasonable poll frequency after change. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10537 Differential Revision: https://secure.phabricator.com/D15464 --- src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php b/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php index dc1ffdc936..1620c8f99a 100644 --- a/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php +++ b/src/infrastructure/daemon/workers/PhabricatorTriggerDaemon.php @@ -411,7 +411,7 @@ final class PhabricatorTriggerDaemon } // If we haven't loaded sources yet, load them first. - if (!$this->nuanceSources) { + if (!$this->nuanceSources && !$this->nuanceCursors) { $this->anyNuanceData = false; $sources = id(new NuanceSourceQuery()) From ba9cd64e514eaf3204dd074e4b7fa8f8e2c7f037 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 11 Mar 2016 16:02:19 -0800 Subject: [PATCH 57/57] Stop moving "Cc" addresses to "To" when building mail targets Summary: Fixes T10539. When building mail targets, we fail to preserve whether a recipient was originally "To" or "Cc", and just move everyone to "To". Test Plan: Added a comment to a task with a "To" user and a "Cc" user, with `metamta.placeholder-to-recipient` set and `metamta.one-mail-per-recipient` set. Got mail with me Cc'd as the Cc'd user: {F1172020} Reviewers: chad Reviewed By: chad Maniphest Tasks: T10539 Differential Revision: https://secure.phabricator.com/D15465 --- .../replyhandler/PhabricatorMailReplyHandler.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php b/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php index efae153a0f..e4da2a04d6 100644 --- a/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php +++ b/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php @@ -254,10 +254,19 @@ abstract class PhabricatorMailReplyHandler extends Phobject { $map = $to + $cc; foreach ($map as $phid => $user) { + // Preserve the original To/Cc information on the target. + if (isset($to[$phid])) { + $target_to = array($phid => $user); + $target_cc = array(); + } else { + $target_to = array(); + $target_cc = array($phid => $user); + } + $target = id(clone $template) ->setViewer($user) - ->setToMap(array($phid => $user)) - ->setCCMap(array()); + ->setToMap($target_to) + ->setCCMap($target_cc); if ($supports_private_replies) { $reply_to = $this->getPrivateReplyHandlerEmailAddress($user);