1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-19 20:10: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:
epriestley 2014-06-16 12:27:12 -07:00
parent f1fa8fccb6
commit 5aae1ee034
10 changed files with 195 additions and 3 deletions

View file

@ -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',

View file

@ -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',

View file

@ -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"

View file

@ -30,6 +30,7 @@ final class PhabricatorDashboardPanelSearchApplicationCustomField
}
return id(new AphrontFormSelectControl())
->setID($this->getFieldControlID())
->setLabel($this->getFieldName())
->setCaption($this->getCaption())
->setName($this->getFieldKey())

View file

@ -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);
}
}

View file

@ -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?
*

View file

@ -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,

View file

@ -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);

View file

@ -391,4 +391,9 @@ abstract class PhabricatorStandardCustomField
return $this->getFieldValue();
}
public function getFieldControlID($key = null) {
$key = coalesce($key, $this->getRawStandardFieldKey());
return 'std:control:'.$key;
}
}

View file

@ -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();
});