diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 1bbfcc9910..09100f9933 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -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', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index c0d1187b0e..7846a57073 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -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', diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelEditController.php b/src/applications/dashboard/controller/PhabricatorDashboardPanelEditController.php index 58cfa6e15b..bdd576fb1d 100644 --- a/src/applications/dashboard/controller/PhabricatorDashboardPanelEditController.php +++ b/src/applications/dashboard/controller/PhabricatorDashboardPanelEditController.php @@ -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" diff --git a/src/applications/dashboard/customfield/PhabricatorDashboardPanelSearchApplicationCustomField.php b/src/applications/dashboard/customfield/PhabricatorDashboardPanelSearchApplicationCustomField.php index 97899359d1..20c9034f89 100644 --- a/src/applications/dashboard/customfield/PhabricatorDashboardPanelSearchApplicationCustomField.php +++ b/src/applications/dashboard/customfield/PhabricatorDashboardPanelSearchApplicationCustomField.php @@ -30,6 +30,7 @@ final class PhabricatorDashboardPanelSearchApplicationCustomField } return id(new AphrontFormSelectControl()) + ->setID($this->getFieldControlID()) ->setLabel($this->getFieldName()) ->setCaption($this->getCaption()) ->setName($this->getFieldKey()) diff --git a/src/applications/dashboard/customfield/PhabricatorDashboardPanelSearchQueryCustomField.php b/src/applications/dashboard/customfield/PhabricatorDashboardPanelSearchQueryCustomField.php new file mode 100644 index 0000000000..65ec40c9be --- /dev/null +++ b/src/applications/dashboard/customfield/PhabricatorDashboardPanelSearchQueryCustomField.php @@ -0,0 +1,67 @@ +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); + } + +} diff --git a/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php b/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php index 377c56b39f..53248e5f2a 100644 --- a/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php +++ b/src/applications/dashboard/paneltype/PhabricatorDashboardPanelType.php @@ -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? * diff --git a/src/applications/dashboard/paneltype/PhabricatorDashboardPanelTypeQuery.php b/src/applications/dashboard/paneltype/PhabricatorDashboardPanelTypeQuery.php index 040a3ade1f..bae946d1fb 100644 --- a/src/applications/dashboard/paneltype/PhabricatorDashboardPanelTypeQuery.php +++ b/src/applications/dashboard/paneltype/PhabricatorDashboardPanelTypeQuery.php @@ -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, diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index cbb9da48e5..42f831c246 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -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); diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php index 8f9394589c..4eafe7c264 100644 --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomField.php @@ -391,4 +391,9 @@ abstract class PhabricatorStandardCustomField return $this->getFieldValue(); } + public function getFieldControlID($key = null) { + $key = coalesce($key, $this->getRawStandardFieldKey()); + return 'std:control:'.$key; + } + } diff --git a/webroot/rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js b/webroot/rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js new file mode 100644 index 0000000000..d58283a19a --- /dev/null +++ b/webroot/rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js @@ -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(); + +});