using C4IT.FASD.Base; using C4IT.FASD.Cockpit.Communication; using C4IT.Logging; using C4IT.MultiLanguage; using FasdDesktopUi.Basics; using FasdDesktopUi.Basics.CustomEvents; using FasdDesktopUi.Basics.Enums; using FasdDesktopUi.Basics.Helper; using FasdDesktopUi.Basics.Models; using FasdDesktopUi.Basics.UiActions; using FasdDesktopUi.Basics.UserControls; using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using System.Windows.Threading; using FasdDesktopUi.Basics.Services.SupportCaseSearchService; using FasdDesktopUi.Basics.Services.RelationService; using static C4IT.Logging.cLogManager; using static FasdDesktopUi.Basics.UserControls.SearchBar; namespace FasdDesktopUi.Pages.AdvancedSearchPage { public partial class AdvancedSearchPage : Window, ISearchUiProvider, INotifyPropertyChanged { public SupportCaseSearchService SearchService { get; } = new SupportCaseSearchService(new RelationService()); // New Advanced-Search-Page IN PROGRESS // Next steps: // 1. Open F4SD Cockpit when user is clicked // 2. Display filter name next to filter icon when filter is selected // 3. When F4SD Tray-Icon is clicked, open the new advanced search page SearchBar.ChangedSearchValueDelegate ChangedSearchValue { get; set; } private cF4sdApiSearchResultRelation preSelectedRelation = null; private static readonly object _currentSearchTaskLock = new object(); private static Guid? CurrentSearchTaskId = null; private static CancellationTokenSource CurrentSearchTaskToken = null; private readonly DispatcherTimer _searchTimer = new DispatcherTimer(); private string searchValue = null; public static AdvancedSearchPage advancedSearchPage; public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); // Mapping zwischen den Enum-Classes, da Integer-Values nicht alle identisch sind. Evtl. im nachgang anpassen, aber vorher alle Abhängigkeiten prüfen private enumFasdInformationClass MapSearchResultClassToInformationClass(enumF4sdSearchResultClass searchResultClass) { switch (searchResultClass) { case enumF4sdSearchResultClass.Computer: return enumFasdInformationClass.Computer; case enumF4sdSearchResultClass.User: return enumFasdInformationClass.User; case enumF4sdSearchResultClass.Ticket: return enumFasdInformationClass.Ticket; case enumF4sdSearchResultClass.VirtualSession: return enumFasdInformationClass.VirtualSession; case enumF4sdSearchResultClass.MobileDevice: return enumFasdInformationClass.MobileDevice; default: return enumFasdInformationClass.Unknown; } } // Alle Suchergebnisse (ungefilterte Sammlung) public cFasdApiSearchResultCollection SearchResults { get; set; } // Gefilterte Suchergebnisse (für die Anzeige in der UI) private cFasdApiSearchResultCollection _filteredSearchResults; public cFasdApiSearchResultCollection FilteredSearchResults { get => _filteredSearchResults; set { if (_filteredSearchResults != value) { _filteredSearchResults = value; OnPropertyChanged(); } } } // Aktuell gesetzte Filterklassen public HashSet StoredFilterInformationClasses { get; set; } public static readonly DependencyProperty FilterInformationClassesProperty = DependencyProperty.Register("FilterInformationClasses", typeof(HashSet), typeof(AdvancedSearchPage), new PropertyMetadata(new HashSet())); // Ermittelte Filter bei der Suche public HashSet FilterInformationClasses { get { return (HashSet)GetValue(FilterInformationClassesProperty); } set { SetValue(FilterInformationClassesProperty, value); } } public eSearchStatus searchStatus { get; private set; } = eSearchStatus.message; public AdvancedSearchPage() { InitializeComponent(); AddCustomEventHandlers(); SearchBarUc.ActivateManualSearch(); advancedSearchPage = this; } private void AddCustomEventHandlers() { AddHandler(cUiActionBase.UiActionClickedEvent, new cUiActionBase.UiActionEventHandlerDelegate(UiActionWasTriggered)); SearchBarUc.ChangedSearchValue = ChangedSearchValueActionAsync; SearchBarUc.CancledSearchAction = CancledSearchAction; } private Task ChangedSearchValueActionAsync(cFilteredResults filteredResults) { this.Dispatcher.Invoke(new Action(() => { try { if (filteredResults?.Results is null) { SetSearchResultVisibility(false); return; } // Speichern der Kompletten Suchergebnisse SearchResults = filteredResults.Results; // Verfügbare FilterIcons dynamisch ermitteln UpdateFilterInformationClasses(); // Filter anwenden ApplyFilters(); // Menü mit den gefilterten Ergebnissen anzeigen string menuHeaderText = filteredResults?.PreSelectedRelation?.DisplayName; ResultMenu.ShowSearchResults(filteredResults, menuHeaderText, this); preSelectedRelation = filteredResults.PreSelectedRelation; SetSearchResultVisibility(true); } catch (Exception E) { cLogManager.LogException(E); } })); return Task.CompletedTask; } private void UpdateFilterInformationClasses() { if (SearchResults == null) { FilterInformationClasses = new HashSet(); return; } var filter = new HashSet(); foreach (var group in SearchResults) { foreach (var entry in group.Value) { var mappedClass = MapSearchResultClassToInformationClass(entry.Type); filter.Add(mappedClass); } } FilterInformationClasses = filter; } private void SearchFilterBar_FilterChanged(object sender, SearchFilterChangedEventArgs e) { // Filter speichern StoredFilterInformationClasses = new HashSet(e.InformationClassesToShow); // Filter anwenden ApplyFilters(); // cFilteredResults erzeugen var filteredResultWrapper = new cFilteredResults() { Results = FilteredSearchResults, PreSelectedRelation = preSelectedRelation }; // Ergebnisse im Menü neu anzeigen string menuHeaderText = preSelectedRelation?.DisplayName; ResultMenu.ShowSearchResults(filteredResultWrapper, menuHeaderText, this); } private void ApplyFilters() { if (SearchResults == null) return; // Wenn keine Filter ausgewählt wurden, alles übernehmen if (StoredFilterInformationClasses == null || StoredFilterInformationClasses.Count == 0) { FilteredSearchResults = SearchResults; return; } // Wenn ein oder mehrere Filter gesetzt, dann Ergebnisse entsprechend gefiltert anzeigen cFasdApiSearchResultCollection filtered = new cFasdApiSearchResultCollection(); foreach (var group in SearchResults) { var filteredEntries = group.Value .Where(entry => StoredFilterInformationClasses.Contains(MapSearchResultClassToInformationClass(entry.Type)) ) .ToList(); if (filteredEntries.Any()) { filtered.Add(group.Key, filteredEntries); } } FilteredSearchResults = filtered; } public void ShowSearchRelations(cSearchHistorySearchResultEntry searchHistoryEntry, IRelationService relationService, ISearchUiProvider searchUiProvider) { try { ResultMenu.SetHeaderText(searchHistoryEntry.HeaderText, hideDetailsCheckbox: false); cSearchManager.ResolveRelations(searchHistoryEntry.Relations); ILookup relationsLookup = searchHistoryEntry.Relations .OrderBy(r => r.UsingLevel) .ThenBy(r => r.LastUsed) .ToLookup(GetInformationClass, GetMenuData); ResultMenu.UpdateSearchRelations(relationsLookup); ResultMenu.SetHeaderText(searchHistoryEntry.HeaderText, hideDetailsCheckbox: false); } catch (Exception ex) { LogException(ex); } cMenuDataBase GetMenuData(cF4sdApiSearchResultRelation relation) { var requiredInformationClasses = new List() { cF4sdIdentityEntry.GetFromSearchResult(relation.Type) }; bool isEnabled = cHealthCardDataHelper.HasAvailableHealthCard(requiredInformationClasses); string disabledReason = null; if (!isEnabled) { disabledReason = cMultiLanguageSupport.GetItem("Searchbar.NoValidHealthcard") ?? string.Empty; } string trailingText = null; return new cMenuDataSearchRelation(relation) { MenuText = relation.DisplayName, TrailingText = trailingText, UiAction = new cUiProcessSearchRelationAction(searchHistoryEntry, relation, relationService, searchUiProvider) { DisplayType = isEnabled ? enumActionDisplayType.enabled : enumActionDisplayType.disabled, Description = isEnabled ? string.Empty : disabledReason, AlternativeDescription = isEnabled ? string.Empty : disabledReason } }; } enumFasdInformationClass GetInformationClass(cF4sdApiSearchResultRelation relation) => cF4sdIdentityEntry.GetFromSearchResult(relation.Type); } public void SetPendingInformationClasses(HashSet informationClasses) => ResultMenu.SetPendingInformationClasses(informationClasses); public void UpdatePendingInformationClasses(HashSet informationClasses) => ResultMenu.UpdatePendingInformationClasses(informationClasses); private async void UiActionWasTriggered(object sender, UiActionEventArgs e) { try { if (e.UiAction == null) return; Mouse.OverrideCursor = Cursors.Wait; switch (e.UiAction) { case cUiProcessSearchResultAction searchResultAction: searchResultAction.PreSelectedSearchRelation = preSelectedRelation; break; case cUiProcessSearchRelationAction _: case cUiProcessSearchHistoryEntry _: break; default: var _t = e.UiAction?.GetType().Name; Debug.Assert(true, $"The UI action '{_t}' is not supported in detailed page"); CancledSearchAction(); return; } SetSearchResultVisibility(true); if (!await e.UiAction.RunUiActionAsync(sender, null, false, null)) CancledSearchAction(); } catch (Exception E) { LogException(E); } finally { Mouse.OverrideCursor = null; } } private void SetSearchResultVisibility(bool isVisible) { SearchResultBorder.Visibility = isVisible ? Visibility.Visible : Visibility.Collapsed; BodyStack.Visibility = (isVisible || SearchResultBorder.IsVisible) ? Visibility.Visible : Visibility.Collapsed; } private void CancledSearchAction() { SearchBarUc.Clear(); ResultMenu.ShowSearchResults(new cFilteredResults(), null, this); SetSearchResultVisibility(false); SetSearchHistoryVisibility(false); Visibility = Visibility.Hidden; } #region Cleanup when implementing search public void SetSearchHistoryVisibility(bool isVisible) { SearchHistoryBorder.Visibility = isVisible && !SearchHistory.IsEmpty() ? Visibility.Visible : Visibility.Collapsed; BodyStack.Visibility = (isVisible || SearchResultBorder.IsVisible) ? Visibility.Visible : Visibility.Collapsed; } private void ComputerDomainSearch(cComputerDomainSearchParameters searchInfo) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { var _h = Task.Run(async () => { try { var _result = await cFasdCockpitCommunicationBase.Instance.GetComputerSearchResults(searchInfo.name, searchInfo.domain); var strInfo = string.Format(cMultiLanguageSupport.GetItem("Searchbar.ComputerSearch.Info"), searchInfo.name); ShowExternalSearchInfo(strInfo, _result, enumF4sdSearchResultClass.Computer); } catch (Exception E) { LogException(E); } }); } catch (Exception E) { LogException(E); } finally { LogMethodEnd(CM); } } private void ShowExternalSearchInfo(string strInfo, cFasdApiSearchResultCollection resultEntry, enumF4sdSearchResultClass Class) { var filteredResults = new cFilteredResults(resultEntry) { AutoContinue = true }; var _t = ShowExternalSearchInfoAsync(strInfo, filteredResults, Class); } private async Task ShowExternalSearchInfoAsync(string strInfo, cFilteredResults result, enumF4sdSearchResultClass Class) { try { await this.Dispatcher.InvokeAsync(async () => { try { await SearchBarUc.SetFixedSearchResultAsync(Class, strInfo, result); if (result.AutoContinue && result.Results?.Count == 1) { ResultMenu.IndexOfSelectedResultItem = 0; ResultMenu.SelectCurrentResultItem(); } Show(); } catch (Exception E) { LogException(E); } }); } catch (Exception E) { LogException(E); } } public string SearchValue { get => searchValue; set { if (value != searchValue) { searchValue = value; if (searchStatus == eSearchStatus.active) UpdateSearchResults(); } } } private async Task UpdateSearchResultsAsync(string searchValue, CancellationToken cancellationToken) { try { if (string.IsNullOrEmpty(searchValue)) { await ChangedSearchValue.Invoke(null); } else { cFasdApiSearchResultCollection filteredResultDictionary = null; try { var taskId = await cFasdCockpitCommunicationBase.Instance.GetSearchResultsStart(searchValue, CancellationToken.None); lock (_currentSearchTaskLock) { CurrentSearchTaskId = taskId; } var _canceled = false; if (!cancellationToken.IsCancellationRequested && taskId != null) try { filteredResultDictionary = await cFasdCockpitCommunicationBase.Instance.GetSearchResultsResult((Guid)taskId, cancellationToken); } catch (TaskCanceledException) { _canceled = true; LogEntry("UpdateSearchResultsAsync was cancelled by token (4).", LogLevels.Debug); } _canceled |= cancellationToken.IsCancellationRequested; taskId = null; if (_canceled) lock (_currentSearchTaskLock) { taskId = CurrentSearchTaskId; CurrentSearchTaskId = null; } if (taskId != null) await cFasdCockpitCommunicationBase.Instance.GetSearchResultsStop((Guid)taskId, CancellationToken.None); } catch (TaskCanceledException) { LogEntry("UpdateSearchResultsAsync was cancelled by token (3).", LogLevels.Debug); } catch (Exception E) { LogException(E); } if (cancellationToken.IsCancellationRequested) { LogEntry("UpdateSearchResultsAsync was cancelled by token (2).", LogLevels.Debug); return; } cFilteredResults filteredResult = new cFilteredResults(filteredResultDictionary); await ChangedSearchValue.Invoke(filteredResult); } LogEntry("UpdateSearchResultsAsync finished successful.", LogLevels.Warning); } catch (TaskCanceledException) { LogEntry("UpdateSearchResultsAsync was cancelled by token (1).", LogLevels.Warning); } catch (Exception E) { LogException(E); } } private void UpdateSearchResults() { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { if (ChangedSearchValue == null) return; if (_searchTimer.IsEnabled) _searchTimer.Stop(); _searchTimer.Start(); var stopTask = Task.Run(() => { CancelRunningSearchTaskAsync(); }); } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } } private void CancelRunningSearchTaskAsync() { // cancel the search task, in case of a running search task lock (_currentSearchTaskLock) { if (CurrentSearchTaskToken != null) { LogEntry("Canceling current running search..."); CurrentSearchTaskToken.Cancel(true); CurrentSearchTaskToken = null; } } } public void ShowLoadingTextItem(string itemText) { SetSearchHistoryVisibility(false); ResultMenu.ShowLoadingTextItem(itemText); } #endregion } }