mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-18 12:52:42 +01:00
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
This commit is contained in:
parent
f1fa8fccb6
commit
5aae1ee034
10 changed files with 195 additions and 3 deletions
|
@ -354,6 +354,7 @@ return array(
|
|||
'rsrc/js/application/countdown/timer.js' => '361e3ed3',
|
||||
'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e',
|
||||
'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => 'fa187a68',
|
||||
'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '3be3eef5',
|
||||
'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'aa077691',
|
||||
'rsrc/js/application/differential/ChangesetViewManager.js' => 'db09a523',
|
||||
'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => 'f2441746',
|
||||
|
@ -555,6 +556,7 @@ return array(
|
|||
'javelin-behavior-dark-console' => 'e9fdb5e5',
|
||||
'javelin-behavior-dashboard-async-panel' => '469c0d9e',
|
||||
'javelin-behavior-dashboard-move-panels' => 'fa187a68',
|
||||
'javelin-behavior-dashboard-query-panel-select' => '3be3eef5',
|
||||
'javelin-behavior-dashboard-tab-panel' => 'aa077691',
|
||||
'javelin-behavior-device' => '03d6ed07',
|
||||
'javelin-behavior-differential-add-reviewers-and-ccs' => '533a187b',
|
||||
|
@ -1113,6 +1115,11 @@ return array(
|
|||
1 => 'javelin-dom',
|
||||
2 => 'phortune-credit-card-form',
|
||||
),
|
||||
'3be3eef5' =>
|
||||
array(
|
||||
0 => 'javelin-behavior',
|
||||
1 => 'javelin-dom',
|
||||
),
|
||||
'40b1ff90' =>
|
||||
array(
|
||||
0 => 'javelin-behavior',
|
||||
|
|
|
@ -1497,6 +1497,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDashboardPanelRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php',
|
||||
'PhabricatorDashboardPanelSearchApplicationCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelSearchApplicationCustomField.php',
|
||||
'PhabricatorDashboardPanelSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php',
|
||||
'PhabricatorDashboardPanelSearchQueryCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelSearchQueryCustomField.php',
|
||||
'PhabricatorDashboardPanelTransaction' => 'applications/dashboard/storage/PhabricatorDashboardPanelTransaction.php',
|
||||
'PhabricatorDashboardPanelTransactionEditor' => 'applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php',
|
||||
'PhabricatorDashboardPanelTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardPanelTransactionQuery.php',
|
||||
|
@ -4317,6 +4318,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDashboardPanelRenderingEngine' => 'Phobject',
|
||||
'PhabricatorDashboardPanelSearchApplicationCustomField' => 'PhabricatorStandardCustomField',
|
||||
'PhabricatorDashboardPanelSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
'PhabricatorDashboardPanelSearchQueryCustomField' => 'PhabricatorStandardCustomField',
|
||||
'PhabricatorDashboardPanelTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'PhabricatorDashboardPanelTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'PhabricatorDashboardPanelTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
|
|
|
@ -120,6 +120,13 @@ final class PhabricatorDashboardPanelEditController
|
|||
->setViewer($viewer)
|
||||
->readFieldsFromStorage($panel);
|
||||
|
||||
if ($is_create && !$request->isFormPost()) {
|
||||
$panel->requireImplementation()->initializeFieldsFromRequest(
|
||||
$panel,
|
||||
$field_list,
|
||||
$request);
|
||||
}
|
||||
|
||||
$validation_exception = null;
|
||||
|
||||
// NOTE: We require 'edit' to distinguish between the "Choose a Type"
|
||||
|
|
|
@ -30,6 +30,7 @@ final class PhabricatorDashboardPanelSearchApplicationCustomField
|
|||
}
|
||||
|
||||
return id(new AphrontFormSelectControl())
|
||||
->setID($this->getFieldControlID())
|
||||
->setLabel($this->getFieldName())
|
||||
->setCaption($this->getCaption())
|
||||
->setName($this->getFieldKey())
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDashboardPanelSearchQueryCustomField
|
||||
extends PhabricatorStandardCustomField {
|
||||
|
||||
public function getFieldType() {
|
||||
return 'search.query';
|
||||
}
|
||||
|
||||
public function shouldAppearInApplicationSearch() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function renderEditControl(array $handles) {
|
||||
|
||||
$engines = id(new PhutilSymbolLoader())
|
||||
->setAncestorClass('PhabricatorApplicationSearchEngine')
|
||||
->loadObjects();
|
||||
|
||||
$value = $this->getFieldValue();
|
||||
|
||||
$queries = array();
|
||||
foreach ($engines as $engine_class => $engine) {
|
||||
$engine->setViewer($this->getViewer());
|
||||
$engine_queries = $engine->loadEnabledNamedQueries();
|
||||
$query_map = mpull($engine_queries, 'getQueryName', 'getQueryKey');
|
||||
asort($query_map);
|
||||
|
||||
foreach ($query_map as $key => $name) {
|
||||
$queries[$engine_class][] = array('key' => $key, 'name' => $name);
|
||||
if ($key == $value) {
|
||||
$seen = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen($value) && !$seen) {
|
||||
$name = pht('Custom Query ("%s")', $value);
|
||||
} else {
|
||||
$name = pht('(None)');
|
||||
}
|
||||
|
||||
$options = array($value => $name);
|
||||
|
||||
$app_control_key = $this->getFieldConfigValue('control.application');
|
||||
Javelin::initBehavior(
|
||||
'dashboard-query-panel-select',
|
||||
array(
|
||||
'applicationID' => $this->getFieldControlID($app_control_key),
|
||||
'queryID' => $this->getFieldControlID(),
|
||||
'options' => $queries,
|
||||
'value' => array(
|
||||
'key' => strlen($value) ? $value : null,
|
||||
'name' => $name
|
||||
)
|
||||
));
|
||||
|
||||
return id(new AphrontFormSelectControl())
|
||||
->setID($this->getFieldControlID())
|
||||
->setLabel($this->getFieldName())
|
||||
->setCaption($this->getCaption())
|
||||
->setName($this->getFieldKey())
|
||||
->setValue($this->getFieldValue())
|
||||
->setOptions($options);
|
||||
}
|
||||
|
||||
}
|
|
@ -12,6 +12,13 @@ abstract class PhabricatorDashboardPanelType extends Phobject {
|
|||
PhabricatorDashboardPanel $panel,
|
||||
PhabricatorDashboardPanelRenderingEngine $engine);
|
||||
|
||||
public function initializeFieldsFromRequest(
|
||||
PhabricatorDashboardPanel $panel,
|
||||
PhabricatorCustomFieldList $field_list,
|
||||
AphrontRequest $request) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should this panel pull content in over AJAX?
|
||||
*
|
||||
|
|
|
@ -25,16 +25,45 @@ final class PhabricatorDashboardPanelTypeQuery
|
|||
),
|
||||
'key' => array(
|
||||
'name' => pht('Query'),
|
||||
'type' => 'text',
|
||||
'type' => 'search.query',
|
||||
'control.application' => 'class',
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => pht('Maximum Number of Items'),
|
||||
'caption' => pht('Leave this blank for the default number of items'),
|
||||
'name' => pht('Limit'),
|
||||
'caption' => pht('Leave this blank for the default number of items.'),
|
||||
'type' => 'text',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function initializeFieldsFromRequest(
|
||||
PhabricatorDashboardPanel $panel,
|
||||
PhabricatorCustomFieldList $field_list,
|
||||
AphrontRequest $request) {
|
||||
|
||||
$map = array();
|
||||
if (strlen($request->getStr('engine'))) {
|
||||
$map['class'] = $request->getStr('engine');
|
||||
}
|
||||
|
||||
if (strlen($request->getStr('query'))) {
|
||||
$map['key'] = $request->getStr('query');
|
||||
}
|
||||
|
||||
$full_map = array();
|
||||
foreach ($map as $key => $value) {
|
||||
$full_map["std:dashboard:core:{$key}"] = $value;
|
||||
}
|
||||
|
||||
foreach ($field_list->getFields() as $field) {
|
||||
$field_key = $field->getFieldKey();
|
||||
if (isset($full_map[$field_key])) {
|
||||
$field->setValueFromStorage($full_map[$field_key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function renderPanelContent(
|
||||
PhabricatorUser $viewer,
|
||||
PhabricatorDashboardPanel $panel,
|
||||
|
|
|
@ -174,6 +174,9 @@ final class PhabricatorApplicationSearchController
|
|||
pht('Save Custom Query...'));
|
||||
}
|
||||
|
||||
// TODO: A "Create Dashboard Panel" action goes here somewhere once
|
||||
// we sort out T5307.
|
||||
|
||||
$form->appendChild($submit);
|
||||
$filter_view = id(new AphrontListFilterView())->appendChild($form);
|
||||
|
||||
|
|
|
@ -391,4 +391,9 @@ abstract class PhabricatorStandardCustomField
|
|||
return $this->getFieldValue();
|
||||
}
|
||||
|
||||
public function getFieldControlID($key = null) {
|
||||
$key = coalesce($key, $this->getRawStandardFieldKey());
|
||||
return 'std:control:'.$key;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* @provides javelin-behavior-dashboard-query-panel-select
|
||||
* @requires javelin-behavior
|
||||
* javelin-dom
|
||||
*/
|
||||
|
||||
/**
|
||||
* When editing a "Query" panel on dashboards, make the "Query" selector control
|
||||
* dynamically update in response to changes to the "Engine" selector control.
|
||||
*/
|
||||
JX.behavior('dashboard-query-panel-select', function(config) {
|
||||
|
||||
var app_control = JX.$(config.applicationID);
|
||||
var query_control = JX.$(config.queryID);
|
||||
|
||||
// If we have a currently-selected query, add it to the appropriate group
|
||||
// in the options list if it does not already exist.
|
||||
if (config.value.key !== null) {
|
||||
var app = app_control.value;
|
||||
if (!(app in config.options)) {
|
||||
config.options[app] = [];
|
||||
}
|
||||
|
||||
var found = false;
|
||||
for (var ii = 0; ii < config.options[app].length; ii++) {
|
||||
if (config.options[app][ii].key == config.value.key) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
config.options[app] = [config.value].concat(config.options[app]);
|
||||
}
|
||||
}
|
||||
|
||||
// When the user changes the selected search engine, update the query
|
||||
// control to show avialable queries for that engine.
|
||||
function update() {
|
||||
var app = app_control.value;
|
||||
|
||||
var old_value = query_control.value;
|
||||
var new_value = null;
|
||||
|
||||
var options = config.options[app] || [];
|
||||
var nodes = [];
|
||||
for (var ii = 0; ii < options.length; ii++) {
|
||||
if (new_value === null) {
|
||||
new_value = options[ii].key;
|
||||
}
|
||||
if (options[ii].key == old_value) {
|
||||
new_value = options[ii].key;
|
||||
}
|
||||
nodes.push(JX.$N('option', {value: options[ii].key}, options[ii].name));
|
||||
}
|
||||
|
||||
JX.DOM.setContent(query_control, nodes);
|
||||
query_control.value = new_value;
|
||||
}
|
||||
|
||||
JX.DOM.listen(app_control, 'change', null, function(e) { update(); });
|
||||
update();
|
||||
|
||||
});
|
Loading…
Reference in a new issue