Page MenuHomePhabricator

D15184.id36665.diff
No OneTemporary

D15184.id36665.diff

diff --git a/src/applications/policy/controller/PhabricatorPolicyEditController.php b/src/applications/policy/controller/PhabricatorPolicyEditController.php
--- a/src/applications/policy/controller/PhabricatorPolicyEditController.php
+++ b/src/applications/policy/controller/PhabricatorPolicyEditController.php
@@ -6,7 +6,6 @@
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
-
$object_phid = $request->getURIData('objectPHID');
if ($object_phid) {
$object = id(new PhabricatorObjectQuery())
@@ -32,6 +31,17 @@
}
}
+ $phid = $request->getURIData('phid');
+ switch ($phid) {
+ case AphrontFormPolicyControl::getSelectProjectKey():
+ return $this->handleProjectRequest($request);
+ case AphrontFormPolicyControl::getSelectCustomKey():
+ $phid = null;
+ break;
+ default:
+ break;
+ }
+
$action_options = array(
PhabricatorPolicy::ACTION_ALLOW => pht('Allow'),
PhabricatorPolicy::ACTION_DENY => pht('Deny'),
@@ -55,7 +65,6 @@
'value' => null,
);
- $phid = $request->getURIData('phid');
if ($phid) {
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
@@ -253,4 +262,79 @@
return id(new AphrontDialogResponse())->setDialog($dialog);
}
+ private function handleProjectRequest(AphrontRequest $request) {
+ $viewer = $this->getViewer();
+
+ $errors = array();
+ $e_project = true;
+
+ if ($request->isFormPost()) {
+ $project_phids = $request->getArr('projectPHIDs');
+ $project_phid = head($project_phids);
+
+ $project = id(new PhabricatorObjectQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($project_phid))
+ ->executeOne();
+
+ if ($project) {
+ // Save this project as one of the user's most recently used projects,
+ // so we'll show it by default in future menus.
+
+ $pref_key = PhabricatorUserPreferences::PREFERENCE_FAVORITE_POLICIES;
+
+ $preferences = $viewer->loadPreferences();
+ $favorites = $preferences->getPreference($pref_key);
+ if (!is_array($favorites)) {
+ $favorites = array();
+ }
+
+ // Add this, or move it to the end of the list.
+ unset($favorites[$project_phid]);
+ $favorites[$project_phid] = true;
+
+ $preferences->setPreference($pref_key, $favorites);
+ $preferences->save();
+
+ $data = array(
+ 'phid' => $project->getPHID(),
+ 'info' => array(
+ 'name' => $project->getName(),
+ 'full' => $project->getName(),
+ 'icon' => $project->getDisplayIconIcon(),
+ ),
+ );
+
+ return id(new AphrontAjaxResponse())->setContent($data);
+ } else {
+ $errors[] = pht('You must choose a project.');
+ $e_project = pht('Required');
+ }
+ }
+
+ $project_datasource = id(new PhabricatorProjectDatasource())
+ ->setParameters(
+ array(
+ 'policy' => 1,
+ ));
+
+ $form = id(new AphrontFormView())
+ ->setUser($viewer)
+ ->appendControl(
+ id(new AphrontFormTokenizerControl())
+ ->setLabel(pht('Members Of'))
+ ->setName('projectPHIDs')
+ ->setLimit(1)
+ ->setError($e_project)
+ ->setDatasource($project_datasource));
+
+ return $this->newDialog()
+ ->setWidth(AphrontDialogView::WIDTH_FORM)
+ ->setErrors($errors)
+ ->setTitle(pht('Select Project'))
+ ->appendForm($form)
+ ->addSubmitButton(pht('Done'))
+ ->addCancelButton('#');
+ }
+
}
diff --git a/src/applications/policy/query/PhabricatorPolicyQuery.php b/src/applications/policy/query/PhabricatorPolicyQuery.php
--- a/src/applications/policy/query/PhabricatorPolicyQuery.php
+++ b/src/applications/policy/query/PhabricatorPolicyQuery.php
@@ -195,10 +195,48 @@
$viewer = $this->getViewer();
if ($viewer->getPHID()) {
- $projects = id(new PhabricatorProjectQuery())
- ->setViewer($viewer)
- ->withMemberPHIDs(array($viewer->getPHID()))
- ->execute();
+ $pref_key = PhabricatorUserPreferences::PREFERENCE_FAVORITE_POLICIES;
+
+ $favorite_limit = 10;
+ $default_limit = 5;
+
+ // If possible, show the user's 10 most recently used projects.
+ $preferences = $viewer->loadPreferences();
+ $favorites = $preferences->getPreference($pref_key);
+ if (!is_array($favorites)) {
+ $favorites = array();
+ }
+ $favorite_phids = array_keys($favorites);
+ $favorite_phids = array_slice($favorite_phids, -$favorite_limit);
+
+ if ($favorite_phids) {
+ $projects = id(new PhabricatorProjectQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($favorite_phids)
+ ->withIsMilestone(false)
+ ->setLimit($favorite_limit)
+ ->execute();
+ $projects = mpull($projects, null, 'getPHID');
+ } else {
+ $projects = array();
+ }
+
+ // If we didn't find enough favorites, add some default projects. These
+ // are just arbitrary projects that the viewer is a member of, but may
+ // be useful on smaller installs and for new users until they can use
+ // the control enough time to establish useful favorites.
+ if (count($projects) < $default_limit) {
+ $default_projects = id(new PhabricatorProjectQuery())
+ ->setViewer($viewer)
+ ->withMemberPHIDs(array($viewer->getPHID()))
+ ->withIsMilestone(false)
+ ->setLimit($default_limit)
+ ->execute();
+ $default_projects = mpull($default_projects, null, 'getPHID');
+ $projects = $projects + $default_projects;
+ $projects = array_slice($projects, 0, $default_limit);
+ }
+
foreach ($projects as $project) {
$phids[] = $project->getPHID();
}
diff --git a/src/applications/project/policyrule/PhabricatorProjectsPolicyRule.php b/src/applications/project/policyrule/PhabricatorProjectsPolicyRule.php
--- a/src/applications/project/policyrule/PhabricatorProjectsPolicyRule.php
+++ b/src/applications/project/policyrule/PhabricatorProjectsPolicyRule.php
@@ -48,7 +48,13 @@
}
public function getValueControlTemplate() {
- return $this->getDatasourceTemplate(new PhabricatorProjectDatasource());
+ $datasource = id(new PhabricatorProjectDatasource())
+ ->setParameters(
+ array(
+ 'policy' => 1,
+ ));
+
+ return $this->getDatasourceTemplate($datasource);
}
public function getRuleOrder() {
diff --git a/src/applications/project/typeahead/PhabricatorProjectDatasource.php b/src/applications/project/typeahead/PhabricatorProjectDatasource.php
--- a/src/applications/project/typeahead/PhabricatorProjectDatasource.php
+++ b/src/applications/project/typeahead/PhabricatorProjectDatasource.php
@@ -32,6 +32,12 @@
$query->withNameTokens($tokens);
}
+ // If this is for policy selection, prevent users from using milestones.
+ $for_policy = $this->getParameter('policy');
+ if ($for_policy) {
+ $query->withIsMilestone(false);
+ }
+
$projs = $this->executeQuery($query);
$projs = mpull($projs, null, 'getPHID');
diff --git a/src/applications/settings/storage/PhabricatorUserPreferences.php b/src/applications/settings/storage/PhabricatorUserPreferences.php
--- a/src/applications/settings/storage/PhabricatorUserPreferences.php
+++ b/src/applications/settings/storage/PhabricatorUserPreferences.php
@@ -42,6 +42,7 @@
const PREFERENCE_DESKTOP_NOTIFICATIONS = 'desktop-notifications';
const PREFERENCE_PROFILE_MENU_COLLAPSED = 'profile-menu.collapsed';
+ const PREFERENCE_FAVORITE_POLICIES = 'policy.favorites';
// These are in an unusual order for historic reasons.
const MAILTAG_PREFERENCE_NOTIFY = 0;
diff --git a/src/view/form/control/AphrontFormPolicyControl.php b/src/view/form/control/AphrontFormPolicyControl.php
--- a/src/view/form/control/AphrontFormPolicyControl.php
+++ b/src/view/form/control/AphrontFormPolicyControl.php
@@ -103,6 +103,24 @@
protected function getOptions() {
$capability = $this->capability;
$policies = $this->policies;
+ $viewer = $this->getUser();
+
+ // Check if we're missing the policy for the current control value. This
+ // is unusual, but can occur if the user is submitting a form and selected
+ // an unusual project as a policy but the change has not been saved yet.
+ $policy_map = mpull($policies, null, 'getPHID');
+ $value = $this->getValue();
+ if ($value && empty($policy_map[$value])) {
+ $handle = id(new PhabricatorHandleQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($value))
+ ->executeOne();
+ if ($handle->isComplete()) {
+ $policies[] = PhabricatorPolicy::newFromPolicyAndHandle(
+ $value,
+ $handle);
+ }
+ }
// Exclude object policies which don't make sense here. This primarily
// filters object policies associated from template capabilities (like
@@ -143,12 +161,27 @@
'name' => $policy_short_name,
'full' => $policy->getName(),
'icon' => $policy->getIcon(),
+ 'sort' => phutil_utf8_strtolower($policy->getName()),
);
}
+ $type_project = PhabricatorPolicyType::TYPE_PROJECT;
+
+ $placeholder = id(new PhabricatorPolicy())
+ ->setName(pht('Other Project...'))
+ ->setIcon('fa-search');
+
+ $options[$type_project] = isort($options[$type_project], 'sort');
+
+ $options[$type_project][$this->getSelectProjectKey()] = array(
+ 'name' => $placeholder->getName(),
+ 'full' => $placeholder->getName(),
+ 'icon' => $placeholder->getIcon(),
+ );
+
// If we were passed several custom policy options, throw away the ones
// which aren't the value for this capability. For example, an object might
- // have a custom view pollicy and a custom edit policy. When we render
+ // have a custom view policy and a custom edit policy. When we render
// the selector for "Can View", we don't want to show the "Can Edit"
// custom policy -- if we did, the menu would look like this:
//
@@ -172,7 +205,7 @@
if (empty($options[$type_custom])) {
$placeholder = new PhabricatorPolicy();
$placeholder->setName(pht('Custom Policy...'));
- $options[$type_custom][$this->getCustomPolicyPlaceholder()] = array(
+ $options[$type_custom][$this->getSelectCustomKey()] = array(
'name' => $placeholder->getName(),
'full' => $placeholder->getName(),
'icon' => $placeholder->getIcon(),
@@ -266,12 +299,12 @@
'options' => $flat_options,
'groups' => array_keys($options),
'order' => $order,
- 'icons' => $icons,
'labels' => $labels,
'value' => $this->getValue(),
'capability' => $this->capability,
'editURI' => '/policy/edit/'.$context_path,
- 'customPlaceholder' => $this->getCustomPolicyPlaceholder(),
+ 'customKey' => $this->getSelectCustomKey(),
+ 'projectKey' => $this->getSelectProjectKey(),
'disabled' => $this->getDisabled(),
));
@@ -322,8 +355,12 @@
));
}
- private function getCustomPolicyPlaceholder() {
- return 'custom:placeholder';
+ public static function getSelectCustomKey() {
+ return 'select:custom';
+ }
+
+ public static function getSelectProjectKey() {
+ return 'select:project';
}
private function buildSpacesControl() {
diff --git a/webroot/rsrc/js/application/policy/behavior-policy-control.js b/webroot/rsrc/js/application/policy/behavior-policy-control.js
--- a/webroot/rsrc/js/application/policy/behavior-policy-control.js
+++ b/webroot/rsrc/js/application/policy/behavior-policy-control.js
@@ -7,6 +7,7 @@
* phuix-action-list-view
* phuix-action-view
* javelin-workflow
+ * phuix-icon-view
* @javelin
*/
JX.behavior('policy-control', function(config) {
@@ -57,6 +58,21 @@
.start();
}, phid);
+ } else if (phid == config.projectKey) {
+ onselect = JX.bind(null, function(phid) {
+ var uri = get_custom_uri(phid, config.capability);
+
+ new JX.Workflow(uri)
+ .setHandler(function(response) {
+ if (!response.phid) {
+ return;
+ }
+
+ add_policy(phid, response.phid, response.info);
+ select_policy(response.phid);
+ })
+ .start();
+ }, phid);
} else {
onselect = JX.bind(null, select_policy, phid);
}
@@ -101,20 +117,22 @@
name = JX.$N('span', {title: option.full}, name);
}
- return [JX.$H(config.icons[option.icon]), name];
+ return [render_icon(option.icon), name];
};
+ var render_icon = function(icon) {
+ return new JX.PHUIXIconView()
+ .setIcon(icon)
+ .getNode();
+ };
/**
* Get the workflow URI to create or edit a policy with a given PHID.
*/
var get_custom_uri = function(phid, capability) {
- var uri = config.editURI;
- if (phid != config.customPlaceholder) {
- uri += phid + '/';
- }
- uri += '?capability=' + capability;
- return uri;
+ return JX.$U(config.editURI + phid + '/')
+ .setQueryParam('capability', capability)
+ .toString();
};
@@ -123,16 +141,28 @@
* policies after the user edits them.
*/
var replace_policy = function(old_phid, new_phid, info) {
+ return add_policy(old_phid, new_phid, info, true);
+ };
+
+
+ /**
+ * Add a new policy above an existing one, optionally replacing it.
+ */
+ var add_policy = function(old_phid, new_phid, info, replace) {
+ if (config.options[new_phid]) {
+ return;
+ }
+
config.options[new_phid] = info;
+
for (var k in config.order) {
for (var ii = 0; ii < config.order[k].length; ii++) {
if (config.order[k][ii] == old_phid) {
- config.order[k][ii] = new_phid;
+ config.order[k].splice(ii, (replace ? 1 : 0), new_phid);
return;
}
}
}
};
-
});

File Metadata

Mime Type
text/plain
Expires
Sun, Mar 9, 2:40 AM (23 h, 32 m ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7385942
Default Alt Text
D15184.id36665.diff (13 KB)

Event Timeline