Add Phoenix remote desktop, action connector, documentation engine, and gamification support. Refactor support-case processing and search/action UI, and update installer assets and dependencies.
478 lines
22 KiB
C#
478 lines
22 KiB
C#
using C4IT.FASD.Base;
|
|
using C4IT.FASD.Cockpit.Communication;
|
|
using FasdCockpitCommunication.RemoteDesktopCommunication;
|
|
using FasdDesktopUi.Basics.CustomEvents;
|
|
using FasdDesktopUi.Basics.Enums;
|
|
using FasdDesktopUi.Basics.Models;
|
|
using FasdDesktopUi.Basics.Services.Models;
|
|
using FasdDesktopUi.Basics.Services.SupportCase.Processors;
|
|
using FasdDesktopUi.Basics.UiActions;
|
|
using FasdDesktopUi.Pages.DetailsPage.Models;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using static C4IT.Logging.cLogManager;
|
|
|
|
namespace FasdDesktopUi.Basics.Services.SupportCase.Controllers
|
|
{
|
|
/// <summary>
|
|
/// Used to manage the <see cref="ISupportCase"/> for the UI via the <see cref="ISupportCaseProcessor"/>
|
|
/// </summary>
|
|
public class SupportCaseController
|
|
{
|
|
private ISupportCaseProcessor _supportCaseProcessor;
|
|
private cF4sdApiSearchResultRelation _focusedRelation;
|
|
private readonly Dictionary<enumFasdInformationClass, cF4sdApiSearchResultRelation> _selectedRelations = new Dictionary<enumFasdInformationClass, cF4sdApiSearchResultRelation>();
|
|
private cHealthCard _selectedHealthcard = null;
|
|
private bool _hasDirectionConnection = false;
|
|
public cSupportCaseDataProvider SupportCaseDataProviderArtifact { get => _supportCaseProcessor?.SupportCaseDataProviderArtifact; }
|
|
|
|
internal void SetSupportCaseProcessor(ISupportCaseProcessor supportCaseProcessor, IEnumerable<cF4sdIdentityEntry> preselectedIdentities)
|
|
{
|
|
IEnumerable<cF4sdApiSearchResultRelation> preselectedRelations = GetPreselectedRelations(supportCaseProcessor.GetCaseRelations(), preselectedIdentities);
|
|
|
|
ResetSelectedRelations(preselectedRelations);
|
|
_hasDirectionConnection = false;
|
|
|
|
if (_supportCaseProcessor != null)
|
|
{
|
|
_supportCaseProcessor.AvailableCaseRelationsAdded -= HandleAvailableCaseRelationsAdded;
|
|
_supportCaseProcessor.CaseDataChanged -= HandleCaseDataChanged;
|
|
_supportCaseProcessor.SupportCaseDataProviderArtifact.DirectConnectionHelper.DirectConnectionChanged -= HandleDirectConnectionChanged;
|
|
}
|
|
|
|
_supportCaseProcessor = supportCaseProcessor;
|
|
|
|
_supportCaseProcessor.AvailableCaseRelationsAdded += HandleAvailableCaseRelationsAdded;
|
|
_supportCaseProcessor.CaseDataChanged += HandleCaseDataChanged;
|
|
_supportCaseProcessor.SupportCaseDataProviderArtifact.DirectConnectionHelper.DirectConnectionChanged += HandleDirectConnectionChanged;
|
|
}
|
|
|
|
private IEnumerable<cF4sdApiSearchResultRelation> GetPreselectedRelations
|
|
(ILookup<enumFasdInformationClass, cF4sdApiSearchResultRelation> loadedRelations, IEnumerable<cF4sdIdentityEntry> preselectedIdentities)
|
|
{
|
|
List<cF4sdApiSearchResultRelation> preselectedRelations = new List<cF4sdApiSearchResultRelation>();
|
|
|
|
foreach (var relationType in loadedRelations)
|
|
{
|
|
foreach (var relation in relationType)
|
|
{
|
|
if (relation.Identities.All(ri => preselectedIdentities.Any(i => i.Id == ri.Id)))
|
|
preselectedRelations.Add(relation);
|
|
}
|
|
}
|
|
|
|
return preselectedRelations;
|
|
}
|
|
|
|
private void ResetSelectedRelations(IEnumerable<cF4sdApiSearchResultRelation> preselectedRelations)
|
|
{
|
|
try
|
|
{
|
|
_selectedRelations.Clear();
|
|
|
|
if (preselectedRelations is null)
|
|
return;
|
|
|
|
foreach (var relation in preselectedRelations)
|
|
{
|
|
_selectedRelations[cF4sdIdentityEntry.GetFromSearchResult(relation.Type)] = relation;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogException(ex);
|
|
}
|
|
}
|
|
|
|
private void HandleAvailableCaseRelationsAdded(object sender, RelationEventArgs e)
|
|
=> AvailableCaseRelationsAdded?.Invoke(this, e);
|
|
|
|
private void HandleCaseDataChanged(object sender, SupportCaseDataEventArgs e)
|
|
{
|
|
CaseDataChanged?.Invoke(this, e);
|
|
|
|
if (e.DataTables.Any(t => !t.Name.StartsWith("Computation_")))
|
|
_supportCaseProcessor.ProcessComputations(e.Relation, _supportCaseProcessor.GetHealthcardFor(e.Relation).Prerequisites.Computations.Values);
|
|
|
|
_ = Task.Run(async () =>
|
|
{
|
|
await UpdateStatusOfSelectedRelations();
|
|
|
|
if (!_hasDirectionConnection)
|
|
await SupportCaseDataProviderArtifact.DirectConnectionHelper.DirectConnectionStartAsync();
|
|
});
|
|
}
|
|
|
|
private void HandleDirectConnectionChanged(object sender, EventArgs e)
|
|
{
|
|
_hasDirectionConnection = SupportCaseDataProviderArtifact.DirectConnectionHelper.IsDirectConnectionActive;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the currently for a support case relevant and shown relation.
|
|
/// </summary>
|
|
/// <remarks>Raises <see cref="FocusedRelationsChanged"/></remarks>
|
|
internal void UpdateFocusedCaseRelation(cF4sdApiSearchResultRelation relation)
|
|
{
|
|
try
|
|
{
|
|
if (_focusedRelation == relation)
|
|
return;
|
|
|
|
_selectedHealthcard = _supportCaseProcessor.GetHealthcardFor(relation);
|
|
HashSet<string> requiredTables = cHealthCard.GetRequiredTables(_selectedHealthcard);
|
|
_ = Task.Run(async () => await _supportCaseProcessor.LoadSupportCaseDataAsync(relation, requiredTables));
|
|
|
|
List<enumFasdInformationClass> requiredInformationClasses = relation.Identities
|
|
.Where(i => i.Class != enumFasdInformationClass.User)
|
|
.Select(i => i.Class)
|
|
.ToList();
|
|
|
|
if (relation.Type == enumF4sdSearchResultClass.User)
|
|
requiredInformationClasses = new List<enumFasdInformationClass>() { enumFasdInformationClass.User };
|
|
|
|
if (requiredInformationClasses.Count == 0)
|
|
requiredInformationClasses.Add(enumFasdInformationClass.User);
|
|
|
|
SupportCaseDataProviderArtifact.HealthCardDataHelper.TrySetSelectedHealthcard(requiredInformationClasses);
|
|
|
|
_focusedRelation = relation;
|
|
|
|
if (!_selectedRelations.Values.Contains(relation) || relation.Type == enumF4sdSearchResultClass.Computer)
|
|
{
|
|
_hasDirectionConnection = false;
|
|
SupportCaseDataProviderArtifact.DirectConnectionHelper.Reset();
|
|
}
|
|
|
|
_selectedRelations[cF4sdIdentityEntry.GetFromSearchResult(relation.Type)] = relation;
|
|
_ = Task.Run(async () => await UpdateStatusOfSelectedRelations());
|
|
|
|
var focusedRelations = new cF4sdApiSearchResultRelation[1] { _focusedRelation };
|
|
FocusedRelationsChanged?.Invoke(this, new RelationEventArgs()
|
|
{
|
|
Relations = focusedRelations.ToLookup(r => cF4sdIdentityEntry.GetFromSearchResult(r.Type), r => r)
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogException(ex);
|
|
}
|
|
}
|
|
|
|
private async Task UpdateStatusOfSelectedRelations()
|
|
{
|
|
const string StatusString = "Status";
|
|
|
|
try
|
|
{
|
|
int? agentUserId = null;
|
|
int? agentDeviceId = null;
|
|
|
|
// todo these values should not be recieved from the NamedParameters, instead from the relation it self
|
|
if (SupportCaseDataProviderArtifact.NamedParameterEntries.TryGetValue("AgentUserId", out var userNamedParameter))
|
|
{
|
|
var value = userNamedParameter.GetValue();
|
|
if (!string.IsNullOrWhiteSpace(value))
|
|
agentUserId = int.Parse(value);
|
|
}
|
|
|
|
|
|
// todo these values should not be recieved from the NamedParameters, instead from the relation it self
|
|
if (SupportCaseDataProviderArtifact.NamedParameterEntries.TryGetValue("AgentDeviceId", out var deviceNamedParameter))
|
|
{
|
|
var value = deviceNamedParameter.GetValue();
|
|
if (!string.IsNullOrWhiteSpace(value))
|
|
agentDeviceId = int.Parse(deviceNamedParameter.GetValue());
|
|
}
|
|
|
|
foreach (var relationEntry in _selectedRelations)
|
|
{
|
|
if (relationEntry.Value is null)
|
|
continue;
|
|
|
|
string statusValue = "Unknown";
|
|
|
|
switch (relationEntry.Key)
|
|
{
|
|
case enumFasdInformationClass.Computer:
|
|
if (!agentDeviceId.HasValue)
|
|
continue;
|
|
|
|
bool isDeviceOnline = await cFasdCockpitCommunicationBase.Instance.GetAgentOnlineStatus(agentDeviceId.Value);
|
|
statusValue = isDeviceOnline ? "Online" : "Offline";
|
|
break;
|
|
case enumFasdInformationClass.User:
|
|
if (!agentDeviceId.HasValue || !agentUserId.HasValue)
|
|
continue;
|
|
|
|
bool isUserOnline = await cFasdCockpitCommunicationBase.Instance.GetAgentOnlineStatus(agentDeviceId.Value, agentUserId.Value);
|
|
statusValue = isUserOnline ? "Online" : "Offline";
|
|
break;
|
|
case enumFasdInformationClass.Ticket:
|
|
if (relationEntry.Value.Infos != null && relationEntry.Value.Infos.TryGetValue("StatusId", out var statusIdValue))
|
|
if (Enum.TryParse<enumTicketStatus>(statusIdValue, out var ticketStatus))
|
|
statusValue = ticketStatus.ToString();
|
|
break;
|
|
|
|
case enumFasdInformationClass.VirtualSession:
|
|
case enumFasdInformationClass.MobileDevice:
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
if (relationEntry.Value.Infos is null)
|
|
relationEntry.Value.Infos = new Dictionary<string, string>();
|
|
|
|
relationEntry.Value.Infos[StatusString] = statusValue;
|
|
}
|
|
|
|
IEnumerable<cHeadingDataModel> newHeadingData = GetHeadingData();
|
|
HeadingDataChanged?.Invoke(this, new HeadingDataEventArgs(newHeadingData));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogException(ex);
|
|
}
|
|
}
|
|
|
|
public async Task RefreshDataForCurrentlyFocusedRelationAsync()
|
|
{
|
|
SupportCaseDataProviderArtifact.HealthCardDataHelper.LoadingHelper.LastDataRequest = DateTime.Now;
|
|
await _supportCaseProcessor.UpdateLatestCaseDataFor(_focusedRelation);
|
|
}
|
|
public cCopyTemplate GetCopyTemplate()
|
|
{
|
|
string defaultCopyTemplate = _selectedHealthcard?.DefaultCopyTemplate
|
|
?? cF4SDCockpitXmlConfig.Instance?.CopyTemplateConfig?.CopyTemplates.DefaultTemplate.Name
|
|
?? string.Empty;
|
|
|
|
Dictionary<string, cCopyTemplate> copyTemplates = cF4SDCockpitXmlConfig.Instance?.CopyTemplateConfig?.CopyTemplates?.CopyTemplates;
|
|
|
|
if (copyTemplates?.TryGetValue(defaultCopyTemplate, out cCopyTemplate _defaultCopyTemplate) ?? false)
|
|
return _defaultCopyTemplate;
|
|
else
|
|
return null;
|
|
}
|
|
public List<List<cWidgetValueModel>> GetWidgetsData()
|
|
{
|
|
try
|
|
{
|
|
List<List<cWidgetValueModel>> widgetsData = new List<List<cWidgetValueModel>>();
|
|
cHealthCard currentHealthCard = _supportCaseProcessor.GetHealthcardFor(_focusedRelation);
|
|
IEnumerable<cHealthCardStateCategory> widgetsDefinition = currentHealthCard.CategoriesStatic.StateCategories;
|
|
|
|
foreach (var widgetDefinition in widgetsDefinition)
|
|
{
|
|
List<cWidgetValueModel> widgetData = new List<cWidgetValueModel>();
|
|
|
|
foreach (var widgetValueDefinition in widgetDefinition.States)
|
|
{
|
|
CockpitValueDisplayData displayData = _supportCaseProcessor.GetCockpitValueDisplayData(widgetValueDefinition, _focusedRelation, true);
|
|
cUiActionBase uiAction = _supportCaseProcessor.GetUiAction(widgetValueDefinition);
|
|
|
|
var widgetValue = new cWidgetValueModel(displayData, GetHighlightColor(displayData?.Levels?.FirstOrDefault() ?? enumHealthCardStateLevel.None), uiAction);
|
|
|
|
if (widgetValue?.UiActionValue != null && ShouldHideUiActionValue(widgetValue.Value))
|
|
widgetValue.UiActionValue.DisplayType = enumActionDisplayType.hidden;
|
|
|
|
widgetData.Add(widgetValue);
|
|
}
|
|
|
|
widgetsData.Add(widgetData);
|
|
}
|
|
|
|
return widgetsData;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogException(ex);
|
|
return null;
|
|
}
|
|
|
|
bool ShouldHideUiActionValue(string widgetValue)
|
|
=> string.IsNullOrWhiteSpace(widgetValue) || widgetValue == "0";
|
|
}
|
|
|
|
public cDetailsPageDataHistoryDataModel GetHistoryData()
|
|
=> ((SupportCaseProcessor)_supportCaseProcessor).GetHistoryData(_focusedRelation);
|
|
|
|
public List<cContainerCollectionData> GetContainerData()
|
|
=> ((SupportCaseProcessor)_supportCaseProcessor).GetContainerData(_focusedRelation);
|
|
|
|
public IEnumerable<cMenuDataBase> GetMenuData()
|
|
{
|
|
var menuBarDatas = cF4SDCockpitXmlConfig.Instance.MenuItems.ToDictionary(config => config.Key, config => (Config: config.Value, MenuValue: MenuDataFactory.Create(config.Value, _supportCaseProcessor.SupportCaseDataProviderArtifact.NamedParameterEntries, _selectedRelations.Values.Select(r => cF4sdIdentityEntry.GetFromSearchResult(r.Type)).ToList())));
|
|
|
|
try
|
|
{
|
|
List<string> menuBarDatasAddedToSection = new List<string>();
|
|
|
|
foreach (var menuBarData in menuBarDatas.Values)
|
|
{
|
|
bool isPinned = cF4SDCockpitXmlConfig.Instance.HealthCardConfig.QuickActionsPinned.Contains(menuBarData.Config.Name);
|
|
menuBarData.MenuValue.IconPositionIndex = isPinned ? cF4SDCockpitXmlConfig.Instance.HealthCardConfig.QuickActionsPinned.IndexOf(menuBarData.Config.Name) : -1;
|
|
|
|
// Same Logic in GetFilteredMenuData()
|
|
bool hasBeenAddedToSection = TryAddMenuDataToSection(ref menuBarDatas, menuBarData.Config.Section, menuBarData.MenuValue);
|
|
|
|
foreach (var section in menuBarData.Config.Sections)
|
|
{
|
|
// todo add test for adding to multiple sections
|
|
hasBeenAddedToSection = TryAddMenuDataToSection(ref menuBarDatas, section, menuBarData.MenuValue) || hasBeenAddedToSection; // order is relevent, because data has to be added to section
|
|
}
|
|
|
|
if (hasBeenAddedToSection)
|
|
menuBarDatasAddedToSection.Add(menuBarData.Config.Name);
|
|
}
|
|
|
|
foreach (var menuBarData in menuBarDatasAddedToSection)
|
|
{
|
|
if (cF4SDCockpitXmlConfig.Instance.HealthCardConfig.QuickActionsPinned.Contains(menuBarData))
|
|
continue;
|
|
|
|
menuBarDatas.Remove(menuBarData);
|
|
}
|
|
|
|
return menuBarDatas.Values.Select(data => data.MenuValue);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogException(ex);
|
|
return new List<cMenuDataBase>();
|
|
}
|
|
}
|
|
|
|
public IEnumerable<cMenuDataBase> GetFilteredMenuData(MenuDataFilter filterQuery)
|
|
{
|
|
Dictionary<string, (cFasdBaseConfigMenuItem Config, cMenuDataBase MenuValue)> menuBarDatas = cF4SDCockpitXmlConfig.Instance.MenuItems.ToDictionary(config => config.Key, config => (Config: config.Value, MenuValue: MenuDataFactory.Create(config.Value, _supportCaseProcessor.SupportCaseDataProviderArtifact.NamedParameterEntries, _selectedRelations.Values.Select(r => cF4sdIdentityEntry.GetFromSearchResult(r.Type)).ToList())));
|
|
|
|
try
|
|
{
|
|
foreach (var menuBarData in menuBarDatas.Values)
|
|
{
|
|
// Same Logic in GetMenuData()
|
|
bool hasBeenAddedToSection = TryAddMenuDataToSection(ref menuBarDatas, menuBarData.Config.Section, menuBarData.MenuValue);
|
|
|
|
foreach (var section in menuBarData.Config.Sections)
|
|
{
|
|
// todo add test for adding to multiple sections
|
|
hasBeenAddedToSection = TryAddMenuDataToSection(ref menuBarDatas, section, menuBarData.MenuValue) || hasBeenAddedToSection; // order is relevent, because data has to be added to section
|
|
}
|
|
}
|
|
|
|
return menuBarDatas.Values.Select(data => data.MenuValue)
|
|
.Where(md => md.MenuText.ToLower().Contains(filterQuery?.SearchString?.ToLower()));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogException(ex);
|
|
return new List<cMenuDataBase>();
|
|
}
|
|
}
|
|
|
|
private bool TryAddMenuDataToSection(ref Dictionary<string, (cFasdBaseConfigMenuItem Config, cMenuDataBase MenuValue)> menuBarDatas, string section, cMenuDataBase menuData)
|
|
{
|
|
if (string.IsNullOrEmpty(section))
|
|
return false;
|
|
|
|
if (!menuBarDatas.TryGetValue(section, out var sectionMenuBar))
|
|
return false;
|
|
|
|
if (!(sectionMenuBar.MenuValue is cMenuDataContainer containerData))
|
|
return false;
|
|
|
|
if (containerData.UiAction is cSubMenuAction subMenuAction)
|
|
{
|
|
if (!subMenuAction.SubMenuData.Any(item => item.MenuText == menuData.MenuText)) // todo SubMenuData maybe better as Dictionary<Guid, cMenuDataBase>
|
|
subMenuAction.SubMenuData.Add(menuData);
|
|
|
|
if (!containerData.SubMenuData.Any(item => item.MenuText == menuData.MenuText))
|
|
containerData.SubMenuData.Add(menuData);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
internal IEnumerable<cHeadingDataModel> GetHeadingData()
|
|
=> SupportCaseHeadingController.GetHeadingData(_selectedRelations);
|
|
|
|
private static enumHighlightColor GetHighlightColor(enumHealthCardStateLevel level)
|
|
{
|
|
switch (level)
|
|
{
|
|
case enumHealthCardStateLevel.None:
|
|
return enumHighlightColor.none;
|
|
case enumHealthCardStateLevel.Ok:
|
|
return enumHighlightColor.green;
|
|
case enumHealthCardStateLevel.Warning:
|
|
return enumHighlightColor.orange;
|
|
case enumHealthCardStateLevel.Error:
|
|
return enumHighlightColor.red;
|
|
case enumHealthCardStateLevel.Info:
|
|
return enumHighlightColor.blue;
|
|
default:
|
|
return enumHighlightColor.none;
|
|
}
|
|
}
|
|
|
|
internal IEnumerable<cF4sdApiSearchResultRelation> GetRelationsOf(enumFasdInformationClass informationClass)
|
|
{
|
|
return _supportCaseProcessor.GetCaseRelations().FirstOrDefault(r => r.Key == informationClass);
|
|
}
|
|
|
|
internal AgentRemoteClientInfo GetAgentClientInfo()
|
|
{
|
|
if (_focusedRelation.Type != enumF4sdSearchResultClass.Computer)
|
|
return null;
|
|
|
|
int deviceId = 0;
|
|
int userId = 0;
|
|
|
|
bool hasAllRequiredNamedParameters =
|
|
_supportCaseProcessor.TryGetNamedParameterValue(_focusedRelation, SupportCaseProcessor.AgentOrganisationCodeNamedParameterName, out int organisationId)
|
|
&& _supportCaseProcessor.TryGetNamedParameterValue(_focusedRelation, SupportCaseProcessor.AgentUserIdNamedParameterName, out userId)
|
|
&& _supportCaseProcessor.TryGetNamedParameterValue(_focusedRelation, SupportCaseProcessor.AgentDeviceIdNamedParameterName, out deviceId);
|
|
|
|
if (!hasAllRequiredNamedParameters)
|
|
return null;
|
|
|
|
return new AgentRemoteClientInfo()
|
|
{
|
|
DeviceCode = deviceId,
|
|
AccountCode = userId,
|
|
OrganisationCode = organisationId
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raised when the currently for a support case relevant and shown relations have been updated.
|
|
/// </summary>
|
|
public event EventHandler<RelationEventArgs> FocusedRelationsChanged;
|
|
|
|
/// <summary>
|
|
/// Raised when newly available relations for a support case were added.
|
|
/// </summary>
|
|
public event EventHandler<RelationEventArgs> AvailableCaseRelationsAdded;
|
|
|
|
/// <summary>
|
|
/// Raised when the data set of a support case has changed.
|
|
/// </summary>
|
|
public event EventHandler<SupportCaseDataEventArgs> CaseDataChanged;
|
|
|
|
public event EventHandler<HeadingDataEventArgs> HeadingDataChanged;
|
|
}
|
|
|
|
public class MenuDataFilter
|
|
{
|
|
public string SearchString { get; set; }
|
|
|
|
public MenuDataFilter(string searchString = null)
|
|
{
|
|
SearchString = searchString;
|
|
}
|
|
|
|
}
|
|
}
|