diff --git a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php --- a/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php +++ b/src/applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php @@ -275,8 +275,11 @@ $header = null; break; case self::HEADER_MODE_EDIT: + // In edit mode, include the panel monogram to make managing boards + // a little easier. + $header_text = pht('%s %s', $panel->getMonogram(), $panel->getName()); $header = id(new PHUIHeaderView()) - ->setHeader($panel->getName()); + ->setHeader($header_text); $header = $this->addPanelHeaderActions($header); break; case self::HEADER_MODE_NORMAL: @@ -316,6 +319,11 @@ ->setIcon('fa-pencil') ->setName(pht('Edit Panel')) ->setHref((string)$edit_uri); + + $actions[] = id(new PhabricatorActionView()) + ->setIcon('fa-window-maximize') + ->setName(pht('View Panel Details')) + ->setHref($panel->getURI()); } if ($dashboard_id) { diff --git a/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php b/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php --- a/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php +++ b/src/applications/dashboard/engine/PhabricatorDashboardRenderingEngine.php @@ -11,11 +11,19 @@ return $this; } + public function getViewer() { + return $this->viewer; + } + public function setDashboard(PhabricatorDashboard $dashboard) { $this->dashboard = $dashboard; return $this; } + public function getDashboard() { + return $this->dashboard; + } + public function setArrangeMode($mode) { $this->arrangeMode = $mode; return $this; @@ -26,6 +34,8 @@ $dashboard = $this->dashboard; $viewer = $this->viewer; + $is_editable = $this->arrangeMode; + $layout_config = $dashboard->getLayoutConfigObject(); $panel_grid_locations = $layout_config->getPanelLocations(); $panels = mpull($dashboard->getPanels(), null, 'getPHID'); @@ -35,7 +45,7 @@ ->setFluidLayout(true) ->setGutter(AphrontMultiColumnView::GUTTER_LARGE); - if ($this->arrangeMode) { + if ($is_editable) { $h_mode = PhabricatorDashboardPanelRenderingEngine::HEADER_MODE_EDIT; } else { $h_mode = PhabricatorDashboardPanelRenderingEngine::HEADER_MODE_NORMAL; @@ -77,8 +87,8 @@ } $column_class = $layout_config->getColumnClass( $column, - $this->arrangeMode); - if ($this->arrangeMode) { + $is_editable); + if ($is_editable) { $column_result[] = $this->renderAddPanelPlaceHolder($column); $column_result[] = $this->renderAddPanelUI($column); } @@ -89,7 +99,7 @@ $metadata = array('columnID' => $column)); } - if ($this->arrangeMode) { + if ($is_editable) { Javelin::initBehavior( 'dashboard-move-panels', array( @@ -98,9 +108,19 @@ )); } + if ($is_editable) { + $info_view = $this->newEditInfoView($dashboard); + } else { + $info_view = null; + } + $view = id(new PHUIBoxView()) ->addClass('dashboard-view') - ->appendChild($result); + ->appendChild( + array( + $info_view, + $result, + )); return $view; } @@ -153,4 +173,108 @@ )); } + + private function newEditInfoView(PhabricatorDashboard $dashboard) { + $viewer = $this->getViewer(); + + // If the dashboard has some panels with stricter view policies, than the + // dashboard itself, render a warning that users who can see the dashboard + // may not be able to see all of the panels. + + // Invalid panels and panels the viewer can't see won't trigger this + // warning, but that should be okay because they render in an explicit + // error state anyway. + + $view_capability = PhabricatorPolicyCapability::CAN_VIEW; + $board_policy_phid = $dashboard->getPolicy($view_capability); + + $policy_phids = array(); + $policy_phids[] = $board_policy_phid; + + $panels = $dashboard->getPanels(); + foreach ($panels as $panel) { + $policy_phids[] = $panel->getPolicy($view_capability); + } + + $policies = id(new PhabricatorPolicyQuery()) + ->setViewer($viewer) + ->withPHIDs($policy_phids) + ->execute(); + $policies = mpull($policies, null, 'getPHID'); + + $messages = array(); + $has_error = false; + + $has_stronger = false; + $has_different = false; + + $board_policy = idx($policies, $board_policy_phid); + if (!$board_policy) { + $messages[] = pht( + 'This dashboard has an invalid view policy ("%s").', + $board_policy_phid); + $has_error = true; + } else { + foreach ($panels as $panel) { + $panel_policy_phid = $panel->getPolicy($view_capability); + + $panel_policy = idx($policies, $panel_policy_phid); + if (!$panel_policy) { + $messages[] = pht( + 'A panel ("%s") has an invalid view policy ("%s").', + $panel->getDiagnosticName(), + $panel_policy_phid); + $has_error = true; + continue; + } + + if ($panel_policy->isStrongerThan($board_policy)) { + $messages[] = pht( + 'A panel ("%s") has a more restrictive view policy ("%s") than '. + 'the view policy for this dashboard ("%s"). Some users who can '. + 'see the dashboard may not be able to see this panel.', + $this->renderPanelDiagnosticLink($panel), + $panel_policy->renderDescription(), + $board_policy->renderDescription()); + } else if ($panel_policy_phid !== $board_policy_phid) { + if (!$board_policy->isStrongerThan($panel_policy)) { + $messages[] = pht( + 'A panel ("%s") has a view policy ("%s") which differs from '. + 'the dashboard view policy ("%s"). Some users who can see the '. + 'dashboard may not be able to see this panel.', + $this->renderPanelDiagnosticLink($panel), + $panel_policy->renderDescription(), + $board_policy->renderDescription()); + } + } + } + } + + if (!$messages) { + return null; + } + + return id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setErrors($messages); + } + + private function renderPanelDiagnosticLink(PhabricatorDashboardPanel $panel) { + $name = $panel->getName(); + if (!strlen($name)) { + $name = pht('PHID <%s>', $panel->getPHID()); + } + + if ($panel->getID()) { + $name = pht('%s %s', $panel->getMonogram(), $name); + } + + return phutil_tag( + 'a', + array( + 'href' => $panel->getURI(), + ), + $name); + } + }