77import org .phoebus .applications .queueserver .api .QueueItemAddBatch ;
88import org .phoebus .applications .queueserver .api .StatusResponse ;
99import org .phoebus .applications .queueserver .client .RunEngineService ;
10+ import org .phoebus .applications .queueserver .util .PlansCache ;
11+ import org .phoebus .applications .queueserver .util .PythonParameterConverter ;
12+ import org .phoebus .applications .queueserver .util .QueueItemSelectionEvent ;
1013import org .phoebus .applications .queueserver .util .StatusBus ;
1114import javafx .application .Platform ;
1215import javafx .beans .property .ReadOnlyObjectWrapper ;
@@ -47,6 +50,8 @@ public final class RePlanHistoryController implements Initializable {
4750 private final RunEngineService svc = new RunEngineService ();
4851 private final ObservableList <Row >rows = FXCollections .observableArrayList ();
4952 private final Map <String ,QueueItem > uid2item = new HashMap <>();
53+ private final Map <String , Map <String , Object >> allowedPlans = new HashMap <>();
54+ private final Map <String , Map <String , Object >> allowedInstructions = new HashMap <>();
5055
5156 private List <Integer > stickySel = List .of ();
5257 private boolean ignoreSel = false ;
@@ -106,6 +111,19 @@ public RePlanHistoryController(boolean viewOnly) {
106111 List .copyOf (table .getSelectionModel ().getSelectedIndices ());
107112 });
108113
114+ initializeAllowedInstructions ();
115+
116+ if (PlansCache .isLoaded ()) {
117+ allowedPlans .clear ();
118+ allowedPlans .putAll (PlansCache .get ());
119+ }
120+ PlansCache .addListener ((o2 , oldP , newP ) -> {
121+ Platform .runLater (() -> {
122+ allowedPlans .clear ();
123+ if (newP != null ) allowedPlans .putAll (newP );
124+ });
125+ });
126+
109127 ChangeListener <StatusResponse > l =
110128 (o ,oldV ,nv ) -> {
111129 // Run refresh in background thread to avoid blocking UI
@@ -263,6 +281,9 @@ private void copySelectedToQueue() {
263281 var sel = table .getSelectionModel ().getSelectedIndices ();
264282 if (sel .isEmpty ()) return ;
265283
284+ // Capture insert position before starting background thread
285+ String afterUid = QueueItemSelectionEvent .getInstance ().getLastSelectedUid ();
286+
266287 List <QueueItem > clones = sel .stream ()
267288 .map (rows ::get )
268289 .map (r -> uid2item .get (r .uid ))
@@ -279,13 +300,16 @@ private void copySelectedToQueue() {
279300 ))
280301 .toList ();
281302
282- try {
283- QueueItemAddBatch req =
284- new QueueItemAddBatch (clones , "GUI Client" , "primary" );
285- svc .queueItemAddBatch (req ); // service takes DTO, not Map
286- } catch (Exception ex ) {
287- logger .log (Level .WARNING , "Copy-to-Queue failed" , ex );
288- }
303+ new Thread (() -> {
304+ try {
305+ QueueItemAddBatch req =
306+ new QueueItemAddBatch (clones , "GUI Client" , "primary" , afterUid );
307+ List <String > newUids = svc .addBatchGetUids (req );
308+ QueueItemSelectionEvent .getInstance ().requestSelectByUids (newUids );
309+ } catch (Exception ex ) {
310+ logger .log (Level .WARNING , "Copy-to-Queue failed" , ex );
311+ }
312+ }).start ();
289313 }
290314
291315 private void clearHistory () {
@@ -365,17 +389,56 @@ private void restoreSelection(Collection<Integer> idx) {
365389 private static String firstLetter (String s ){
366390 return (s ==null ||s .isBlank ())?"" :s .substring (0 ,1 ).toUpperCase ();
367391 }
368- private static String fmtParams (QueueItem q ){
392+ private void initializeAllowedInstructions () {
393+ Map <String , Object > queueStopInstr = new HashMap <>();
394+ queueStopInstr .put ("name" , "queue_stop" );
395+ queueStopInstr .put ("description" , "Stop execution of the queue." );
396+ queueStopInstr .put ("parameters" , List .of ());
397+ allowedInstructions .put ("queue_stop" , queueStopInstr );
398+ }
399+
400+ @ SuppressWarnings ("unchecked" )
401+ private String fmtParams (QueueItem q ){
369402 String a = Optional .ofNullable (q .args ()).orElse (List .of ())
370- .stream ().map (Object ::toString ).collect (Collectors .joining (", " ));
371- String k = Optional .ofNullable (q .kwargs ()).orElse (Map .of ())
372- .entrySet ().stream ()
373- .map (e -> e .getKey ()+": " +e .getValue ())
403+ .stream ().map (PythonParameterConverter ::toPythonRepr )
374404 .collect (Collectors .joining (", " ));
405+
406+ Map <String , Object > itemInfo = null ;
407+ if ("plan" .equals (q .itemType ())) {
408+ itemInfo = allowedPlans .get (q .name ());
409+ } else if ("instruction" .equals (q .itemType ())) {
410+ itemInfo = allowedInstructions .get (q .name ());
411+ }
412+
413+ String k ;
414+ if (itemInfo != null ) {
415+ List <Map <String , Object >> parameters =
416+ (List <Map <String , Object >>) itemInfo .get ("parameters" );
417+ if (parameters != null ) {
418+ Map <String , Object > kwargs = Optional .ofNullable (q .kwargs ()).orElse (Map .of ());
419+ k = parameters .stream ()
420+ .map (p -> (String ) p .get ("name" ))
421+ .filter (kwargs ::containsKey )
422+ .map (name -> name + ": " + PythonParameterConverter .toPythonRepr (kwargs .get (name )))
423+ .collect (Collectors .joining (", " ));
424+ } else {
425+ k = formatKwargsDefault (q .kwargs ());
426+ }
427+ } else {
428+ k = formatKwargsDefault (q .kwargs ());
429+ }
430+
375431 return Stream .of (a ,k ).filter (s ->!s .isEmpty ())
376432 .collect (Collectors .joining (", " ));
377433 }
378434
435+ private static String formatKwargsDefault (Map <String , Object > kwargs ) {
436+ return Optional .ofNullable (kwargs ).orElse (Map .of ())
437+ .entrySet ().stream ()
438+ .map (e -> e .getKey () + ": " + PythonParameterConverter .toPythonRepr (e .getValue ()))
439+ .collect (Collectors .joining (", " ));
440+ }
441+
379442 private static String exitStatus (QueueItem q ){
380443 Map <String ,Object > res = q .result ();
381444
0 commit comments