mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-20 04:20:55 +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/countdown/timer.js' => '361e3ed3',
|
||||||
'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e',
|
'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-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/dashboard/behavior-dashboard-tab-panel.js' => 'aa077691',
|
||||||
'rsrc/js/application/differential/ChangesetViewManager.js' => 'db09a523',
|
'rsrc/js/application/differential/ChangesetViewManager.js' => 'db09a523',
|
||||||
'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => 'f2441746',
|
'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => 'f2441746',
|
||||||
|
@ -555,6 +556,7 @@ return array(
|
||||||
'javelin-behavior-dark-console' => 'e9fdb5e5',
|
'javelin-behavior-dark-console' => 'e9fdb5e5',
|
||||||
'javelin-behavior-dashboard-async-panel' => '469c0d9e',
|
'javelin-behavior-dashboard-async-panel' => '469c0d9e',
|
||||||
'javelin-behavior-dashboard-move-panels' => 'fa187a68',
|
'javelin-behavior-dashboard-move-panels' => 'fa187a68',
|
||||||
|
'javelin-behavior-dashboard-query-panel-select' => '3be3eef5',
|
||||||
'javelin-behavior-dashboard-tab-panel' => 'aa077691',
|
'javelin-behavior-dashboard-tab-panel' => 'aa077691',
|
||||||
'javelin-behavior-device' => '03d6ed07',
|
'javelin-behavior-device' => '03d6ed07',
|
||||||
'javelin-behavior-differential-add-reviewers-and-ccs' => '533a187b',
|
'javelin-behavior-differential-add-reviewers-and-ccs' => '533a187b',
|
||||||
|
@ -1113,6 +1115,11 @@ return array(
|
||||||
1 => 'javelin-dom',
|
1 => 'javelin-dom',
|
||||||
2 => 'phortune-credit-card-form',
|
2 => 'phortune-credit-card-form',
|
||||||
),
|
),
|
||||||
|
'3be3eef5' =>
|
||||||
|
array(
|
||||||
|
0 => 'javelin-behavior',
|
||||||
|
1 => 'javelin-dom',
|
||||||
|
),
|
||||||
'40b1ff90' =>
|
'40b1ff90' =>
|
||||||
array(
|
array(
|
||||||
0 => 'javelin-behavior',
|
0 => 'javelin-behavior',
|
||||||
|
|
|
@ -1497,6 +1497,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorDashboardPanelRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php',
|
'PhabricatorDashboardPanelRenderingEngine' => 'applications/dashboard/engine/PhabricatorDashboardPanelRenderingEngine.php',
|
||||||
'PhabricatorDashboardPanelSearchApplicationCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelSearchApplicationCustomField.php',
|
'PhabricatorDashboardPanelSearchApplicationCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelSearchApplicationCustomField.php',
|
||||||
'PhabricatorDashboardPanelSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php',
|
'PhabricatorDashboardPanelSearchEngine' => 'applications/dashboard/query/PhabricatorDashboardPanelSearchEngine.php',
|
||||||
|
'PhabricatorDashboardPanelSearchQueryCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelSearchQueryCustomField.php',
|
||||||
'PhabricatorDashboardPanelTransaction' => 'applications/dashboard/storage/PhabricatorDashboardPanelTransaction.php',
|
'PhabricatorDashboardPanelTransaction' => 'applications/dashboard/storage/PhabricatorDashboardPanelTransaction.php',
|
||||||
'PhabricatorDashboardPanelTransactionEditor' => 'applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php',
|
'PhabricatorDashboardPanelTransactionEditor' => 'applications/dashboard/editor/PhabricatorDashboardPanelTransactionEditor.php',
|
||||||
'PhabricatorDashboardPanelTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardPanelTransactionQuery.php',
|
'PhabricatorDashboardPanelTransactionQuery' => 'applications/dashboard/query/PhabricatorDashboardPanelTransactionQuery.php',
|
||||||
|
@ -4317,6 +4318,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorDashboardPanelRenderingEngine' => 'Phobject',
|
'PhabricatorDashboardPanelRenderingEngine' => 'Phobject',
|
||||||
'PhabricatorDashboardPanelSearchApplicationCustomField' => 'PhabricatorStandardCustomField',
|
'PhabricatorDashboardPanelSearchApplicationCustomField' => 'PhabricatorStandardCustomField',
|
||||||
'PhabricatorDashboardPanelSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
'PhabricatorDashboardPanelSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||||
|
'PhabricatorDashboardPanelSearchQueryCustomField' => 'PhabricatorStandardCustomField',
|
||||||
'PhabricatorDashboardPanelTransaction' => 'PhabricatorApplicationTransaction',
|
'PhabricatorDashboardPanelTransaction' => 'PhabricatorApplicationTransaction',
|
||||||
'PhabricatorDashboardPanelTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
|
'PhabricatorDashboardPanelTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||||
'PhabricatorDashboardPanelTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
'PhabricatorDashboardPanelTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||||
|
|
|
@ -120,6 +120,13 @@ final class PhabricatorDashboardPanelEditController
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->readFieldsFromStorage($panel);
|
->readFieldsFromStorage($panel);
|
||||||
|
|
||||||
|
if ($is_create && !$request->isFormPost()) {
|
||||||
|
$panel->requireImplementation()->initializeFieldsFromRequest(
|
||||||
|
$panel,
|
||||||
|
$field_list,
|
||||||
|
$request);
|
||||||
|
}
|
||||||
|
|
||||||
$validation_exception = null;
|
$validation_exception = null;
|
||||||
|
|
||||||
// NOTE: We require 'edit' to distinguish between the "Choose a Type"
|
// NOTE: We require 'edit' to distinguish between the "Choose a Type"
|
||||||
|
|
|
@ -30,6 +30,7 @@ final class PhabricatorDashboardPanelSearchApplicationCustomField
|
||||||
}
|
}
|
||||||
|
|
||||||
return id(new AphrontFormSelectControl())
|
return id(new AphrontFormSelectControl())
|
||||||
|
->setID($this->getFieldControlID())
|
||||||
->setLabel($this->getFieldName())
|
->setLabel($this->getFieldName())
|
||||||
->setCaption($this->getCaption())
|
->setCaption($this->getCaption())
|
||||||
->setName($this->getFieldKey())
|
->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,
|
PhabricatorDashboardPanel $panel,
|
||||||
PhabricatorDashboardPanelRenderingEngine $engine);
|
PhabricatorDashboardPanelRenderingEngine $engine);
|
||||||
|
|
||||||
|
public function initializeFieldsFromRequest(
|
||||||
|
PhabricatorDashboardPanel $panel,
|
||||||
|
PhabricatorCustomFieldList $field_list,
|
||||||
|
AphrontRequest $request) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should this panel pull content in over AJAX?
|
* Should this panel pull content in over AJAX?
|
||||||
*
|
*
|
||||||
|
|
|
@ -25,16 +25,45 @@ final class PhabricatorDashboardPanelTypeQuery
|
||||||
),
|
),
|
||||||
'key' => array(
|
'key' => array(
|
||||||
'name' => pht('Query'),
|
'name' => pht('Query'),
|
||||||
'type' => 'text',
|
'type' => 'search.query',
|
||||||
|
'control.application' => 'class',
|
||||||
),
|
),
|
||||||
'limit' => array(
|
'limit' => array(
|
||||||
'name' => pht('Maximum Number of Items'),
|
'name' => pht('Limit'),
|
||||||
'caption' => pht('Leave this blank for the default number of items'),
|
'caption' => pht('Leave this blank for the default number of items.'),
|
||||||
'type' => 'text',
|
'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(
|
public function renderPanelContent(
|
||||||
PhabricatorUser $viewer,
|
PhabricatorUser $viewer,
|
||||||
PhabricatorDashboardPanel $panel,
|
PhabricatorDashboardPanel $panel,
|
||||||
|
|
|
@ -174,6 +174,9 @@ final class PhabricatorApplicationSearchController
|
||||||
pht('Save Custom Query...'));
|
pht('Save Custom Query...'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: A "Create Dashboard Panel" action goes here somewhere once
|
||||||
|
// we sort out T5307.
|
||||||
|
|
||||||
$form->appendChild($submit);
|
$form->appendChild($submit);
|
||||||
$filter_view = id(new AphrontListFilterView())->appendChild($form);
|
$filter_view = id(new AphrontListFilterView())->appendChild($form);
|
||||||
|
|
||||||
|
|
|
@ -391,4 +391,9 @@ abstract class PhabricatorStandardCustomField
|
||||||
return $this->getFieldValue();
|
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