aktueller stand
This commit is contained in:
@@ -38,6 +38,7 @@ namespace C4IT.DataHistoryProvider
|
||||
private const string constUrlGetPickupValues = "m42Services/api/c4itf4sdwebapi/getpickup/{0}?group={1}";
|
||||
private const string constUrlGetRoleMeberships = "m42services/api/c4itf4sdwebapi/getrolememberships/?sid={0}";
|
||||
private const string constUrlGetTicketOverviewCounts = "m42Services/api/c4itf4sdwebapi/getticketoverviewcounts?sid={0}&scope={1}&keys={2}";
|
||||
private const string constUrlGetTicketOverviewCountsByRoles = "m42Services/api/c4itf4sdwebapi/getticketoverviewcountsbyroles";
|
||||
private const string constUrlGetTicketOverviewRelations = "m42Services/api/c4itf4sdwebapi/getticketoverviewrelations?sid={0}&scope={1}&key={2}&count={3}";
|
||||
private const string constUrlGetDataQueryRelationItems = "m42Services/api/dataquery/relationitems";
|
||||
private const string constUrlGetDataQueryRelationItemsCount = "m42Services/api/dataquery/relationitems/count";
|
||||
@@ -66,11 +67,16 @@ namespace C4IT.DataHistoryProvider
|
||||
|
||||
private readonly Timer onlineTimer = null;
|
||||
|
||||
private bool IsOnline = false;
|
||||
|
||||
private Dictionary<WebClientCacheIndex, WebClientCacheEntry> UserWebClientCache = new Dictionary<WebClientCacheIndex, WebClientCacheEntry>();
|
||||
|
||||
private readonly cDataHistoryCollector _collector;
|
||||
private bool IsOnline = false;
|
||||
|
||||
private Dictionary<WebClientCacheIndex, WebClientCacheEntry> UserWebClientCache = new Dictionary<WebClientCacheIndex, WebClientCacheEntry>();
|
||||
|
||||
private readonly object _ticketOverviewCacheLock = new object();
|
||||
private readonly Dictionary<string, TicketOverviewCountCacheEntry> _ticketOverviewPersonalCache = new Dictionary<string, TicketOverviewCountCacheEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<Guid, TicketOverviewCountCacheEntry> _ticketOverviewRoleCache = new Dictionary<Guid, TicketOverviewCountCacheEntry>();
|
||||
private readonly Dictionary<string, TicketOverviewRoleListCacheEntry> _ticketOverviewRoleListCache = new Dictionary<string, TicketOverviewRoleListCacheEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private readonly cDataHistoryCollector _collector;
|
||||
|
||||
private static class _OnlineCheckCriticalSection
|
||||
{
|
||||
@@ -88,12 +94,38 @@ namespace C4IT.DataHistoryProvider
|
||||
public string Language;
|
||||
}
|
||||
|
||||
private class WebClientCacheEntry
|
||||
{
|
||||
public cM42WebClient WebClient;
|
||||
public DateTime LastUsed;
|
||||
public DateTime ValidUntil;
|
||||
}
|
||||
private class WebClientCacheEntry
|
||||
{
|
||||
public cM42WebClient WebClient;
|
||||
public DateTime LastUsed;
|
||||
public DateTime ValidUntil;
|
||||
}
|
||||
|
||||
private sealed class TicketOverviewCountCacheEntry
|
||||
{
|
||||
public Dictionary<string, int> Counts { get; set; } = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
public DateTime ExpiresAtUtc { get; set; }
|
||||
}
|
||||
|
||||
private sealed class TicketOverviewRoleListCacheEntry
|
||||
{
|
||||
public List<Guid> RoleIds { get; set; } = new List<Guid>();
|
||||
public DateTime ExpiresAtUtc { get; set; }
|
||||
}
|
||||
|
||||
private sealed class TicketOverviewCountsByRolesRequest
|
||||
{
|
||||
public string Sid { get; set; }
|
||||
public List<Guid> RoleGuids { get; set; } = new List<Guid>();
|
||||
public List<string> Keys { get; set; } = new List<string>();
|
||||
}
|
||||
|
||||
private sealed class TicketOverviewCountsByRolesResponse
|
||||
{
|
||||
[JsonProperty("countsByRole")]
|
||||
public Dictionary<string, Dictionary<string, int>> CountsByRole { get; set; }
|
||||
= new Dictionary<string, Dictionary<string, int>>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public class cM42ApiTokenModel
|
||||
{
|
||||
@@ -601,34 +633,295 @@ namespace C4IT.DataHistoryProvider
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, int>> GetTicketOverviewCountsAsync(IEnumerable<string> keys, bool useRoleScope, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
|
||||
private TimeSpan GetTicketOverviewCacheDuration(bool useRoleScope)
|
||||
{
|
||||
var ticketConfig = Collector?.GetGlobalConfig()?.TicketConfiguration;
|
||||
var minutes = useRoleScope
|
||||
? ticketConfig?.OverviewPollingRole ?? cF4sdTicketConfig.DefaultOverviewPollingRole
|
||||
: ticketConfig?.OverviewPollingPersonal ?? cF4sdTicketConfig.DefaultOverviewPollingPersonal;
|
||||
|
||||
if (minutes < 1)
|
||||
minutes = 1;
|
||||
|
||||
return TimeSpan.FromMinutes(minutes);
|
||||
}
|
||||
|
||||
private bool TryGetTicketOverviewPersonalCache(string sid, DateTime now, out Dictionary<string, int> counts)
|
||||
{
|
||||
counts = null;
|
||||
if (string.IsNullOrWhiteSpace(sid))
|
||||
return false;
|
||||
|
||||
lock (_ticketOverviewCacheLock)
|
||||
{
|
||||
if (_ticketOverviewPersonalCache.TryGetValue(sid, out var entry))
|
||||
{
|
||||
if (entry.ExpiresAtUtc > now)
|
||||
{
|
||||
counts = new Dictionary<string, int>(entry.Counts, StringComparer.OrdinalIgnoreCase);
|
||||
return true;
|
||||
}
|
||||
|
||||
_ticketOverviewPersonalCache.Remove(sid);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetTicketOverviewRoleCache(Guid roleId, DateTime now, out Dictionary<string, int> counts)
|
||||
{
|
||||
counts = null;
|
||||
if (roleId == Guid.Empty)
|
||||
return false;
|
||||
|
||||
lock (_ticketOverviewCacheLock)
|
||||
{
|
||||
if (_ticketOverviewRoleCache.TryGetValue(roleId, out var entry))
|
||||
{
|
||||
if (entry.ExpiresAtUtc > now)
|
||||
{
|
||||
counts = new Dictionary<string, int>(entry.Counts, StringComparer.OrdinalIgnoreCase);
|
||||
return true;
|
||||
}
|
||||
|
||||
_ticketOverviewRoleCache.Remove(roleId);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetTicketOverviewPersonalCache(string sid, Dictionary<string, int> counts, DateTime expiresAtUtc)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sid))
|
||||
return;
|
||||
|
||||
lock (_ticketOverviewCacheLock)
|
||||
{
|
||||
_ticketOverviewPersonalCache[sid] = new TicketOverviewCountCacheEntry
|
||||
{
|
||||
Counts = new Dictionary<string, int>(counts ?? new Dictionary<string, int>(), StringComparer.OrdinalIgnoreCase),
|
||||
ExpiresAtUtc = expiresAtUtc
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void SetTicketOverviewRoleCache(Guid roleId, Dictionary<string, int> counts, DateTime expiresAtUtc)
|
||||
{
|
||||
if (roleId == Guid.Empty)
|
||||
return;
|
||||
|
||||
lock (_ticketOverviewCacheLock)
|
||||
{
|
||||
_ticketOverviewRoleCache[roleId] = new TicketOverviewCountCacheEntry
|
||||
{
|
||||
Counts = new Dictionary<string, int>(counts ?? new Dictionary<string, int>(), StringComparer.OrdinalIgnoreCase),
|
||||
ExpiresAtUtc = expiresAtUtc
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetTicketOverviewRoleIdsFromCache(string sid, DateTime now, out List<Guid> roleIds)
|
||||
{
|
||||
roleIds = null;
|
||||
if (string.IsNullOrWhiteSpace(sid))
|
||||
return false;
|
||||
|
||||
lock (_ticketOverviewCacheLock)
|
||||
{
|
||||
if (_ticketOverviewRoleListCache.TryGetValue(sid, out var entry))
|
||||
{
|
||||
if (entry.ExpiresAtUtc > now)
|
||||
{
|
||||
roleIds = new List<Guid>(entry.RoleIds);
|
||||
return true;
|
||||
}
|
||||
|
||||
_ticketOverviewRoleListCache.Remove(sid);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetTicketOverviewRoleIdsCache(string sid, IEnumerable<Guid> roleIds, DateTime expiresAtUtc)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sid))
|
||||
return;
|
||||
|
||||
var normalized = (roleIds ?? Enumerable.Empty<Guid>())
|
||||
.Where(id => id != Guid.Empty)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
lock (_ticketOverviewCacheLock)
|
||||
{
|
||||
_ticketOverviewRoleListCache[sid] = new TicketOverviewRoleListCacheEntry
|
||||
{
|
||||
RoleIds = normalized,
|
||||
ExpiresAtUtc = expiresAtUtc
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<Guid>> GetTicketOverviewRoleIdsAsync(string sid, DateTime now, TimeSpan ttl, CancellationToken token)
|
||||
{
|
||||
if (TryGetTicketOverviewRoleIdsFromCache(sid, now, out var cached))
|
||||
return cached;
|
||||
|
||||
var userInfo = await GetM42UserInfoAsync(sid, token);
|
||||
var roles = userInfo?.Roles ?? new List<M42UserUserInfo.M42Role>();
|
||||
var roleIds = roles
|
||||
.Where(r => r != null && r.Id != Guid.Empty)
|
||||
.Select(r => r.Id)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
SetTicketOverviewRoleIdsCache(sid, roleIds, now.Add(ttl));
|
||||
return roleIds;
|
||||
}
|
||||
|
||||
private async Task<Dictionary<Guid, Dictionary<string, int>>> FetchTicketOverviewCountsByRolesAsync(
|
||||
IEnumerable<Guid> roleIds,
|
||||
IReadOnlyCollection<string> requestedKeys,
|
||||
cF4sdWebRequestInfo requestInfo,
|
||||
int logDeep,
|
||||
CancellationToken token)
|
||||
{
|
||||
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
|
||||
if (cPerformanceLogger.IsActive && requestInfo != null) { if (CM == null) CM = MethodBase.GetCurrentMethod(); cPerformanceLogger.LogPerformanceStart(LogDeep, CM, requestInfo.id, requestInfo.created); }
|
||||
var _startTime = DateTime.UtcNow;
|
||||
if (cPerformanceLogger.IsActive && requestInfo != null) { if (CM == null) CM = MethodBase.GetCurrentMethod(); cPerformanceLogger.LogPerformanceStart(logDeep, CM, requestInfo.id, requestInfo.created); }
|
||||
var startTime = DateTime.UtcNow;
|
||||
|
||||
try
|
||||
{
|
||||
if (!await CheckOnline())
|
||||
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var sid = requestInfo?.userInfo?.AdSid;
|
||||
if (string.IsNullOrWhiteSpace(sid))
|
||||
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var scope = useRoleScope ? "role" : "personal";
|
||||
var normalizedKeys = (keys ?? Enumerable.Empty<string>())
|
||||
.Where(k => !string.IsNullOrWhiteSpace(k))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
var roleList = (roleIds ?? Enumerable.Empty<Guid>())
|
||||
.Where(id => id != Guid.Empty)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
var keyParam = normalizedKeys.Count > 0
|
||||
? HttpUtility.UrlEncode(string.Join(",", normalizedKeys))
|
||||
: string.Empty;
|
||||
|
||||
var url = string.Format(constUrlGetTicketOverviewCounts, HttpUtility.UrlEncode(sid), scope, keyParam);
|
||||
var wc = await GetWebClient(requestInfo, Token);
|
||||
var res = await wc.HttpEnh.GetAsync(url, Token);
|
||||
if (Token.IsCancellationRequested)
|
||||
if (roleList.Count == 0)
|
||||
return new Dictionary<Guid, Dictionary<string, int>>();
|
||||
|
||||
var request = new TicketOverviewCountsByRolesRequest
|
||||
{
|
||||
Sid = requestInfo?.userInfo?.AdSid,
|
||||
RoleGuids = roleList,
|
||||
Keys = requestedKeys?.Where(k => !string.IsNullOrWhiteSpace(k)).ToList() ?? new List<string>()
|
||||
};
|
||||
|
||||
var jsonBody = JsonConvert.SerializeObject(request, Formatting.None,
|
||||
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
|
||||
var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
|
||||
|
||||
var wc = await GetWebClient(requestInfo, token);
|
||||
var res = await wc.HttpEnh.PostAsync(constUrlGetTicketOverviewCountsByRoles, content, token);
|
||||
if (token.IsCancellationRequested)
|
||||
return new Dictionary<Guid, Dictionary<string, int>>();
|
||||
|
||||
if (res?.IsSuccessStatusCode == true)
|
||||
{
|
||||
var json = await res.Content.ReadAsStringAsync();
|
||||
if (!string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
var response = JsonConvert.DeserializeObject<TicketOverviewCountsByRolesResponse>(json);
|
||||
if (response?.CountsByRole != null)
|
||||
{
|
||||
var result = new Dictionary<Guid, Dictionary<string, int>>();
|
||||
foreach (var entry in response.CountsByRole)
|
||||
{
|
||||
if (!Guid.TryParse(entry.Key, out var roleId) || roleId == Guid.Empty)
|
||||
continue;
|
||||
|
||||
var counts = entry.Value ?? new Dictionary<string, int>();
|
||||
result[roleId] = new Dictionary<string, int>(counts, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StartOnlineValidation();
|
||||
}
|
||||
catch (Exception E)
|
||||
{
|
||||
LogException(E);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(logDeep, CM, requestInfo.id, requestInfo.created, startTime); }
|
||||
if (CM != null) LogMethodEnd(CM);
|
||||
}
|
||||
|
||||
return new Dictionary<Guid, Dictionary<string, int>>();
|
||||
}
|
||||
|
||||
private Dictionary<string, int> SumTicketOverviewCounts(IEnumerable<Dictionary<string, int>> sources)
|
||||
{
|
||||
var result = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
if (sources == null)
|
||||
return result;
|
||||
|
||||
foreach (var counts in sources)
|
||||
{
|
||||
if (counts == null)
|
||||
continue;
|
||||
|
||||
foreach (var kvp in counts)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(kvp.Key))
|
||||
continue;
|
||||
|
||||
result[kvp.Key] = result.TryGetValue(kvp.Key, out var existing)
|
||||
? existing + kvp.Value
|
||||
: kvp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Dictionary<string, int> FilterTicketOverviewCounts(Dictionary<string, int> counts, IReadOnlyCollection<string> requestedKeys)
|
||||
{
|
||||
if (counts == null)
|
||||
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (requestedKeys == null || requestedKeys.Count == 0)
|
||||
return new Dictionary<string, int>(counts, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var filtered = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var key in requestedKeys)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
continue;
|
||||
|
||||
filtered[key] = counts.TryGetValue(key, out var value) ? value : 0;
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, int>> FetchTicketOverviewCountsAsync(
|
||||
string sid,
|
||||
string scope,
|
||||
IReadOnlyCollection<string> requestedKeys,
|
||||
cF4sdWebRequestInfo requestInfo,
|
||||
CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var keyParam = requestedKeys != null && requestedKeys.Count > 0
|
||||
? HttpUtility.UrlEncode(string.Join(",", requestedKeys))
|
||||
: null;
|
||||
|
||||
var url = string.Format(constUrlGetTicketOverviewCounts, HttpUtility.UrlEncode(sid), scope, keyParam ?? string.Empty);
|
||||
if (string.IsNullOrWhiteSpace(keyParam))
|
||||
{
|
||||
url = $"m42Services/api/c4itf4sdwebapi/getticketoverviewcounts?sid={HttpUtility.UrlEncode(sid)}&scope={scope}";
|
||||
}
|
||||
var wc = await GetWebClient(requestInfo, token);
|
||||
var res = await wc.HttpEnh.GetAsync(url, token);
|
||||
if (token.IsCancellationRequested)
|
||||
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (res?.IsSuccessStatusCode == true)
|
||||
@@ -661,6 +954,86 @@ namespace C4IT.DataHistoryProvider
|
||||
{
|
||||
LogException(E);
|
||||
}
|
||||
|
||||
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, int>> GetTicketOverviewCountsAsync(IEnumerable<string> keys, bool useRoleScope, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
|
||||
{
|
||||
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
|
||||
if (cPerformanceLogger.IsActive && requestInfo != null) { if (CM == null) CM = MethodBase.GetCurrentMethod(); cPerformanceLogger.LogPerformanceStart(LogDeep, CM, requestInfo.id, requestInfo.created); }
|
||||
var _startTime = DateTime.UtcNow;
|
||||
|
||||
try
|
||||
{
|
||||
if (!await CheckOnline())
|
||||
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var sid = requestInfo?.userInfo?.AdSid;
|
||||
if (string.IsNullOrWhiteSpace(sid))
|
||||
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var normalizedKeys = (keys ?? Enumerable.Empty<string>())
|
||||
.Where(k => !string.IsNullOrWhiteSpace(k))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
var now = DateTime.UtcNow;
|
||||
var ttl = GetTicketOverviewCacheDuration(useRoleScope);
|
||||
|
||||
if (!useRoleScope)
|
||||
{
|
||||
if (TryGetTicketOverviewPersonalCache(sid, now, out var cachedPersonal))
|
||||
return FilterTicketOverviewCounts(cachedPersonal, normalizedKeys);
|
||||
|
||||
var counts = await FetchTicketOverviewCountsAsync(sid, "personal", normalizedKeys, requestInfo, Token);
|
||||
if (Token.IsCancellationRequested)
|
||||
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
SetTicketOverviewPersonalCache(sid, counts, now.Add(ttl));
|
||||
return FilterTicketOverviewCounts(counts, normalizedKeys);
|
||||
}
|
||||
|
||||
var roleIds = await GetTicketOverviewRoleIdsAsync(sid, now, ttl, Token);
|
||||
if (roleIds == null || roleIds.Count == 0)
|
||||
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var countsByRole = new Dictionary<Guid, Dictionary<string, int>>();
|
||||
var missingRoles = new List<Guid>();
|
||||
|
||||
foreach (var roleId in roleIds)
|
||||
{
|
||||
if (TryGetTicketOverviewRoleCache(roleId, now, out var cachedRole))
|
||||
{
|
||||
countsByRole[roleId] = cachedRole;
|
||||
}
|
||||
else
|
||||
{
|
||||
missingRoles.Add(roleId);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingRoles.Count > 0)
|
||||
{
|
||||
var fetched = await FetchTicketOverviewCountsByRolesAsync(missingRoles, normalizedKeys, requestInfo, LogDeep + 1, Token);
|
||||
var expiresAt = DateTime.UtcNow.Add(ttl);
|
||||
|
||||
foreach (var roleId in missingRoles)
|
||||
{
|
||||
if (!fetched.TryGetValue(roleId, out var roleCounts))
|
||||
roleCounts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
SetTicketOverviewRoleCache(roleId, roleCounts, expiresAt);
|
||||
countsByRole[roleId] = roleCounts;
|
||||
}
|
||||
}
|
||||
|
||||
var summed = SumTicketOverviewCounts(countsByRole.Values);
|
||||
return FilterTicketOverviewCounts(summed, normalizedKeys);
|
||||
}
|
||||
catch (Exception E)
|
||||
{
|
||||
LogException(E);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); }
|
||||
|
||||
Reference in New Issue
Block a user