Page MenuHomePhabricator

Convert the application selector in "pin applications" to a typeahead
Closed, ResolvedPublic

Description

The list of pinnable applications is quite long and annoying to pick applications from as a dropdown, instead, it would be much nicer to be able to add applications using a typeahead.

Event Timeline

epriestley added a subscriber: epriestley.

You can find this UI in ConfigHome PagePin Application. Here's the relevant control:

Screen Shot 2016-08-22 at 2.08.07 PM.png (1×762 px, 98 KB)

This should be one of these things instead:

Screen Shot 2016-08-22 at 2.09.24 PM.png (179×1 px, 22 KB)

The code is in PhabricatorHomePreferencesSettingsPanel.php. Near line 81, the big select control is built:

$options_control = id(new AphrontFormSelectControl())
  ->setName('pin')
  ->setLabel(pht('Application'))
  ->setOptions($options)
  ->setDisabledOptions(array_keys($app_list));

You should be able to replace that with a AphrontFormTokenizerControl, passing a new PhabricatorApplicationDatasource() as the datasource, to convert this control into a typeahead/tokenizer. Use setLimit(1) to preserve the existing behavior (otherwise, it will be confusing if users type multiple applications).

You can look at other uses of AphrontFormTokenizerControl in the codebase for reference.

The current select uses setDisabledOptions() to disable applications you have already selected. There isn't a trivial analog for this in the tokenizer, but I think we can probably just drop this behavior and trust that users won't type the same application several times. If they do, we can revisit this later.

To test:

  • Type some application name to pin it.
  • Make sure that typing an application which is already on the list doesn't completely explode horribly (fine if the behavior isn't 100% perfect).
  • Make sure that typing nothing (i.e., no application name) doesn't completely explode.
eadler added a project: Restricted Project.Aug 23 2016, 4:50 PM
eadler added a subscriber: eadler.

Alright I'm making a bit of progress here. Posting this update on the chance that there's something obvious I'm missing. The AphrontFormTokenizerControl behaves a little bit differently than the AphrontFormSelectControl. Specifically, this is what the form post data looks like the old way (SelectControl):

pasted_file (175×360 px, 12 KB)

And here's what it looks like with the AphrontFormTokenizerControl:

pasted_file (181×448 px, 13 KB)

The main differences being:

  1. pin becomes an array with the tokenizer (because you can typically select multiple options)
  2. The format of the application names received from PhabricatorApplicationDatasource is different than what is returned by $app->getName()

The end result is that when I click "Pin Application" on the TokenizerControl the form gets cleared out and nothing else happens. The form doesn't close and the application doesn't get added to the list of pinned applications.

Ah, right -- I think those are both expected and that you're on the right track. You should be able to use $request->getStrList(...) instead of $request->getStr(...) to read the list, then head() to grab the first (item if it exists).

You should be able to do something like this to convert an application PHID into a classname (in theory, we should probably switch the internal storage to PHIDs, but this is probably not trivial and the distinction is sort of meaningless for Applications anyway, at least today):

$app = id(new PhabricatorApplicationQuery())
  ->setViewer($viewer)
  ->withPHIDs(array($phid))
  ->executeOne();
if ($app) {
  $class_name = get_class($app);
}

@epriestley PHP noob question: what do you do for debugging? I'm having a hard time just looking at the values of the variables involved here:

$pins = $request->getStrList('pin');
$pin = head($pins);
$app = id(new PhabricatorApplicationQuery())
  ->setViewer($viewer)
  ->withPHIDs(array($phid))
  ->executeOne();
if ($app) {
  $pin = get_class($app);
}

Macro noidea:

print_r($variable);
look at web page.

Oh! var_dump(...) or print_r(...) should send stuff to a big pink header on the page.

Or phlog(...) should send stuff to /var/log/apache/error.log or similar.

You can also sometimes throw new Exception(...) or die(...), particularly if you want to stop execution at a particular point (for example, if you're testing a form submission and don't want the actual effects to happen). With print_r(..., true) you can get a string back instead of immediate output, so throw new Exception(print_r($value, true)) is sometimes useful.

The Phabricator config option debug.stop-on-redirect is occasionally useful too (when debugging form submissions or weird redirect stuff).

Occasionally, it's useful to cheat stuff out of a process with Filesystem::writeFile('/tmp/thing.txt', serialize($value)); or similar: load the page, then go look at what got written to the file.

Finally, there are some actual PHP debuggers with "step into / step over / examine variables" sorts of features -- I think "XDebug" is the major one -- but I haven't really used them or found much need for them so I don't have too much guidance there. I think I do about 95% of debugging with print_r() / var_dump(), which is low-tech but feels pretty effective most of the time.