@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Make query panels editable by normal humans

Summary:
Ref T4986. Instead of requiring you to know engine class names and copy/paste URLs, provide select dropdowns that use SCARY JAVASCRIPT to do magical things.

I think this is mostly reasonable, the only issue is that it's hard to create a panel out of a completely ad-hoc query (you'd have to save it, then create a panel out of the saved query, then remove the saved query). Once we develop T5307 we can do a better job of this.

Test Plan: See screenshots.

Reviewers: chad

Reviewed By: chad

Subscribers: epriestley

Maniphest Tasks: T4986

Differential Revision: https://secure.phabricator.com/D9572

+195 -3
+7
resources/celerity/map.php
··· 354 354 'rsrc/js/application/countdown/timer.js' => '361e3ed3', 355 355 'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e', 356 356 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => 'fa187a68', 357 + 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '3be3eef5', 357 358 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'aa077691', 358 359 'rsrc/js/application/differential/ChangesetViewManager.js' => 'db09a523', 359 360 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => 'f2441746', ··· 555 556 'javelin-behavior-dark-console' => 'e9fdb5e5', 556 557 'javelin-behavior-dashboard-async-panel' => '469c0d9e', 557 558 'javelin-behavior-dashboard-move-panels' => 'fa187a68', 559 + 'javelin-behavior-dashboard-query-panel-select' => '3be3eef5', 558 560 'javelin-behavior-dashboard-tab-panel' => 'aa077691', 559 561 'javelin-behavior-device' => '03d6ed07', 560 562 'javelin-behavior-differential-add-reviewers-and-ccs' => '533a187b', ··· 1112 1114 0 => 'javelin-behavior', 1113 1115 1 => 'javelin-dom', 1114 1116 2 => 'phortune-credit-card-form', 1117 + ), 1118 + '3be3eef5' => 1119 + array( 1120 + 0 => 'javelin-behavior', 1121 + 1 => 'javelin-dom', 1115 1122 ), 1116 1123 '40b1ff90' => 1117 1124 array(
+2
src/__phutil_library_map__.php
··· 1497 1497 'PhabricatorDashboardPanelRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php', 1498 1498 'PhabricatorDashboardPanelSearchApplicationCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelSearchApplicationCustomField.php', 1499 1499 'PhabricatorDashboardPanelSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php', 1500 + 'PhabricatorDashboardPanelSearchQueryCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelSearchQueryCustomField.php', 1500 1501 'PhabricatorDashboardPanelTransaction' => 'applications/dashboard/storage/PhabricatorDashboardPanelTransaction.php', 1501 1502 'PhabricatorDashboardPanelTransactionEditor' => 'applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php', 1502 1503 'PhabricatorDashboardPanelTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardPanelTransactionQuery.php', ··· 4317 4318 'PhabricatorDashboardPanelRenderingEngine' => 'Phobject', 4318 4319 'PhabricatorDashboardPanelSearchApplicationCustomField' => 'PhabricatorStandardCustomField', 4319 4320 'PhabricatorDashboardPanelSearchEngine' => 'PhabricatorApplicationSearchEngine', 4321 + 'PhabricatorDashboardPanelSearchQueryCustomField' => 'PhabricatorStandardCustomField', 4320 4322 'PhabricatorDashboardPanelTransaction' => 'PhabricatorApplicationTransaction', 4321 4323 'PhabricatorDashboardPanelTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 4322 4324 'PhabricatorDashboardPanelTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+7
src/applications/dashboard/controller/PhabricatorDashboardPanelEditController.php
··· 120 120 ->setViewer($viewer) 121 121 ->readFieldsFromStorage($panel); 122 122 123 + if ($is_create && !$request->isFormPost()) { 124 + $panel->requireImplementation()->initializeFieldsFromRequest( 125 + $panel, 126 + $field_list, 127 + $request); 128 + } 129 + 123 130 $validation_exception = null; 124 131 125 132 // NOTE: We require 'edit' to distinguish between the "Choose a Type"
+1
src/applications/dashboard/customfield/PhabricatorDashboardPanelSearchApplicationCustomField.php
··· 30 30 } 31 31 32 32 return id(new AphrontFormSelectControl()) 33 + ->setID($this->getFieldControlID()) 33 34 ->setLabel($this->getFieldName()) 34 35 ->setCaption($this->getCaption()) 35 36 ->setName($this->getFieldKey())
+67
src/applications/dashboard/customfield/PhabricatorDashboardPanelSearchQueryCustomField.php
··· 1 + <?php 2 + 3 + final class PhabricatorDashboardPanelSearchQueryCustomField 4 + extends PhabricatorStandardCustomField { 5 + 6 + public function getFieldType() { 7 + return 'search.query'; 8 + } 9 + 10 + public function shouldAppearInApplicationSearch() { 11 + return false; 12 + } 13 + 14 + public function renderEditControl(array $handles) { 15 + 16 + $engines = id(new PhutilSymbolLoader()) 17 + ->setAncestorClass('PhabricatorApplicationSearchEngine') 18 + ->loadObjects(); 19 + 20 + $value = $this->getFieldValue(); 21 + 22 + $queries = array(); 23 + foreach ($engines as $engine_class => $engine) { 24 + $engine->setViewer($this->getViewer()); 25 + $engine_queries = $engine->loadEnabledNamedQueries(); 26 + $query_map = mpull($engine_queries, 'getQueryName', 'getQueryKey'); 27 + asort($query_map); 28 + 29 + foreach ($query_map as $key => $name) { 30 + $queries[$engine_class][] = array('key' => $key, 'name' => $name); 31 + if ($key == $value) { 32 + $seen = true; 33 + } 34 + } 35 + } 36 + 37 + if (strlen($value) && !$seen) { 38 + $name = pht('Custom Query ("%s")', $value); 39 + } else { 40 + $name = pht('(None)'); 41 + } 42 + 43 + $options = array($value => $name); 44 + 45 + $app_control_key = $this->getFieldConfigValue('control.application'); 46 + Javelin::initBehavior( 47 + 'dashboard-query-panel-select', 48 + array( 49 + 'applicationID' => $this->getFieldControlID($app_control_key), 50 + 'queryID' => $this->getFieldControlID(), 51 + 'options' => $queries, 52 + 'value' => array( 53 + 'key' => strlen($value) ? $value : null, 54 + 'name' => $name 55 + ) 56 + )); 57 + 58 + return id(new AphrontFormSelectControl()) 59 + ->setID($this->getFieldControlID()) 60 + ->setLabel($this->getFieldName()) 61 + ->setCaption($this->getCaption()) 62 + ->setName($this->getFieldKey()) 63 + ->setValue($this->getFieldValue()) 64 + ->setOptions($options); 65 + } 66 + 67 + }
+7
src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php
··· 12 12 PhabricatorDashboardPanel $panel, 13 13 PhabricatorDashboardPanelRenderingEngine $engine); 14 14 15 + public function initializeFieldsFromRequest( 16 + PhabricatorDashboardPanel $panel, 17 + PhabricatorCustomFieldList $field_list, 18 + AphrontRequest $request) { 19 + return; 20 + } 21 + 15 22 /** 16 23 * Should this panel pull content in over AJAX? 17 24 *
+32 -3
src/applications/dashboard/paneltype/PhabricatorDashboardPanelTypeQuery.php
··· 25 25 ), 26 26 'key' => array( 27 27 'name' => pht('Query'), 28 - 'type' => 'text', 28 + 'type' => 'search.query', 29 + 'control.application' => 'class', 29 30 ), 30 31 'limit' => array( 31 - 'name' => pht('Maximum Number of Items'), 32 - 'caption' => pht('Leave this blank for the default number of items'), 32 + 'name' => pht('Limit'), 33 + 'caption' => pht('Leave this blank for the default number of items.'), 33 34 'type' => 'text', 34 35 ), 35 36 ); 36 37 } 38 + 39 + public function initializeFieldsFromRequest( 40 + PhabricatorDashboardPanel $panel, 41 + PhabricatorCustomFieldList $field_list, 42 + AphrontRequest $request) { 43 + 44 + $map = array(); 45 + if (strlen($request->getStr('engine'))) { 46 + $map['class'] = $request->getStr('engine'); 47 + } 48 + 49 + if (strlen($request->getStr('query'))) { 50 + $map['key'] = $request->getStr('query'); 51 + } 52 + 53 + $full_map = array(); 54 + foreach ($map as $key => $value) { 55 + $full_map["std:dashboard:core:{$key}"] = $value; 56 + } 57 + 58 + foreach ($field_list->getFields() as $field) { 59 + $field_key = $field->getFieldKey(); 60 + if (isset($full_map[$field_key])) { 61 + $field->setValueFromStorage($full_map[$field_key]); 62 + } 63 + } 64 + } 65 + 37 66 38 67 public function renderPanelContent( 39 68 PhabricatorUser $viewer,
+3
src/applications/search/controller/PhabricatorApplicationSearchController.php
··· 174 174 pht('Save Custom Query...')); 175 175 } 176 176 177 + // TODO: A "Create Dashboard Panel" action goes here somewhere once 178 + // we sort out T5307. 179 + 177 180 $form->appendChild($submit); 178 181 $filter_view = id(new AphrontListFilterView())->appendChild($form); 179 182
+5
src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php
··· 391 391 return $this->getFieldValue(); 392 392 } 393 393 394 + public function getFieldControlID($key = null) { 395 + $key = coalesce($key, $this->getRawStandardFieldKey()); 396 + return 'std:control:'.$key; 397 + } 398 + 394 399 }
+64
webroot/rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js
··· 1 + /** 2 + * @provides javelin-behavior-dashboard-query-panel-select 3 + * @requires javelin-behavior 4 + * javelin-dom 5 + */ 6 + 7 + /** 8 + * When editing a "Query" panel on dashboards, make the "Query" selector control 9 + * dynamically update in response to changes to the "Engine" selector control. 10 + */ 11 + JX.behavior('dashboard-query-panel-select', function(config) { 12 + 13 + var app_control = JX.$(config.applicationID); 14 + var query_control = JX.$(config.queryID); 15 + 16 + // If we have a currently-selected query, add it to the appropriate group 17 + // in the options list if it does not already exist. 18 + if (config.value.key !== null) { 19 + var app = app_control.value; 20 + if (!(app in config.options)) { 21 + config.options[app] = []; 22 + } 23 + 24 + var found = false; 25 + for (var ii = 0; ii < config.options[app].length; ii++) { 26 + if (config.options[app][ii].key == config.value.key) { 27 + found = true; 28 + break; 29 + } 30 + } 31 + 32 + if (!found) { 33 + config.options[app] = [config.value].concat(config.options[app]); 34 + } 35 + } 36 + 37 + // When the user changes the selected search engine, update the query 38 + // control to show avialable queries for that engine. 39 + function update() { 40 + var app = app_control.value; 41 + 42 + var old_value = query_control.value; 43 + var new_value = null; 44 + 45 + var options = config.options[app] || []; 46 + var nodes = []; 47 + for (var ii = 0; ii < options.length; ii++) { 48 + if (new_value === null) { 49 + new_value = options[ii].key; 50 + } 51 + if (options[ii].key == old_value) { 52 + new_value = options[ii].key; 53 + } 54 + nodes.push(JX.$N('option', {value: options[ii].key}, options[ii].name)); 55 + } 56 + 57 + JX.DOM.setContent(query_control, nodes); 58 + query_control.value = new_value; 59 + } 60 + 61 + JX.DOM.listen(app_control, 'change', null, function(e) { update(); }); 62 + update(); 63 + 64 + });