Testing und interfaces
This commit is contained in:
@@ -0,0 +1,150 @@
|
|||||||
|
using FasdDesktopUi.Basics.Services.Models;
|
||||||
|
|
||||||
|
namespace F4SD.Cockpit.Client.Test.Basics.Sevices.TicketOverview;
|
||||||
|
|
||||||
|
public class TicketOverviewCountProcessorTest
|
||||||
|
{
|
||||||
|
private static readonly string[] OverviewKeys = ["TicketsNew", "IncidentNew"];
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Calculate_PersonalScope_UpdatesOnlyPersonalAndReturnsDeltaChanges()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Dictionary<string, TileCounts> current = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["TicketsNew"] = new TileCounts(1, 7),
|
||||||
|
["IncidentNew"] = new TileCounts(2, 8)
|
||||||
|
};
|
||||||
|
Dictionary<string, int> incoming = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["TicketsNew"] = 5,
|
||||||
|
["IncidentNew"] = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
ScopeCountProcessingResult result = TicketOverviewCountProcessor.Calculate(
|
||||||
|
current,
|
||||||
|
OverviewKeys,
|
||||||
|
TileScope.Personal,
|
||||||
|
incoming,
|
||||||
|
hasInitializedScope: true);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result.IsInitialization);
|
||||||
|
Assert.Equal(5, result.UpdatedCounts["TicketsNew"].Personal);
|
||||||
|
Assert.Equal(7, result.UpdatedCounts["TicketsNew"].Role);
|
||||||
|
Assert.Equal(2, result.UpdatedCounts["IncidentNew"].Personal);
|
||||||
|
Assert.Equal(8, result.UpdatedCounts["IncidentNew"].Role);
|
||||||
|
|
||||||
|
Assert.Single(result.Changes);
|
||||||
|
TileCountChange change = result.Changes[0];
|
||||||
|
Assert.Equal("TicketsNew", change.Key);
|
||||||
|
Assert.Equal(TileScope.Personal, change.Scope);
|
||||||
|
Assert.Equal(1, change.OldCount);
|
||||||
|
Assert.Equal(5, change.NewCount);
|
||||||
|
Assert.Equal(4, change.Delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Calculate_RoleScope_UpdatesOnlyRoleAndReturnsDeltaChanges()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Dictionary<string, TileCounts> current = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["TicketsNew"] = new TileCounts(10, 3),
|
||||||
|
["IncidentNew"] = new TileCounts(4, 6)
|
||||||
|
};
|
||||||
|
Dictionary<string, int> incoming = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["TicketsNew"] = 3,
|
||||||
|
["IncidentNew"] = 9
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
ScopeCountProcessingResult result = TicketOverviewCountProcessor.Calculate(
|
||||||
|
current,
|
||||||
|
OverviewKeys,
|
||||||
|
TileScope.Role,
|
||||||
|
incoming,
|
||||||
|
hasInitializedScope: true);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result.IsInitialization);
|
||||||
|
Assert.Equal(10, result.UpdatedCounts["TicketsNew"].Personal);
|
||||||
|
Assert.Equal(3, result.UpdatedCounts["TicketsNew"].Role);
|
||||||
|
Assert.Equal(4, result.UpdatedCounts["IncidentNew"].Personal);
|
||||||
|
Assert.Equal(9, result.UpdatedCounts["IncidentNew"].Role);
|
||||||
|
|
||||||
|
Assert.Single(result.Changes);
|
||||||
|
TileCountChange change = result.Changes[0];
|
||||||
|
Assert.Equal("IncidentNew", change.Key);
|
||||||
|
Assert.Equal(TileScope.Role, change.Scope);
|
||||||
|
Assert.Equal(6, change.OldCount);
|
||||||
|
Assert.Equal(9, change.NewCount);
|
||||||
|
Assert.Equal(3, change.Delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Calculate_FirstInitialization_DoesNotReturnDeltaChanges()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Dictionary<string, TileCounts> current = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["TicketsNew"] = new TileCounts(0, 0),
|
||||||
|
["IncidentNew"] = new TileCounts(0, 0)
|
||||||
|
};
|
||||||
|
Dictionary<string, int> incoming = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["TicketsNew"] = 4,
|
||||||
|
["IncidentNew"] = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
ScopeCountProcessingResult result = TicketOverviewCountProcessor.Calculate(
|
||||||
|
current,
|
||||||
|
OverviewKeys,
|
||||||
|
TileScope.Personal,
|
||||||
|
incoming,
|
||||||
|
hasInitializedScope: false);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result.IsInitialization);
|
||||||
|
Assert.Empty(result.Changes);
|
||||||
|
Assert.Equal(4, result.UpdatedCounts["TicketsNew"].Personal);
|
||||||
|
Assert.Equal(1, result.UpdatedCounts["IncidentNew"].Personal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Calculate_MissingIncomingValues_DefaultsToZero()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Dictionary<string, TileCounts> current = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["TicketsNew"] = new TileCounts(5, 1),
|
||||||
|
["IncidentNew"] = new TileCounts(2, 4)
|
||||||
|
};
|
||||||
|
Dictionary<string, int> incoming = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["TicketsNew"] = 5
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
ScopeCountProcessingResult result = TicketOverviewCountProcessor.Calculate(
|
||||||
|
current,
|
||||||
|
OverviewKeys,
|
||||||
|
TileScope.Personal,
|
||||||
|
incoming,
|
||||||
|
hasInitializedScope: true);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(5, result.UpdatedCounts["TicketsNew"].Personal);
|
||||||
|
Assert.Equal(1, result.UpdatedCounts["TicketsNew"].Role);
|
||||||
|
Assert.Equal(0, result.UpdatedCounts["IncidentNew"].Personal);
|
||||||
|
Assert.Equal(4, result.UpdatedCounts["IncidentNew"].Role);
|
||||||
|
|
||||||
|
Assert.Single(result.Changes);
|
||||||
|
Assert.Equal("IncidentNew", result.Changes[0].Key);
|
||||||
|
Assert.Equal(2, result.Changes[0].OldCount);
|
||||||
|
Assert.Equal(0, result.Changes[0].NewCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
using FasdDesktopUi.Basics.Services;
|
||||||
|
using FasdDesktopUi.Basics.Services.Models;
|
||||||
|
|
||||||
|
namespace F4SD.Cockpit.Client.Test.Basics.Sevices.TicketOverview;
|
||||||
|
|
||||||
|
public class TicketOverviewUpdateServiceTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateAvailability_FirstFetch_EmitsInitializationEventsForBothScopes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var communication = new FakeCommunication();
|
||||||
|
communication.SetCounts(TileScope.Personal, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["TicketsNew"] = 2
|
||||||
|
});
|
||||||
|
communication.SetCounts(TileScope.Role, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["TicketsNew"] = 5
|
||||||
|
});
|
||||||
|
|
||||||
|
var service = CreateService(communication, out _);
|
||||||
|
var events = new List<TicketOverviewCountsChangedEventArgs>();
|
||||||
|
service.OverviewCountsChanged += (_, args) => events.Add(args);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
service.UpdateAvailability(true);
|
||||||
|
await WaitUntilAsync(() => service.AreAllScopesInitialized);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, events.Count);
|
||||||
|
Assert.All(events, e => Assert.Empty(e.Changes));
|
||||||
|
Assert.Contains(events, e => e.InitializedScope == TileScope.Personal);
|
||||||
|
Assert.Contains(events, e => e.InitializedScope == TileScope.Role);
|
||||||
|
Assert.Equal(2, service.CurrentCounts["TicketsNew"].Personal);
|
||||||
|
Assert.Equal(5, service.CurrentCounts["TicketsNew"].Role);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FetchAsync_ChangedAndUnchangedCounts_EmitsOnlyRealDeltaEvent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var communication = new FakeCommunication();
|
||||||
|
communication.SetCounts(TileScope.Personal, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["TicketsNew"] = 1
|
||||||
|
});
|
||||||
|
communication.SetCounts(TileScope.Role, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["TicketsNew"] = 3
|
||||||
|
});
|
||||||
|
|
||||||
|
var service = CreateService(communication, out _);
|
||||||
|
var events = new List<TicketOverviewCountsChangedEventArgs>();
|
||||||
|
service.OverviewCountsChanged += (_, args) => events.Add(args);
|
||||||
|
|
||||||
|
service.UpdateAvailability(true);
|
||||||
|
await WaitUntilAsync(() => service.AreAllScopesInitialized);
|
||||||
|
events.Clear();
|
||||||
|
|
||||||
|
// Act + Assert (changed)
|
||||||
|
communication.SetCounts(TileScope.Personal, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["TicketsNew"] = 4
|
||||||
|
});
|
||||||
|
await service.FetchAsync(TileScope.Personal);
|
||||||
|
|
||||||
|
Assert.Single(events);
|
||||||
|
Assert.Single(events[0].Changes);
|
||||||
|
Assert.Equal("TicketsNew", events[0].Changes[0].Key);
|
||||||
|
Assert.Equal(1, events[0].Changes[0].OldCount);
|
||||||
|
Assert.Equal(4, events[0].Changes[0].NewCount);
|
||||||
|
Assert.Equal(3, events[0].Changes[0].Delta);
|
||||||
|
|
||||||
|
// Act + Assert (unchanged)
|
||||||
|
events.Clear();
|
||||||
|
await service.FetchAsync(TileScope.Personal);
|
||||||
|
Assert.Empty(events);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Stop_ResetsInitializationAndCounts()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var communication = new FakeCommunication();
|
||||||
|
communication.SetCounts(TileScope.Personal, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["TicketsNew"] = 7
|
||||||
|
});
|
||||||
|
communication.SetCounts(TileScope.Role, new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["IncidentNew"] = 9
|
||||||
|
});
|
||||||
|
|
||||||
|
var service = CreateService(communication, out _);
|
||||||
|
|
||||||
|
service.UpdateAvailability(true);
|
||||||
|
await WaitUntilAsync(() => service.AreAllScopesInitialized);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
service.Stop();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(service.IsScopeInitialized(TileScope.Personal));
|
||||||
|
Assert.False(service.IsScopeInitialized(TileScope.Role));
|
||||||
|
Assert.All(service.CurrentCounts.Values, value =>
|
||||||
|
{
|
||||||
|
Assert.Equal(0, value.Personal);
|
||||||
|
Assert.Equal(0, value.Role);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TicketOverviewUpdateService CreateService(FakeCommunication communication, out FakeDispatcher dispatcher)
|
||||||
|
{
|
||||||
|
dispatcher = new FakeDispatcher();
|
||||||
|
var source = new FakeCommunicationSource(communication);
|
||||||
|
var settings = new FakeSettingsProvider();
|
||||||
|
return new TicketOverviewUpdateService(source, dispatcher, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task WaitUntilAsync(Func<bool> condition, int timeoutMs = 2000)
|
||||||
|
{
|
||||||
|
var startedAt = DateTime.UtcNow;
|
||||||
|
while (!condition())
|
||||||
|
{
|
||||||
|
if ((DateTime.UtcNow - startedAt).TotalMilliseconds > timeoutMs)
|
||||||
|
{
|
||||||
|
throw new TimeoutException("Condition was not reached within timeout.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class FakeCommunicationSource : ITicketOverviewCommunicationSource
|
||||||
|
{
|
||||||
|
private readonly ITicketOverviewCommunication _communication;
|
||||||
|
|
||||||
|
public FakeCommunicationSource(ITicketOverviewCommunication communication)
|
||||||
|
{
|
||||||
|
_communication = communication;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITicketOverviewCommunication Resolve()
|
||||||
|
{
|
||||||
|
return _communication;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class FakeCommunication : ITicketOverviewCommunication
|
||||||
|
{
|
||||||
|
private Dictionary<string, int> _personalCounts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private Dictionary<string, int> _roleCounts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
public bool IsDemo()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Dictionary<string, int>> GetTicketOverviewCounts(string[] overviewKeys, bool useRoleScope)
|
||||||
|
{
|
||||||
|
var source = useRoleScope ? _roleCounts : _personalCounts;
|
||||||
|
return Task.FromResult(new Dictionary<string, int>(source, StringComparer.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterGeneratedTicket(FasdCockpitCommunicationDemo.DemoTicketRecord record)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCounts(TileScope scope, Dictionary<string, int> counts)
|
||||||
|
{
|
||||||
|
var copy = counts == null
|
||||||
|
? new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
: new Dictionary<string, int>(counts, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (scope == TileScope.Role)
|
||||||
|
{
|
||||||
|
_roleCounts = copy;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_personalCounts = copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class FakeDispatcher : ITicketOverviewDispatcher
|
||||||
|
{
|
||||||
|
public Task InvokeAsync(Action action)
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InvokeAsync(Func<Task> action)
|
||||||
|
{
|
||||||
|
return action();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITicketOverviewTimer CreateTimer(TimeSpan interval, Action tick)
|
||||||
|
{
|
||||||
|
return new FakeTimer(interval, tick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class FakeSettingsProvider : ITicketOverviewSettingsProvider
|
||||||
|
{
|
||||||
|
public int GetPollingMinutes(TileScope scope)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class FakeTimer : ITicketOverviewTimer
|
||||||
|
{
|
||||||
|
private readonly Action _tick;
|
||||||
|
|
||||||
|
public FakeTimer(TimeSpan interval, Action tick)
|
||||||
|
{
|
||||||
|
Interval = interval;
|
||||||
|
_tick = tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan Interval { get; set; }
|
||||||
|
|
||||||
|
public bool IsEnabled { get; private set; }
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
IsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
IsEnabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Fire()
|
||||||
|
{
|
||||||
|
_tick?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using C4IT.FASD.Cockpit.Communication;
|
||||||
|
#if isDemo
|
||||||
|
using FasdCockpitCommunicationDemo;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace FasdDesktopUi.Basics.Services.Models
|
||||||
|
{
|
||||||
|
internal interface ITicketOverviewCommunication
|
||||||
|
{
|
||||||
|
bool IsDemo();
|
||||||
|
|
||||||
|
Task<Dictionary<string, int>> GetTicketOverviewCounts(string[] overviewKeys, bool useRoleScope);
|
||||||
|
|
||||||
|
#if isDemo
|
||||||
|
void RegisterGeneratedTicket(DemoTicketRecord record);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
internal interface ITicketOverviewCommunicationSource
|
||||||
|
{
|
||||||
|
ITicketOverviewCommunication Resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class TicketOverviewCommunicationSource : ITicketOverviewCommunicationSource
|
||||||
|
{
|
||||||
|
public ITicketOverviewCommunication Resolve()
|
||||||
|
{
|
||||||
|
var communication = cFasdCockpitCommunicationBase.Instance;
|
||||||
|
return communication == null ? null : new TicketOverviewCommunicationAdapter(communication);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class TicketOverviewCommunicationAdapter : ITicketOverviewCommunication
|
||||||
|
{
|
||||||
|
private readonly cFasdCockpitCommunicationBase _communication;
|
||||||
|
|
||||||
|
internal TicketOverviewCommunicationAdapter(cFasdCockpitCommunicationBase communication)
|
||||||
|
{
|
||||||
|
_communication = communication ?? throw new ArgumentNullException(nameof(communication));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsDemo()
|
||||||
|
{
|
||||||
|
return _communication.IsDemo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Dictionary<string, int>> GetTicketOverviewCounts(string[] overviewKeys, bool useRoleScope)
|
||||||
|
{
|
||||||
|
var rawCounts = await _communication.GetTicketOverviewCounts(overviewKeys, useRoleScope).ConfigureAwait(false);
|
||||||
|
return rawCounts == null
|
||||||
|
? new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
: new Dictionary<string, int>(rawCounts, StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if isDemo
|
||||||
|
public void RegisterGeneratedTicket(DemoTicketRecord record)
|
||||||
|
{
|
||||||
|
var demoCommunication = _communication as cFasdCockpitCommunicationDemo;
|
||||||
|
demoCommunication?.RegisterGeneratedTicket(record);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace FasdDesktopUi.Basics.Services.Models
|
||||||
|
{
|
||||||
|
internal static class TicketOverviewCountProcessor
|
||||||
|
{
|
||||||
|
internal static ScopeCountProcessingResult Calculate(
|
||||||
|
IReadOnlyDictionary<string, TileCounts> currentCounts,
|
||||||
|
IEnumerable<string> overviewKeys,
|
||||||
|
TileScope scope,
|
||||||
|
IDictionary<string, int> incomingCounts,
|
||||||
|
bool hasInitializedScope)
|
||||||
|
{
|
||||||
|
if (overviewKeys == null)
|
||||||
|
throw new ArgumentNullException(nameof(overviewKeys));
|
||||||
|
|
||||||
|
var updatedCounts = new Dictionary<string, TileCounts>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
if (currentCounts != null)
|
||||||
|
{
|
||||||
|
foreach (var kvp in currentCounts)
|
||||||
|
{
|
||||||
|
updatedCounts[kvp.Key] = kvp.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var changes = new List<TileCountChange>();
|
||||||
|
foreach (var key in overviewKeys)
|
||||||
|
{
|
||||||
|
var previous = currentCounts != null && currentCounts.TryGetValue(key, out var counts)
|
||||||
|
? counts
|
||||||
|
: TileCounts.Empty;
|
||||||
|
var incoming = incomingCounts != null && incomingCounts.TryGetValue(key, out var value)
|
||||||
|
? value
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
TileCounts updated;
|
||||||
|
int oldValue;
|
||||||
|
|
||||||
|
if (scope == TileScope.Role)
|
||||||
|
{
|
||||||
|
updated = new TileCounts(previous.Personal, incoming);
|
||||||
|
oldValue = previous.Role;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
updated = new TileCounts(incoming, previous.Role);
|
||||||
|
oldValue = previous.Personal;
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedCounts[key] = updated;
|
||||||
|
|
||||||
|
if (hasInitializedScope && oldValue != incoming)
|
||||||
|
changes.Add(new TileCountChange(key, scope, oldValue, incoming));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ScopeCountProcessingResult(updatedCounts, changes, !hasInitializedScope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class ScopeCountProcessingResult
|
||||||
|
{
|
||||||
|
internal ScopeCountProcessingResult(
|
||||||
|
IReadOnlyDictionary<string, TileCounts> updatedCounts,
|
||||||
|
IReadOnlyList<TileCountChange> changes,
|
||||||
|
bool isInitialization)
|
||||||
|
{
|
||||||
|
UpdatedCounts = updatedCounts ?? throw new ArgumentNullException(nameof(updatedCounts));
|
||||||
|
Changes = changes ?? Array.Empty<TileCountChange>();
|
||||||
|
IsInitialization = isInitialization;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal IReadOnlyDictionary<string, TileCounts> UpdatedCounts { get; }
|
||||||
|
|
||||||
|
internal IReadOnlyList<TileCountChange> Changes { get; }
|
||||||
|
|
||||||
|
internal bool IsInitialization { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
|
namespace FasdDesktopUi.Basics.Services.Models
|
||||||
|
{
|
||||||
|
internal interface ITicketOverviewTimer
|
||||||
|
{
|
||||||
|
TimeSpan Interval { get; set; }
|
||||||
|
|
||||||
|
bool IsEnabled { get; }
|
||||||
|
|
||||||
|
void Start();
|
||||||
|
|
||||||
|
void Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal interface ITicketOverviewDispatcher
|
||||||
|
{
|
||||||
|
Task InvokeAsync(Action action);
|
||||||
|
|
||||||
|
Task InvokeAsync(Func<Task> action);
|
||||||
|
|
||||||
|
ITicketOverviewTimer CreateTimer(TimeSpan interval, Action tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class TicketOverviewDispatcher : ITicketOverviewDispatcher
|
||||||
|
{
|
||||||
|
private readonly Dispatcher _dispatcher;
|
||||||
|
|
||||||
|
internal TicketOverviewDispatcher(Dispatcher dispatcher)
|
||||||
|
{
|
||||||
|
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InvokeAsync(Action action)
|
||||||
|
{
|
||||||
|
return _dispatcher.InvokeAsync(action, DispatcherPriority.Background).Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task InvokeAsync(Func<Task> action)
|
||||||
|
{
|
||||||
|
return _dispatcher.InvokeAsync(action, DispatcherPriority.Background).Task.Unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITicketOverviewTimer CreateTimer(TimeSpan interval, Action tick)
|
||||||
|
{
|
||||||
|
var timer = new DispatcherTimer(interval, DispatcherPriority.Background, (sender, args) => tick?.Invoke(), _dispatcher)
|
||||||
|
{
|
||||||
|
IsEnabled = false
|
||||||
|
};
|
||||||
|
return new TicketOverviewTimer(timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class TicketOverviewTimer : ITicketOverviewTimer
|
||||||
|
{
|
||||||
|
private readonly DispatcherTimer _timer;
|
||||||
|
|
||||||
|
internal TicketOverviewTimer(DispatcherTimer timer)
|
||||||
|
{
|
||||||
|
_timer = timer ?? throw new ArgumentNullException(nameof(timer));
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan Interval
|
||||||
|
{
|
||||||
|
get => _timer.Interval;
|
||||||
|
set => _timer.Interval = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEnabled => _timer.IsEnabled;
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
_timer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
_timer.Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using C4IT.FASD.Base;
|
||||||
|
|
||||||
|
namespace FasdDesktopUi.Basics.Services.Models
|
||||||
|
{
|
||||||
|
internal interface ITicketOverviewSettingsProvider
|
||||||
|
{
|
||||||
|
int GetPollingMinutes(TileScope scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class TicketOverviewSettingsProvider : ITicketOverviewSettingsProvider
|
||||||
|
{
|
||||||
|
public int GetPollingMinutes(TileScope scope)
|
||||||
|
{
|
||||||
|
int minutes = scope == TileScope.Role
|
||||||
|
? cF4sdTicketConfig.DefaultOverviewPollingRole
|
||||||
|
: cF4sdTicketConfig.DefaultOverviewPollingPersonal;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var ticketConfig = cFasdCockpitConfig.Instance?.Global?.TicketConfiguration;
|
||||||
|
if (ticketConfig != null)
|
||||||
|
{
|
||||||
|
minutes = scope == TileScope.Role
|
||||||
|
? ticketConfig.OverviewPollingRole
|
||||||
|
: ticketConfig.OverviewPollingPersonal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"[TicketOverview] Settings fallback to defaults: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minutes < 1)
|
||||||
|
{
|
||||||
|
minutes = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return minutes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
|
||||||
using C4IT.FASD.Base;
|
using C4IT.FASD.Base;
|
||||||
using C4IT.FASD.Cockpit.Communication;
|
using C4IT.FASD.Cockpit.Communication;
|
||||||
using FasdDesktopUi;
|
|
||||||
using FasdDesktopUi.Basics.Models;
|
using FasdDesktopUi.Basics.Models;
|
||||||
using FasdDesktopUi.Basics.Services.Models;
|
using FasdDesktopUi.Basics.Services.Models;
|
||||||
#if isDemo
|
#if isDemo
|
||||||
@@ -20,6 +19,9 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
{
|
{
|
||||||
public sealed class TicketOverviewUpdateService
|
public sealed class TicketOverviewUpdateService
|
||||||
{
|
{
|
||||||
|
#region Fields
|
||||||
|
|
||||||
|
private readonly ITicketOverviewCommunicationSource _communicationSource;
|
||||||
private static readonly string[] OverviewKeys = new[]
|
private static readonly string[] OverviewKeys = new[]
|
||||||
{
|
{
|
||||||
"TicketsNew",
|
"TicketsNew",
|
||||||
@@ -33,15 +35,16 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
"UnassignedTickets",
|
"UnassignedTickets",
|
||||||
"UnassignedTicketsCritical"
|
"UnassignedTicketsCritical"
|
||||||
};
|
};
|
||||||
private readonly Dispatcher _dispatcher;
|
private readonly ITicketOverviewDispatcher _dispatcher;
|
||||||
|
private readonly ITicketOverviewSettingsProvider _settingsProvider;
|
||||||
private readonly Dictionary<string, TileCounts> _currentCounts = new Dictionary<string, TileCounts>(StringComparer.OrdinalIgnoreCase);
|
private readonly Dictionary<string, TileCounts> _currentCounts = new Dictionary<string, TileCounts>(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly Dictionary<(string Key, bool UseRoleScope), List<cF4sdApiSearchResultRelation>> _demoRelations = new Dictionary<(string, bool), List<cF4sdApiSearchResultRelation>>();
|
private readonly Dictionary<(string Key, bool UseRoleScope), List<cF4sdApiSearchResultRelation>> _demoRelations = new Dictionary<(string, bool), List<cF4sdApiSearchResultRelation>>();
|
||||||
private readonly HashSet<TileScope> _pendingScopes = new HashSet<TileScope>();
|
private readonly HashSet<TileScope> _pendingScopes = new HashSet<TileScope>();
|
||||||
private readonly HashSet<TileScope> _initializedScopes = new HashSet<TileScope>();
|
private readonly HashSet<TileScope> _initializedScopes = new HashSet<TileScope>();
|
||||||
private readonly object _fetchLock = new object();
|
private readonly object _fetchLock = new object();
|
||||||
private readonly HashSet<TileScope> _retryScopes = new HashSet<TileScope>();
|
private readonly HashSet<TileScope> _retryScopes = new HashSet<TileScope>();
|
||||||
private DispatcherTimer _personalTimer;
|
private ITicketOverviewTimer _personalTimer;
|
||||||
private DispatcherTimer _roleTimer;
|
private ITicketOverviewTimer _roleTimer;
|
||||||
private Task _fetchWorker;
|
private Task _fetchWorker;
|
||||||
private bool _retryScheduled;
|
private bool _retryScheduled;
|
||||||
private bool _isDemo;
|
private bool _isDemo;
|
||||||
@@ -54,30 +57,53 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
private readonly HashSet<string> _usedSummaries = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
private readonly HashSet<string> _usedSummaries = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
private const int SimulationHotkeyDelayMs = 400;
|
private const int SimulationHotkeyDelayMs = 400;
|
||||||
private int _pendingSimulations;
|
private int _pendingSimulations;
|
||||||
private DispatcherTimer _simulationFlushTimer;
|
private ITicketOverviewTimer _simulationFlushTimer;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Construction and Singleton
|
||||||
|
|
||||||
private TicketOverviewUpdateService()
|
private TicketOverviewUpdateService()
|
||||||
|
: this(
|
||||||
|
new TicketOverviewCommunicationSource(),
|
||||||
|
new TicketOverviewDispatcher(Application.Current?.Dispatcher ?? Dispatcher.CurrentDispatcher),
|
||||||
|
new TicketOverviewSettingsProvider())
|
||||||
{
|
{
|
||||||
_dispatcher = Application.Current?.Dispatcher ?? Dispatcher.CurrentDispatcher;
|
}
|
||||||
|
|
||||||
|
internal TicketOverviewUpdateService(
|
||||||
|
ITicketOverviewCommunicationSource communicationSource,
|
||||||
|
ITicketOverviewDispatcher dispatcher)
|
||||||
|
: this(communicationSource, dispatcher, new TicketOverviewSettingsProvider())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal TicketOverviewUpdateService(
|
||||||
|
ITicketOverviewCommunicationSource communicationSource,
|
||||||
|
ITicketOverviewDispatcher dispatcher,
|
||||||
|
ITicketOverviewSettingsProvider settingsProvider)
|
||||||
|
{
|
||||||
|
_communicationSource = communicationSource ?? throw new ArgumentNullException(nameof(communicationSource));
|
||||||
|
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
||||||
|
_settingsProvider = settingsProvider ?? throw new ArgumentNullException(nameof(settingsProvider));
|
||||||
foreach (var key in OverviewKeys)
|
foreach (var key in OverviewKeys)
|
||||||
{
|
{
|
||||||
_currentCounts[key] = TileCounts.Empty;
|
_currentCounts[key] = TileCounts.Empty;
|
||||||
}
|
}
|
||||||
#if isDemo
|
#if isDemo
|
||||||
_simulationFlushTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(SimulationHotkeyDelayMs), DispatcherPriority.Background, SimulationFlushTimer_Tick, _dispatcher)
|
_simulationFlushTimer = _dispatcher.CreateTimer(TimeSpan.FromMilliseconds(SimulationHotkeyDelayMs), SimulationFlushTimer_Tick);
|
||||||
{
|
|
||||||
IsEnabled = false
|
|
||||||
};
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static TicketOverviewUpdateService()
|
private static readonly Lazy<TicketOverviewUpdateService> _instance =
|
||||||
{
|
new Lazy<TicketOverviewUpdateService>(() => new TicketOverviewUpdateService());
|
||||||
Instance = new TicketOverviewUpdateService();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TicketOverviewUpdateService Instance { get; }
|
public static TicketOverviewUpdateService Instance => _instance.Value;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public API
|
||||||
|
|
||||||
public event EventHandler<TicketOverviewCountsChangedEventArgs> OverviewCountsChanged;
|
public event EventHandler<TicketOverviewCountsChangedEventArgs> OverviewCountsChanged;
|
||||||
|
|
||||||
@@ -113,6 +139,10 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
UpdateAvailability(false);
|
UpdateAvailability(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Lifecycle
|
||||||
|
|
||||||
public void UpdateAvailability(bool isEnabled)
|
public void UpdateAvailability(bool isEnabled)
|
||||||
{
|
{
|
||||||
if (isEnabled)
|
if (isEnabled)
|
||||||
@@ -146,7 +176,7 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
_isDemo = true;
|
_isDemo = true;
|
||||||
LoadPersistedDemoTickets();
|
LoadPersistedDemoTickets();
|
||||||
#else
|
#else
|
||||||
_isDemo = cFasdCockpitCommunicationBase.Instance?.IsDemo() == true;
|
_isDemo = _communicationSource.Resolve()?.IsDemo() == true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
InitializeTimers();
|
InitializeTimers();
|
||||||
@@ -171,7 +201,7 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
_retryScopes.Clear();
|
_retryScopes.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
_dispatcher.InvokeAsync(() =>
|
_ = _dispatcher.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
_personalTimer?.Stop();
|
_personalTimer?.Stop();
|
||||||
_roleTimer?.Stop();
|
_roleTimer?.Stop();
|
||||||
@@ -185,6 +215,10 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Polling and Fetch Pipeline
|
||||||
|
|
||||||
private void InitializeTimers()
|
private void InitializeTimers()
|
||||||
{
|
{
|
||||||
_personalTimer = CreateScopeTimer(TileScope.Personal);
|
_personalTimer = CreateScopeTimer(TileScope.Personal);
|
||||||
@@ -194,33 +228,15 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
_roleTimer?.Start();
|
_roleTimer?.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private DispatcherTimer CreateScopeTimer(TileScope scope)
|
private ITicketOverviewTimer CreateScopeTimer(TileScope scope)
|
||||||
{
|
{
|
||||||
var interval = GetPollingInterval(scope);
|
var interval = GetPollingInterval(scope);
|
||||||
var timer = new DispatcherTimer(interval, DispatcherPriority.Background, async (s, e) => await FetchAsync(scope).ConfigureAwait(false), _dispatcher)
|
return _dispatcher.CreateTimer(interval, () => _ = FetchAsync(scope));
|
||||||
{
|
|
||||||
IsEnabled = false
|
|
||||||
};
|
|
||||||
return timer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TimeSpan GetPollingInterval(TileScope scope)
|
private TimeSpan GetPollingInterval(TileScope scope)
|
||||||
{
|
{
|
||||||
var ticketConfig = cFasdCockpitConfig.Instance?.Global?.TicketConfiguration;
|
int minutes = _settingsProvider.GetPollingMinutes(scope);
|
||||||
int minutes = scope == TileScope.Role
|
|
||||||
? cF4sdTicketConfig.DefaultOverviewPollingRole
|
|
||||||
: cF4sdTicketConfig.DefaultOverviewPollingPersonal;
|
|
||||||
|
|
||||||
if (ticketConfig != null)
|
|
||||||
{
|
|
||||||
minutes = scope == TileScope.Role
|
|
||||||
? ticketConfig.OverviewPollingRole
|
|
||||||
: ticketConfig.OverviewPollingPersonal;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (minutes < 1)
|
|
||||||
minutes = 1;
|
|
||||||
|
|
||||||
return TimeSpan.FromMinutes(minutes);
|
return TimeSpan.FromMinutes(minutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,7 +307,7 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
if (!_isEnabled)
|
if (!_isEnabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var communication = cFasdCockpitCommunicationBase.Instance;
|
var communication = _communicationSource.Resolve();
|
||||||
if (communication == null)
|
if (communication == null)
|
||||||
{
|
{
|
||||||
ScheduleFetchRetry(scope);
|
ScheduleFetchRetry(scope);
|
||||||
@@ -328,9 +344,13 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Count Processing
|
||||||
|
|
||||||
private void RefreshTimerIntervals()
|
private void RefreshTimerIntervals()
|
||||||
{
|
{
|
||||||
_dispatcher.InvokeAsync(() =>
|
_ = _dispatcher.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
if (_personalTimer != null)
|
if (_personalTimer != null)
|
||||||
_personalTimer.Interval = GetPollingInterval(TileScope.Personal);
|
_personalTimer.Interval = GetPollingInterval(TileScope.Personal);
|
||||||
@@ -345,39 +365,26 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
if (newCounts == null)
|
if (newCounts == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var hasInitializedScope = _initializedScopes.Contains(scope);
|
bool hasInitializedScope;
|
||||||
var changes = new List<TileCountChange>();
|
lock (_fetchLock)
|
||||||
|
|
||||||
foreach (var key in OverviewKeys)
|
|
||||||
{
|
{
|
||||||
var previous = _currentCounts.TryGetValue(key, out var counts) ? counts : TileCounts.Empty;
|
hasInitializedScope = _initializedScopes.Contains(scope);
|
||||||
var incoming = newCounts.TryGetValue(key, out var value) ? value : 0;
|
|
||||||
|
|
||||||
TileCounts updated;
|
|
||||||
int oldValue;
|
|
||||||
|
|
||||||
if (scope == TileScope.Role)
|
|
||||||
{
|
|
||||||
updated = new TileCounts(previous.Personal, incoming);
|
|
||||||
oldValue = previous.Role;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
updated = new TileCounts(incoming, previous.Role);
|
|
||||||
oldValue = previous.Personal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentCounts[key] = updated;
|
var result = TicketOverviewCountProcessor.Calculate(_currentCounts, OverviewKeys, scope, newCounts, hasInitializedScope);
|
||||||
|
_currentCounts.Clear();
|
||||||
if (hasInitializedScope && oldValue != incoming)
|
foreach (var kvp in result.UpdatedCounts)
|
||||||
{
|
{
|
||||||
changes.Add(new TileCountChange(key, scope, oldValue, incoming));
|
_currentCounts[kvp.Key] = kvp.Value;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasInitializedScope)
|
if (result.IsInitialization)
|
||||||
|
{
|
||||||
|
lock (_fetchLock)
|
||||||
{
|
{
|
||||||
_initializedScopes.Add(scope);
|
_initializedScopes.Add(scope);
|
||||||
|
}
|
||||||
|
|
||||||
var initArgs = new TicketOverviewCountsChangedEventArgs(
|
var initArgs = new TicketOverviewCountsChangedEventArgs(
|
||||||
Array.Empty<TileCountChange>(),
|
Array.Empty<TileCountChange>(),
|
||||||
new Dictionary<string, TileCounts>(_currentCounts, StringComparer.OrdinalIgnoreCase),
|
new Dictionary<string, TileCounts>(_currentCounts, StringComparer.OrdinalIgnoreCase),
|
||||||
@@ -386,16 +393,20 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changes.Count == 0)
|
if (result.Changes.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var args = new TicketOverviewCountsChangedEventArgs(changes, new Dictionary<string, TileCounts>(_currentCounts, StringComparer.OrdinalIgnoreCase));
|
var args = new TicketOverviewCountsChangedEventArgs(result.Changes, new Dictionary<string, TileCounts>(_currentCounts, StringComparer.OrdinalIgnoreCase));
|
||||||
OverviewCountsChanged?.Invoke(this, args);
|
OverviewCountsChanged?.Invoke(this, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Demo - Public
|
||||||
|
|
||||||
public void SimulateDemoTicket()
|
public void SimulateDemoTicket()
|
||||||
{
|
{
|
||||||
_isDemo = cFasdCockpitCommunicationBase.Instance?.IsDemo() == true;
|
_isDemo = _communicationSource.Resolve()?.IsDemo() == true;
|
||||||
if (!_isDemo)
|
if (!_isDemo)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -407,7 +418,7 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_pendingSimulations++;
|
Interlocked.Increment(ref _pendingSimulations);
|
||||||
|
|
||||||
if (_simulationFlushTimer != null)
|
if (_simulationFlushTimer != null)
|
||||||
{
|
{
|
||||||
@@ -417,8 +428,8 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ProcessDemoSimulations(_pendingSimulations);
|
var count = Interlocked.Exchange(ref _pendingSimulations, 0);
|
||||||
_pendingSimulations = 0;
|
ProcessDemoSimulations(count);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -437,7 +448,11 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
return Enumerable.Empty<cF4sdApiSearchResultRelation>();
|
return Enumerable.Empty<cF4sdApiSearchResultRelation>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#if isDemo
|
#if isDemo
|
||||||
|
#region Demo - Internals
|
||||||
|
|
||||||
private void LoadPersistedDemoTickets()
|
private void LoadPersistedDemoTickets()
|
||||||
{
|
{
|
||||||
var data = TicketOverviewDataStore.LoadData();
|
var data = TicketOverviewDataStore.LoadData();
|
||||||
@@ -471,11 +486,10 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
_demoTemplates.AddRange(templates);
|
_demoTemplates.AddRange(templates);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SimulationFlushTimer_Tick(object sender, EventArgs e)
|
private void SimulationFlushTimer_Tick()
|
||||||
{
|
{
|
||||||
_simulationFlushTimer.Stop();
|
_simulationFlushTimer.Stop();
|
||||||
var count = _pendingSimulations;
|
var count = Interlocked.Exchange(ref _pendingSimulations, 0);
|
||||||
_pendingSimulations = 0;
|
|
||||||
ProcessDemoSimulations(count);
|
ProcessDemoSimulations(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,7 +525,9 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
if (appliedChanges.Count == 0)
|
if (appliedChanges.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var args = new TicketOverviewCountsChangedEventArgs(appliedChanges, new Dictionary<string, TileCounts>(_currentCounts));
|
var args = new TicketOverviewCountsChangedEventArgs(
|
||||||
|
appliedChanges,
|
||||||
|
new Dictionary<string, TileCounts>(_currentCounts, StringComparer.OrdinalIgnoreCase));
|
||||||
OverviewCountsChanged?.Invoke(this, args);
|
OverviewCountsChanged?.Invoke(this, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,10 +627,7 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
if (!string.IsNullOrWhiteSpace(record.Summary))
|
if (!string.IsNullOrWhiteSpace(record.Summary))
|
||||||
_usedSummaries.Add(record.Summary);
|
_usedSummaries.Add(record.Summary);
|
||||||
|
|
||||||
if (cFasdCockpitCommunicationBase.Instance is cFasdCockpitCommunicationDemo demoCommunication)
|
_communicationSource.Resolve()?.RegisterGeneratedTicket(record);
|
||||||
{
|
|
||||||
demoCommunication.RegisterGeneratedTicket(record);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_currentCounts.TryGetValue(record.TileKey, out var previousCounts))
|
if (!_currentCounts.TryGetValue(record.TileKey, out var previousCounts))
|
||||||
previousCounts = TileCounts.Empty;
|
previousCounts = TileCounts.Empty;
|
||||||
@@ -761,22 +774,19 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#region Utilities
|
||||||
|
|
||||||
public Dictionary<string, int> GetCountsForScope(bool useRoleScope)
|
public Dictionary<string, int> GetCountsForScope(bool useRoleScope)
|
||||||
{
|
{
|
||||||
return _currentCounts.ToDictionary(kvp => kvp.Key, kvp => useRoleScope ? kvp.Value.Role : kvp.Value.Personal, StringComparer.OrdinalIgnoreCase);
|
return _currentCounts.ToDictionary(kvp => kvp.Key, kvp => useRoleScope ? kvp.Value.Role : kvp.Value.Personal, StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetDemoRelationCount(string key, bool useRoleScope)
|
#endregion
|
||||||
{
|
|
||||||
lock (_demoRelations)
|
#region Retry Handling
|
||||||
{
|
|
||||||
if (_demoRelations.TryGetValue((key, useRoleScope), out var list))
|
|
||||||
return list.Count;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ScheduleFetchRetry(TileScope scope)
|
private void ScheduleFetchRetry(TileScope scope)
|
||||||
{
|
{
|
||||||
@@ -814,7 +824,9 @@ namespace FasdDesktopUi.Basics.Services
|
|||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"[TicketOverview] Retry scheduling failed: {ex}");
|
System.Diagnostics.Debug.WriteLine($"[TicketOverview] Retry scheduling failed: {ex}");
|
||||||
}
|
}
|
||||||
}, DispatcherPriority.Background);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -225,6 +225,10 @@
|
|||||||
<Compile Include="Basics\Helper\UiElementHelper.cs" />
|
<Compile Include="Basics\Helper\UiElementHelper.cs" />
|
||||||
<Compile Include="Basics\HotKeyManager.cs" />
|
<Compile Include="Basics\HotKeyManager.cs" />
|
||||||
<Compile Include="Basics\Helper\TrayTicketNotificationManager.cs" />
|
<Compile Include="Basics\Helper\TrayTicketNotificationManager.cs" />
|
||||||
|
<Compile Include="Basics\Services\Models\TicketOverviewCommunicationSource.cs" />
|
||||||
|
<Compile Include="Basics\Services\Models\TicketOverviewDispatcher.cs" />
|
||||||
|
<Compile Include="Basics\Services\Models\TicketOverviewCountProcessor.cs" />
|
||||||
|
<Compile Include="Basics\Services\Models\TicketOverviewSettingsProvider.cs" />
|
||||||
<Compile Include="Basics\Services\Models\TicketOverviewCountsChangedEventArgs.cs" />
|
<Compile Include="Basics\Services\Models\TicketOverviewCountsChangedEventArgs.cs" />
|
||||||
<Compile Include="Basics\Services\TicketOverviewUpdateService.cs" />
|
<Compile Include="Basics\Services\TicketOverviewUpdateService.cs" />
|
||||||
<Compile Include="Basics\Models\ConnectionStatusHelper.cs" />
|
<Compile Include="Basics\Models\ConnectionStatusHelper.cs" />
|
||||||
|
|||||||
Reference in New Issue
Block a user