1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.admincentral.findbar;
35
36 import static info.magnolia.admincentral.ViewportLayout.View.*;
37 import static java.util.stream.Collectors.*;
38
39 import info.magnolia.admincentral.ViewportLayout;
40 import info.magnolia.admincentral.findbar.layout.FilterLayout;
41 import info.magnolia.admincentral.findbar.search.ResultCollector;
42 import info.magnolia.admincentral.findbar.search.SupplierSorter;
43 import info.magnolia.cms.security.SecuritySupport;
44 import info.magnolia.cms.security.User;
45 import info.magnolia.context.Context;
46 import info.magnolia.event.EventBus;
47 import info.magnolia.i18nsystem.SimpleTranslator;
48 import info.magnolia.icons.MagnoliaIcons;
49 import info.magnolia.periscope.Periscope;
50 import info.magnolia.periscope.SupplierAwareSearchResult;
51 import info.magnolia.periscope.ai.speech.SpeechRecognizer;
52 import info.magnolia.periscope.operation.OperationRequest;
53 import info.magnolia.periscope.operation.OperationResult;
54 import info.magnolia.periscope.search.SearchQuery;
55 import info.magnolia.periscope.search.SearchResultSupplier;
56 import info.magnolia.periscope.search.jcr.JcrSearchResultSupplier;
57 import info.magnolia.periscope.tag.EmptyPeriscopeTagsProvider;
58 import info.magnolia.periscope.tag.PeriscopeTagsProvider;
59 import info.magnolia.ui.api.app.AppController;
60 import info.magnolia.ui.api.app.AppLifecycleEvent;
61 import info.magnolia.ui.api.app.AppLifecycleEventHandler;
62 import info.magnolia.ui.api.app.launcherlayout.AppLauncherGroupEntry;
63 import info.magnolia.ui.api.app.launcherlayout.AppLauncherLayout;
64 import info.magnolia.ui.api.app.launcherlayout.AppLauncherLayoutManager;
65 import info.magnolia.ui.api.event.AdmincentralEventBus;
66 import info.magnolia.ui.api.location.DefaultLocation;
67 import info.magnolia.ui.api.location.Location;
68 import info.magnolia.ui.api.location.LocationController;
69 import info.magnolia.ui.api.view.View;
70 import info.magnolia.ui.theme.ResurfaceTheme;
71 import info.magnolia.ui.vaadin.applauncher.AppLauncher;
72 import info.magnolia.util.OptionalConsumer;
73
74 import java.time.LocalTime;
75 import java.time.ZoneId;
76 import java.util.Arrays;
77 import java.util.Collections;
78 import java.util.HashSet;
79 import java.util.List;
80 import java.util.Optional;
81 import java.util.Set;
82 import java.util.concurrent.atomic.AtomicBoolean;
83 import java.util.stream.Stream;
84
85 import javax.inject.Inject;
86 import javax.inject.Named;
87 import javax.inject.Provider;
88
89 import org.apache.commons.collections4.CollectionUtils;
90 import org.apache.commons.lang3.StringUtils;
91 import org.slf4j.Logger;
92 import org.slf4j.LoggerFactory;
93 import org.vaadin.addons.ComboBoxMultiselect;
94
95 import com.google.common.collect.Sets;
96 import com.vaadin.addon.daterangefield.DateRangeField;
97 import com.vaadin.event.ShortcutAction;
98 import com.vaadin.event.ShortcutListener;
99 import com.vaadin.server.Page;
100 import com.vaadin.server.Sizeable;
101 import com.vaadin.ui.AbsoluteLayout;
102 import com.vaadin.ui.AbstractOrderedLayout;
103 import com.vaadin.ui.Button;
104 import com.vaadin.ui.CheckBox;
105 import com.vaadin.ui.Component;
106 import com.vaadin.ui.Label;
107 import com.vaadin.ui.Layout;
108 import com.vaadin.ui.UI;
109 import com.vaadin.ui.themes.ValoTheme;
110
111
112
113
114
115
116
117 public class FindBar implements View {
118
119 private static final Logger log = LoggerFactory.getLogger(FindBar.class);
120
121 private static final int DEFAULT_POLL_INTERVAL = -1;
122
123 private final FindBarLayout findBarLayout = new FindBarLayout();
124
125 private final AtomicBoolean isRecording = new AtomicBoolean(false);
126
127 private FindBarTextField findBarTextField;
128 private Button findBarButton;
129 private ComboBoxMultiselect<String> supplierField;
130 private ComboBoxMultiselect<String> tagField;
131 private ComboBoxMultiselect<String> editorField;
132 private CheckBox appBiasToggle;
133 private DateRangeField dateRangeField;
134 private FilterLayout filterLayout;
135 SearchResultsGrid resultsGrid;
136 private Set<SearchResultSupplier> availableResultSuppliers;
137
138 private final Periscope periscope;
139 private final FindBarConfigurationProvider configProvider;
140 private final PeriscopeTagsProvider periscopeTagsProvider;
141 private final SecuritySupport securitySupport;
142 private final SimpleTranslator i18n;
143 private final EventBus eventBus;
144 private final User currentUser;
145
146 private final LocationController locationController;
147 private final AppLauncher appLauncher;
148 private final AppLauncherLayoutManager appLauncherLayoutManager;
149 private final AppController appController;
150 private final ResultCollector resultCollector;
151 private final SupplierSorter sorter;
152 private final SpeechRecognizer speechRecognizer;
153
154 private SearchQuery latestQuery;
155 private String latestApp;
156 private String currentApp;
157
158 private final ViewportLayout viewportLayout;
159
160 @Inject
161 FindBar(Periscope periscope, SecuritySupport securitySupport, SpeechRecognizer speechRecognizer,
162 FindBarConfigurationProvider configProvider,
163 PeriscopeTagsProvider periscopeTagsProvider, @Named(AdmincentralEventBus.NAME) EventBus eventBus,
164 SimpleTranslator i18n, Provider<Context> context, LocationController locationController,
165 AppLauncherLayoutManager appLauncherLayoutManager, AppController appController,
166 ViewportLayout viewportLayout) {
167
168 this.periscope = periscope;
169 this.configProvider = configProvider;
170 this.periscopeTagsProvider = periscopeTagsProvider;
171 this.securitySupport = securitySupport;
172 this.i18n = i18n;
173 this.eventBus = eventBus;
174 this.currentUser = context.get().getUser();
175 this.locationController = locationController;
176 this.appLauncherLayoutManager = appLauncherLayoutManager;
177 this.appController = appController;
178 this.appLauncher = new AppLauncher(this::openApp);
179 this.resultCollector = new ResultCollector(periscope::search, this::updateResults);
180 this.speechRecognizer = speechRecognizer;
181
182 this.sorter = new SupplierSorter(Optional.ofNullable(configProvider.get().getSupplierOrder()).orElse(Collections.emptyList()));
183 this.viewportLayout = viewportLayout;
184 initUi();
185 }
186
187 private synchronized void updateResults(SearchQuery searchQuery, List<SupplierAwareSearchResult> results) {
188 findUi(this.asVaadinComponent()).ifPresent(ui -> ui.access(() -> {
189 filterLayout.updateTitleLabel(StringUtils.isEmpty(searchQuery.getQuery()), results.size());
190 resultsGrid.setSearchResults(results, searchQuery.getQuery());
191 }));
192 }
193
194 private Optional<UI> findUi(Component component) {
195 UI current = component.getUI();
196 if (current == null) {
197 throw new IllegalStateException("Top-level UI component could not be found");
198 }
199 return current.isAttached() ? Optional.of(current) : Optional.empty();
200 }
201
202 private void launchCommand() {
203 OptionalConsumer<OperationRequest> operationRequestConsumer = OptionalConsumer.of(findBarTextField.getCommand());
204 operationRequestConsumer.ifPresent(command -> {
205 OperationResult operationResult = periscope.executeOperation(command);
206 if (operationResult.isSucceeded()) {
207 findBarTextField.clearCommand();
208 findBarButton.setIcon(MagnoliaIcons.SEARCH);
209 }
210 }).ifNotPresent(() -> {
211 if (resultsGrid.hasSelection()) {
212 resultsGrid.triggerSelected();
213
214 Page.getCurrent().getJavaScript().execute("jQuery('input.findbar').blur()");
215 }
216 });
217 }
218
219 void initUi() {
220 Set<String> configuredSuppliers = configProvider.get().getSuppliers();
221 availableResultSuppliers = CollectionUtils.isEmpty(configuredSuppliers) ? new HashSet<>(periscope.getResultSuppliers())
222 : periscope.getResultSuppliers().stream().filter(s -> configuredSuppliers.contains(s.getName())).collect(toSet());
223
224 AppLauncherLayout userLayout = this.appLauncherLayoutManager.getLayoutForUser(currentUser);
225 List<SearchResultSupplier> inaccessibleAppSuppliers = availableResultSuppliers.stream()
226 .filter(supplier -> isForbiddenApp(supplier, userLayout))
227 .collect(toList());
228 availableResultSuppliers.removeAll(inaccessibleAppSuppliers);
229
230 Set<String> availableSupplierNames = availableResultSuppliers.stream()
231 .map(SearchResultSupplier::getName)
232 .collect(toSet());
233
234 if (CollectionUtils.isNotEmpty(configuredSuppliers)) {
235 Sets.difference(configuredSuppliers, availableSupplierNames).forEach(supplier -> log.warn("Search result supplier enabled in config, but not found: {}", supplier));
236 }
237
238 this.supplierField = new ComboBoxMultiselect<>(i18n.translate("findbar.type"));
239 this.supplierField.setItems(availableSupplierNames);
240
241 this.tagField = new ComboBoxMultiselect<>(i18n.translate("findbar.tags"));
242 this.tagField.setItems(periscopeTagsProvider.getAllTags());
243
244 this.editorField = new ComboBoxMultiselect<>(i18n.translate("findbar.lasteditor"));
245 this.editorField.setDataProvider(new LastEditorDataProvider(this::resolveUsers));
246 this.dateRangeField = new DateRangeField(i18n.translate("findbar.lastedited"));
247
248 this.appBiasToggle = new CheckBox();
249 this.appBiasToggle.setValue(true);
250
251 this.resultsGrid = new SearchResultsGrid(i18n, appBiasToggle);
252 this.resultsGrid.addSelectionCallback(searchResult -> this.periscope.resultPicked(buildSearchQuery(), searchResult));
253
254 final Layout resultsLayout = viewportLayout.getFindBarResultsLayout();
255 this.resultsGrid.setScrollingCallback(scrolled -> {
256 if (scrolled) {
257 resultsLayout.addStyleName("grid-scrolled");
258 } else {
259 resultsLayout.removeStyleName("grid-scrolled");
260 }
261 });
262
263 this.filterLayout = periscopeTagsProvider instanceof EmptyPeriscopeTagsProvider ?
264 new FilterLayout(dateRangeField, i18n, Arrays.asList(supplierField, editorField)) :
265 new FilterLayout(dateRangeField, i18n, Arrays.asList(supplierField, tagField, editorField));
266 resultsLayout.addComponents(filterLayout, resultsGrid);
267 resultsGrid.setSizeFull();
268 resultsLayout.addStyleName("filter-and-results");
269
270 userLayout.getGroups().forEach(group -> {
271 appLauncher.addAppGroup(group.getName(), group.getLabel(), group.getColor(), true, group.isClientGroup());
272 group.getApps().stream().map(AppLauncherGroupEntry::getAppDescriptor).forEach(descriptor -> {
273 appLauncher.addAppTile(descriptor.getName(), descriptor.getLabel(), descriptor.getIcon(), group.getName());
274 appLauncher.setAppActive(descriptor.getName(), appController.isAppStarted(descriptor.getName()));
275 });
276 });
277
278 final Label title = new Label("Apps");
279 title.addStyleName(ValoTheme.LABEL_H1);
280
281 AbstractOrderedLayout appLauncherLayout = viewportLayout.getAppLauncherLayout();
282 appLauncherLayout.setSizeFull();
283 appLauncher.setWidth(100, Sizeable.Unit.PERCENTAGE);
284 appLauncherLayout.addComponents(title, appLauncher);
285 appLauncherLayout.setExpandRatio(appLauncher, 1f);
286 appLauncherLayout.addStyleName("v-app-launcher");
287
288 appController.setViewport(view -> {
289 final Location currentAppLocation = appController.getCurrentAppLocation();
290 Component content = view == null ? null : view.asVaadinComponent();
291 Optional.ofNullable(viewportLayout.getActiveAppView(currentAppLocation)).ifPresent(v -> v.setContent(content));
292 });
293
294 this.supplierField.addValueChangeListener(event -> {
295 updateBiasToggleVisibility();
296 search(true);
297 });
298 this.tagField.addValueChangeListener(event -> search(true));
299 this.editorField.addValueChangeListener(event -> search(true));
300 this.dateRangeField.addValueChangeListener(event -> search(true));
301 this.appBiasToggle.addValueChangeListener(event -> search(true));
302
303 findBarTextField = new FindBarTextField(i18n, this::search);
304 findBarButton = new Button(MagnoliaIcons.SEARCH);
305 findBarButton.addStyleNames(ResurfaceTheme.BUTTON_ICON, "clear");
306 findBarButton.addClickListener(event -> {
307 viewportLayout.toggleFindBar();
308 if (viewportLayout.getActiveView() == FIND_BAR) {
309 findBarButton.addStyleName("active");
310 findBarTextField.focus();
311 } else {
312 findBarButton.removeStyleName("active");
313 }
314 });
315
316 Button clearTextButton = new Button(MagnoliaIcons.DELETE_SEARCH);
317 clearTextButton.addStyleNames(ResurfaceTheme.BUTTON_ICON, "clear");
318 clearTextButton.setVisible(false);
319 clearTextButton.addClickListener(event -> {
320 findBarTextField.clear();
321 findBarTextField.focus();
322 clearTextButton.setVisible(false);
323 });
324
325 Button voiceButton = new Button(MagnoliaIcons.MICROPHONE);
326 voiceButton.addStyleNames(ResurfaceTheme.BUTTON_ICON, "voice");
327
328
329 Button appLauncherButton = new Button(MagnoliaIcons.APP_LAUNCHER);
330 appLauncherButton.addStyleNames(ResurfaceTheme.BUTTON_ICON, "appLauncher");
331 appLauncherButton.addClickListener(event -> {
332 findBarButton.removeStyleName("active");
333 viewportLayout.toggleAppLauncher();
334 appLauncherButton.setStyleName("active", viewportLayout.getActiveView() == APP_LAUNCHER);
335 });
336 appLauncherButton.addBlurListener(event -> appLauncherButton.removeStyleName("active"));
337
338 this.findBarTextField.addFocusListener(event -> {
339 viewportLayout.showFindBar();
340 findBarTextField.setFocusStatus(true);
341 findBarButton.addStyleName("active");
342 appLauncherButton.removeStyleName("active");
343 if (StringUtils.isNotEmpty(findBarTextField.getValue().trim())) {
344 resultsGrid.selectFirstResult();
345 }
346 findBarTextField.getCommand().ifPresent(command -> {
347 findBarTextField.getCommandHint().setVisible(true);
348 findBarButton.setIcon(MagnoliaIcons.ENTER_ARROW);
349 findBarTextField.focus();
350 });
351 });
352 findBarTextField.addBlurListener(event -> {
353 findBarTextField.setFocusStatus(false);
354 findBarButton.removeStyleName("active");
355 findBarTextField.getCommandHint().setVisible(false);
356 findBarButton.setIcon(MagnoliaIcons.SEARCH);
357 });
358
359 final ShortcutListener enterListener = createShortcutListener("Enter", ShortcutAction.KeyCode.ENTER, new int[]{}, this::launchCommand);
360 findBarTextField.addShortcutListener(enterListener);
361 final ShortcutListener downListener = createShortcutListener("Arrow down", ShortcutAction.KeyCode.ARROW_DOWN, new int[]{}, () -> resultsGrid.selectNextResult());
362 findBarTextField.addShortcutListener(downListener);
363 final ShortcutListener upListener = createShortcutListener("Arrow up", ShortcutAction.KeyCode.ARROW_UP, new int[]{}, () -> resultsGrid.selectPreviousResult());
364 findBarTextField.addShortcutListener(upListener);
365
366 this.findBarTextField.addValueChangeListener(event -> {
367 if (StringUtils.isBlank(event.getValue())) {
368 clearTextButton.setVisible(false);
369 } else {
370 clearTextButton.setVisible(true);
371 }
372 search(true);
373 });
374
375
376 findBarLayout.addComponent(findBarTextField);
377 findBarLayout.addComponent(findBarButton, "top: 4px; left: 8px;");
378 findBarLayout.addComponent(findBarTextField.getCommandHint());
379 findBarLayout.addComponent(clearTextButton, "top: 6px; right: 40px;");
380 findBarLayout.addComponent(voiceButton, "top: 6px; right: 40px;");
381 findBarLayout.addComponent(appLauncherButton, "top: 6px; right: 8px;");
382 findBarLayout.addStyleNames("findbar");
383
384 findBarLayout.addLayoutClickListener(event -> findBarTextField.focus());
385
386 initBrowserSpeechRecognition(voiceButton, () -> {
387 voiceButton.setVisible(true);
388
389 AbsoluteLayout.ComponentPosition clearPosition = findBarLayout.getPosition(clearTextButton);
390 clearPosition.setCSSString("top: 6px; right: 72px;");
391 });
392
393 eventBus.addHandler(AppLifecycleEvent.class, new AppLifecycleEventHandler.Adapter() {
394
395 @Override
396 public void onAppFocused(AppLifecycleEvent event) {
397 log.debug("#onAppFocused: {}", event.getAppDescriptor().getName());
398 updateCurrentApp(event.getAppDescriptor().getName());
399 viewportLayout.showApp(event.getAppDescriptor().getName());
400 }
401
402 @Override
403 public void onAppStopped(AppLifecycleEvent event) {
404 log.debug("#onAppStopped: {}", event.getAppDescriptor().getName());
405 updateCurrentApp(null);
406 supplierField.deselectAll();
407 appLauncher.setAppActive(event.getAppDescriptor().getName(), false);
408 filterLayout.updateGlobalTokenLayout();
409
410 search();
411 viewportLayout.hideApp(event.getAppDescriptor().getName());
412 }
413
414 @Override
415 public void onAppStarted(AppLifecycleEvent event) {
416 appLauncher.setAppActive(event.getAppDescriptor().getName(), true);
417 log.debug("#onAppStarted: {}", event.getAppDescriptor().getName());
418 updateCurrentApp(event.getAppDescriptor().getName());
419 viewportLayout.showApp(event.getAppDescriptor().getName());
420 }
421
422 private void updateCurrentApp(String appName) {
423 latestApp = currentApp;
424 currentApp = appName;
425 updateBiasToggleVisibility();
426 if (!StringUtils.equals(currentApp, latestApp)) {
427 findBarTextField.clear();
428 search();
429 }
430 }
431 });
432 }
433
434 private ShortcutListener createShortcutListener(String caption, int keyCode, int[] modifiers, Runnable action) {
435 return new ShortcutListener(caption, keyCode, modifiers) {
436 @Override
437 public void handleAction(Object sender, Object target) {
438 action.run();
439 }
440 };
441 }
442
443 private void updateBiasToggleVisibility() {
444 if (!supplierField.isEmpty()) {
445 Set<String> selected = supplierField.getValue();
446 appBiasToggle.getParent().setVisible(selected.size() > 1 && selected.contains(currentApp));
447 return;
448 }
449
450 boolean currentAppHasSupplier = availableResultSuppliers.stream().anyMatch(s -> s.getName().equals(currentApp));
451 appBiasToggle.getParent().setVisible(currentAppHasSupplier);
452 }
453
454 private void openApp(String appName) {
455 locationController.goTo(new DefaultLocation(Location.LOCATION_TYPE_APP, appName));
456 viewportLayout.showApp(appName);
457 }
458
459 private void initBrowserSpeechRecognition(Button voiceButton, SpeechRecognizer.Listener supportedListener) {
460 speechRecognizer.attachTo(findBarLayout);
461
462
463 voiceButton.setVisible(false);
464 speechRecognizer.setSupportedListener(supportedListener);
465 speechRecognizer.addSpeechResultListener(transcript -> {
466 if (!transcript.isEmpty()) {
467 findBarTextField.setValue(transcript);
468 launchCommand();
469 }
470 isRecording.set(false);
471 });
472
473 voiceButton.addClickListener((Button.ClickListener) event -> {
474 if (isRecording.get()) {
475 isRecording.set(false);
476 speechRecognizer.abort();
477 return;
478 }
479 isRecording.set(true);
480 speechRecognizer.record();
481 });
482 }
483
484 @Override
485 public Component.Focusable asVaadinComponent() {
486 return findBarLayout;
487 }
488
489
490
491
492 private boolean isForbiddenApp(SearchResultSupplier supplier, AppLauncherLayout userLayout) {
493 return supplier instanceof JcrSearchResultSupplier &&
494 !Optional.ofNullable(((JcrSearchResultSupplier) supplier).getAppName())
495 .map(userLayout::containsApp)
496 .orElse(true);
497 }
498
499 protected synchronized void search() {
500 search(false);
501 }
502
503 protected synchronized void search(boolean forceSearch) {
504 resultsGrid.clear();
505 resultsGrid.dropSelection();
506
507 SearchQuery searchQuery = buildSearchQuery();
508
509 if (!forceSearch && searchQuery.equals(latestQuery) && StringUtils.equals(currentApp, latestApp)) {
510 return;
511 }
512 latestQuery = searchQuery;
513
514 FindBarConfiguration findBarConfiguration = configProvider.get();
515
516 searchQuery.setLimitPerSupplier(findBarConfiguration.getDefaultCountPerSupplier());
517
518
519 if (forceSearch && StringUtils.isNotBlank(searchQuery.getQuery())) {
520 Optional<OperationRequest> operationRequestOptional = periscope.sniffQuery(searchQuery.getQuery());
521 if (operationRequestOptional.isPresent()) {
522 findBarTextField.focus();
523 findBarTextField.setCommand(operationRequestOptional.get());
524 findBarButton.setIcon(MagnoliaIcons.ENTER_ARROW);
525 return;
526 }
527 findBarTextField.clearCommand();
528 findBarButton.setIcon(MagnoliaIcons.SEARCH);
529 } else {
530
531 findBarTextField.clearCommand();
532 findBarButton.setIcon(MagnoliaIcons.SEARCH);
533
534 if (hasNoExtraQueryCriteria()) {
535 searchQuery.setLimitPerSupplier(findBarConfiguration.getSuggestionCountPerSupplier());
536 }
537 }
538
539 List<SearchResultSupplier> suppliers = buildSuppliersOrder(searchQuery);
540
541 updateCurrentSupplier();
542
543
544 final UI currentUi = UI.getCurrent();
545 if (suppliers.size() > 0) {
546 currentUi.setPollInterval(100);
547 resultsGrid.addStyleName("loading");
548 }
549
550 resultCollector.search(searchQuery, suppliers, () -> {
551
552 if (latestQuery == searchQuery) {
553 currentUi.access(() -> {
554 currentUi.setPollInterval(DEFAULT_POLL_INTERVAL);
555 resultsGrid.removeStyleName("loading");
556 if (findBarTextField.hasFocus() && StringUtils.isNotEmpty(searchQuery.getQuery())) {
557 resultsGrid.selectFirstResult();
558 }
559 });
560 }
561 });
562 }
563
564 private void updateCurrentSupplier() {
565 if (!appBiasToggle.getParent().isVisible() || !appBiasToggle.getValue()) {
566 resultsGrid.setCurrentAppSupplier(null);
567 return;
568 }
569
570 Optional<SearchResultSupplier> currentSupplier = availableResultSuppliers.stream()
571 .filter(s -> s.getName().equals(currentApp))
572 .findAny();
573 resultsGrid.setCurrentAppSupplier(currentSupplier.orElse(null));
574 }
575
576
577
578
579 List<SearchResultSupplier> buildSuppliersOrder(SearchQuery searchQuery) {
580 Set<String> selectedSuppliers = this.supplierField.getValue();
581 Stream<SearchResultSupplier> suppliers = CollectionUtils.isEmpty(selectedSuppliers) ? availableResultSuppliers.stream() :
582 availableResultSuppliers.stream().filter(e -> selectedSuppliers.contains(e.getName()));
583
584 List<String> configuredSuppliers = configProvider.get().getSupplierOrder();
585 if (StringUtils.isBlank(searchQuery.getQuery()) && CollectionUtils.isNotEmpty(configuredSuppliers)) {
586 suppliers = suppliers.filter(supplier ->
587 configuredSuppliers.contains(supplier.getName()) || supplier.getName().equals(currentApp));
588 }
589
590 suppliers = suppliers.sorted((a, b) -> {
591 if (appBiasToggle.getValue()) {
592
593 if (a.getName().equals(currentApp)) {
594 return -1;
595 } else if (b.getName().equals(currentApp)) {
596 return 1;
597 }
598 }
599
600 return sorter.compare(a, b);
601 });
602
603 return suppliers.collect(toList());
604 }
605
606 private SearchQuery buildSearchQuery() {
607 SearchQuery.SearchQueryBuilder searchQueryBuilder = SearchQuery.builder()
608 .query(this.findBarTextField.getValue().trim())
609 .tags(this.tagField.getValue())
610 .editors(this.editorField.getValue())
611 .currentUser(this.currentUser);
612
613
614 if (this.dateRangeField.getBeginDateField().getValue() != null) {
615 searchQueryBuilder.startDate(this.dateRangeField.getBeginDateField().getValue().atStartOfDay(ZoneId.systemDefault()));
616 }
617 if (this.dateRangeField.getEndDateField().getValue() != null) {
618 searchQueryBuilder.endDate(this.dateRangeField.getEndDateField().getValue().atTime(LocalTime.MAX).atZone(ZoneId.systemDefault()));
619 }
620
621 return searchQueryBuilder.build();
622 }
623
624 private boolean hasNoExtraQueryCriteria() {
625 return CollectionUtils.isEmpty(editorField.getValue()) && CollectionUtils.isEmpty(tagField.getValue()) &&
626 CollectionUtils.isEmpty(supplierField.getValue()) && dateRangeField.getBeginDate() == null && dateRangeField.getEndDate() == null;
627 }
628
629 private List<String> resolveUsers() {
630 return Optional.ofNullable(configProvider.get().getEditorRoles())
631 .map(roles -> roles.stream().flatMap(role -> securitySupport.getUserManager().getUsersWithRole(role).stream()))
632 .orElseGet(() -> securitySupport.getUserManager().getAllUsers().stream().map(User::getName))
633 .collect(toList());
634 }
635
636
637
638
639 FindBarTextField getTextField() {
640 return findBarTextField;
641 }
642
643
644
645
646 ComboBoxMultiselect<String> getSupplierField() {
647 return supplierField;
648 }
649
650 private class FindBarLayout extends AbsoluteLayout implements Component.Focusable {
651
652 @Override
653 public void focus() {
654 findBarTextField.focus();
655 }
656
657 @Override
658 public int getTabIndex() {
659 return 0;
660 }
661
662 @Override
663 public void setTabIndex(int tabIndex) {
664 }
665 }
666 }