From cafd2dd6cb4e97b33207b9cfb12fa2f788f55331 Mon Sep 17 00:00:00 2001
From: Chad Little <chad@chadsdomain.com>
Date: Tue, 29 Apr 2014 10:14:18 -0700
Subject: [PATCH] Add Success/Fail states to PHUIObjectList

Summary:
A number of interfaces could use a more consice looking ObjectItemList for showing pass/fail/warn states.

 - Added a new "State" for PHUIObjectItemListView
 - Updated UIExamples
 - Implemented in Herald (next Harmormaster)

Test Plan: UIExamples / Herald, desktop and mobile

Reviewers: btrahan, epriestley

Reviewed By: epriestley

Subscribers: epriestley, Korvin

Differential Revision: https://secure.phabricator.com/D8893
---
 resources/celerity/map.php                    | 10 ++--
 .../controller/HeraldTranscriptController.php | 55 ++++++-------------
 .../PhabricatorUIExampleRenderController.php  |  3 +-
 .../examples/PHUIObjectItemListExample.php    | 44 +++++++++++++++
 src/view/phui/PHUIObjectItemListView.php      |  9 +++
 src/view/phui/PHUIObjectItemView.php          | 48 +++++++++++++++-
 .../css/phui/phui-object-item-list-view.css   | 38 +++++++++++++
 7 files changed, 163 insertions(+), 44 deletions(-)

diff --git a/resources/celerity/map.php b/resources/celerity/map.php
index 50c2597e36..66e90a87a0 100644
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -7,7 +7,7 @@
 return array(
   'names' =>
   array(
-    'core.pkg.css' => 'b39b2afb',
+    'core.pkg.css' => 'a762d94d',
     'core.pkg.js' => '417722ff',
     'darkconsole.pkg.js' => 'ca8671ce',
     'differential.pkg.css' => '8a064eb7',
@@ -106,7 +106,7 @@ return array(
     'rsrc/css/application/tokens/tokens.css' => '5f7bca25',
     'rsrc/css/application/uiexample/example.css' => '528b19de',
     'rsrc/css/core/core.css' => 'da26ddb2',
-    'rsrc/css/core/remarkup.css' => 'f27ecac4',
+    'rsrc/css/core/remarkup.css' => '98a7627b',
     'rsrc/css/core/syntax.css' => '3c18c1cb',
     'rsrc/css/core/z-index.css' => '0d89d53c',
     'rsrc/css/diviner/diviner-shared.css' => '38813222',
@@ -137,7 +137,7 @@ return array(
     'rsrc/css/phui/phui-info-panel.css' => '27ea50a1',
     'rsrc/css/phui/phui-list.css' => 'ef8035b6',
     'rsrc/css/phui/phui-object-box.css' => 'ce92d8ec',
-    'rsrc/css/phui/phui-object-item-list-view.css' => '8b459abe',
+    'rsrc/css/phui/phui-object-item-list-view.css' => 'c11ec980',
     'rsrc/css/phui/phui-pinboard-view.css' => '4b346c2a',
     'rsrc/css/phui/phui-property-list-view.css' => 'c4d44192',
     'rsrc/css/phui/phui-remarkup-preview.css' => '19ad512b',
@@ -711,7 +711,7 @@ return array(
     'phabricator-prefab' => '0326e5d0',
     'phabricator-profile-css' => '33e6f703',
     'phabricator-project-tag-css' => '095c9404',
-    'phabricator-remarkup-css' => 'f27ecac4',
+    'phabricator-remarkup-css' => '98a7627b',
     'phabricator-search-results-css' => 'f240504c',
     'phabricator-settings-css' => 'ea8f5915',
     'phabricator-shaped-request' => 'dfa181a4',
@@ -759,7 +759,7 @@ return array(
     'phui-info-panel-css' => '27ea50a1',
     'phui-list-view-css' => 'ef8035b6',
     'phui-object-box-css' => 'ce92d8ec',
-    'phui-object-item-list-view-css' => '8b459abe',
+    'phui-object-item-list-view-css' => 'c11ec980',
     'phui-pinboard-view-css' => '4b346c2a',
     'phui-property-list-view-css' => 'c4d44192',
     'phui-remarkup-preview-css' => '19ad512b',
diff --git a/src/applications/herald/controller/HeraldTranscriptController.php b/src/applications/herald/controller/HeraldTranscriptController.php
index 50b24872f8..14a2d04cd3 100644
--- a/src/applications/herald/controller/HeraldTranscriptController.php
+++ b/src/applications/herald/controller/HeraldTranscriptController.php
@@ -294,13 +294,15 @@ final class HeraldTranscriptController extends HeraldController {
     $rule_type_global = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL;
     $action_names = $adapter->getActionNameMap($rule_type_global);
 
-    $rows = array();
+    $list = new PHUIObjectItemListView();
+    $list->setStates(true);
+    $list->setNoDataString(pht('No actions were taken.'));
     foreach ($xscript->getApplyTranscripts() as $apply_xscript) {
 
       $target = $apply_xscript->getTarget();
       switch ($apply_xscript->getAction()) {
         case HeraldAdapter::ACTION_NOTHING:
-          $target = '';
+          $target = null;
           break;
         case HeraldAdapter::ACTION_FLAG:
           $target = PhabricatorFlagColor::getColorName($target);
@@ -316,55 +318,34 @@ final class HeraldTranscriptController extends HeraldController {
                 $target[$k] = $handles[$phid]->getName();
               }
             }
-            $target = implode("\n", $target);
+            $target = implode(", ", $target);
           } else {
             $target = '<empty>';
           }
           break;
       }
 
+      $item = new PHUIObjectItemView();
+
       if ($apply_xscript->getApplied()) {
-        $outcome = phutil_tag(
-          'span',
-          array('class' => 'outcome-success'),
-          pht('SUCCESS'));
+        $item->setState(PHUIObjectItemView::STATE_SUCCESS);
       } else {
-        $outcome = phutil_tag(
-          'span',
-          array('class' => 'outcome-failure'),
-          pht('FAILURE'));
+        $item->setState(PHUIObjectItemView::STATE_FAIL);
       }
 
-      $rows[] = array(
-        idx($action_names, $apply_xscript->getAction(), pht('Unknown')),
-        $target,
-        hsprintf(
-          '<strong>Taken because:</strong> %s<br />'.
-            '<strong>Outcome:</strong> %s %s',
-          $apply_xscript->getReason(),
-          $outcome,
-          $apply_xscript->getAppliedReason()),
-      );
-    }
+      $rule = idx($action_names, $apply_xscript->getAction(), pht('Unknown'));
 
-    $table = new AphrontTableView($rows);
-    $table->setNoDataString(pht('No actions were taken.'));
-    $table->setHeaders(
-      array(
-        pht('Action'),
-        pht('Target'),
-        pht('Details'),
-      ));
-    $table->setColumnClasses(
-      array(
-        '',
-        '',
-        'wide',
-      ));
+      $item->setHeader(pht('%s: %s', $rule, $target));
+      $item->addAttribute($apply_xscript->getReason());
+      $item->addAttribute(
+        pht('Outcome: %s', $apply_xscript->getAppliedReason()));
+
+      $list->addItem($item);
+    }
 
     $box = new PHUIObjectBoxView();
     $box->setHeaderText(pht('Actions Taken'));
-    $box->appendChild($table);
+    $box->appendChild($list);
 
     return $box;
   }
diff --git a/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php b/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php
index 7b65600aa3..97977798e2 100644
--- a/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php
+++ b/src/applications/uiexample/controller/PhabricatorUIExampleRenderController.php
@@ -45,7 +45,8 @@ final class PhabricatorUIExampleRenderController extends PhabricatorController {
 
     $header = id(new PHUIHeaderView())
       ->setHeader(pht('%s (%s)', $example->getName(), get_class($example)))
-      ->setSubheader($example->getDescription());
+      ->setSubheader($example->getDescription())
+      ->setNoBackground(true);
 
     $nav->appendChild(
       array(
diff --git a/src/applications/uiexample/examples/PHUIObjectItemListExample.php b/src/applications/uiexample/examples/PHUIObjectItemListExample.php
index 53019231d5..9523a95f63 100644
--- a/src/applications/uiexample/examples/PHUIObjectItemListExample.php
+++ b/src/applications/uiexample/examples/PHUIObjectItemListExample.php
@@ -360,6 +360,50 @@ final class PHUIObjectItemListExample extends PhabricatorUIExample {
 
     $out[] = array($head, $list);
 
+    $head = id(new PHUIHeaderView())
+      ->setHeader(pht('States'));
+
+    $list = id(new PHUIObjectItemListView())
+      ->setStates(true);
+
+    $list->addItem(
+      id(new PHUIObjectItemView())
+        ->setObjectName('X1200')
+        ->setHeader(pht('Action Passed'))
+        ->addAttribute(pht('That went swimmingly, go you'))
+        ->setHref('#')
+        ->setState(PHUIObjectItemView::STATE_SUCCESS));
+
+    $list->addItem(
+      id(new PHUIObjectItemView())
+        ->setObjectName('X1201')
+        ->setHeader(pht('Action Failed'))
+        ->addAttribute(pht('Whoopsies, might want to fix that'))
+        ->setHref('#')
+        ->setState(PHUIObjectItemView::STATE_FAIL));
+
+    $list->addItem(
+      id(new PHUIObjectItemView())
+        ->setObjectName('X1202')
+        ->setHeader(pht('Action Warning'))
+        ->addAttribute(pht('We need to talk about things'))
+        ->setHref('#')
+        ->setState(PHUIObjectItemView::STATE_WARN));
+
+    $list->addItem(
+      id(new PHUIObjectItemView())
+        ->setObjectName('X1203')
+        ->setHeader(pht('Action Noted'))
+        ->addAttribute(pht('The weather seems nice today'))
+        ->setHref('#')
+        ->setState(PHUIObjectItemView::STATE_NOTE));
+
+    $box = id(new PHUIObjectBoxView())
+      ->setHeaderText('Test Things')
+      ->appendChild($list);
+
+    $out[] = array($head, $box);
+
     return $out;
   }
 }
diff --git a/src/view/phui/PHUIObjectItemListView.php b/src/view/phui/PHUIObjectItemListView.php
index f1efc05fc2..4dff2e18ce 100644
--- a/src/view/phui/PHUIObjectItemListView.php
+++ b/src/view/phui/PHUIObjectItemListView.php
@@ -11,6 +11,7 @@ final class PHUIObjectItemListView extends AphrontTagView {
   private $flush;
   private $plain;
   private $allowEmptyList;
+  private $states;
 
 
   public function setAllowEmptyList($allow_empty_list) {
@@ -62,6 +63,11 @@ final class PHUIObjectItemListView extends AphrontTagView {
     return $this;
   }
 
+  public function setStates($states) {
+    $this->states = $states;
+    return $this;
+  }
+
   protected function getTagName() {
     return 'ul';
   }
@@ -76,6 +82,9 @@ final class PHUIObjectItemListView extends AphrontTagView {
     if ($this->cards) {
       $classes[] = 'phui-object-list-cards';
     }
+    if ($this->states) {
+      $classes[] = 'phui-object-list-states';
+    }
     if ($this->flush) {
       $classes[] = 'phui-object-list-flush';
     }
diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php
index 1275524bc9..cbba7a64b2 100644
--- a/src/view/phui/PHUIObjectItemView.php
+++ b/src/view/phui/PHUIObjectItemView.php
@@ -19,11 +19,18 @@ final class PHUIObjectItemView extends AphrontTagView {
   private $headIcons = array();
   private $disabled;
   private $imageURI;
+  private $state;
+  private $fontIcon;
 
   const AGE_FRESH = 'fresh';
   const AGE_STALE = 'stale';
   const AGE_OLD   = 'old';
 
+  const STATE_SUCCESS = 'green';
+  const STATE_FAIL = 'red';
+  const STATE_WARN = 'yellow';
+  const STATE_NOTE = 'blue';
+
   public function setDisabled($disabled) {
     $this->disabled = $disabled;
     return $this;
@@ -107,6 +114,27 @@ final class PHUIObjectItemView extends AphrontTagView {
     return $this->imageURI;
   }
 
+  public function setState($state) {
+    $this->state = $state;
+    switch ($state) {
+      case self::STATE_SUCCESS:
+        $fi = 'fa-check-circle green';
+      break;
+      case self::STATE_FAIL:
+        $fi = 'fa-times-circle red';
+      break;
+      case self::STATE_WARN:
+        $fi = 'fa-exclamation-circle yellow';
+      break;
+      case self::STATE_NOTE:
+        $fi = 'fa-info-circle blue';
+      break;
+    }
+    $this->fontIcon = id(new PHUIIconView())
+      ->setIconFont($fi.' fa-2x');
+    return $this;
+  }
+
   public function setEpoch($epoch, $age = self::AGE_FRESH) {
     $date = phabricator_datetime($epoch, $this->getUser());
 
@@ -234,6 +262,10 @@ final class PHUIObjectItemView extends AphrontTagView {
       $item_classes[] = 'phui-object-item-disabled';
     }
 
+    if ($this->state) {
+      $item_classes[] = 'phui-object-item-state-'.$this->state;
+    }
+
     switch ($this->effect) {
       case 'highlighted':
         $item_classes[] = 'phui-object-item-highlighted';
@@ -251,10 +283,14 @@ final class PHUIObjectItemView extends AphrontTagView {
       $item_classes[] = 'phui-object-item-grippable';
     }
 
-    if ($this->getImageuRI()) {
+    if ($this->getImageURI()) {
       $item_classes[] = 'phui-object-item-with-image';
     }
 
+    if ($this->fontIcon) {
+      $item_classes[] = 'phui-object-item-with-ficon';
+    }
+
     return array(
       'class' => $item_classes,
     );
@@ -494,6 +530,16 @@ final class PHUIObjectItemView extends AphrontTagView {
         '');
     }
 
+    $ficon = null;
+    if ($this->fontIcon) {
+      $image = phutil_tag(
+        'div',
+        array(
+          'class' => 'phui-object-item-ficon',
+        ),
+        $this->fontIcon);
+    }
+
     $box = phutil_tag(
       'div',
       array(
diff --git a/webroot/rsrc/css/phui/phui-object-item-list-view.css b/webroot/rsrc/css/phui/phui-object-item-list-view.css
index da12b42125..724b1fb303 100644
--- a/webroot/rsrc/css/phui/phui-object-item-list-view.css
+++ b/webroot/rsrc/css/phui/phui-object-item-list-view.css
@@ -598,3 +598,41 @@
 .phui-object-item-with-image .phui-object-item-content-box {
   margin-left: 54px;
 }
+
+/* - State ---------------------------------------------------------------------
+
+  Provides a list of object status or states, success or fail, etc
+
+*/
+
+.phui-object-item-ficon {
+  width: 26px;
+  height: 26px;
+  margin: 7px 9px 7px 0;
+  position: absolute;
+}
+
+.phui-object-item-with-ficon .phui-object-item-content-box {
+  margin-left: 24px;
+}
+
+.phui-object-box .phui-object-list-states {
+  padding: 8px 12px 0 12px;
+}
+
+.phui-object-box .phui-object-list-states li:last-child .phui-object-item-frame {
+  border: none;
+}
+
+.phui-object-list-states .phui-object-item-frame {
+  border: none;
+  border-bottom: 1px solid {$thinblueborder};
+}
+
+.phui-object-list-states .phui-object-item {
+  border: none;
+}
+
+.phui-object-list-states .phui-object-item-frame {
+  min-height: 44px;
+}