Add generic ticket activity filters

This commit is contained in:
Drechsler, Meik
2026-07-23 11:18:42 +02:00
parent d8022567af
commit 8f66923952
15 changed files with 502 additions and 181 deletions

View File

@@ -302,9 +302,12 @@
<Compile Include="..\F4SD-Cockpit-ServerCore\DataSources\IRemoteDesktopManager.cs"> <Compile Include="..\F4SD-Cockpit-ServerCore\DataSources\IRemoteDesktopManager.cs">
<Link>DataSources\IRemoteDesktopManager.cs</Link> <Link>DataSources\IRemoteDesktopManager.cs</Link>
</Compile> </Compile>
<Compile Include="..\F4SD-Cockpit-ServerCore\DataSources\ISearchResultRelationProvider.cs"> <Compile Include="..\F4SD-Cockpit-ServerCore\DataSources\ISearchResultRelationProvider.cs">
<Link>DataSources\ISearchResultRelationProvider.cs</Link> <Link>DataSources\ISearchResultRelationProvider.cs</Link>
</Compile> </Compile>
<Compile Include="..\F4SD-Cockpit-ServerCore\DataSources\ITicketActivityProvider.cs">
<Link>DataSources\ITicketActivityProvider.cs</Link>
</Compile>
<Compile Include="..\F4SD-Cockpit-ServerCore\F4SDConfigGlobalParametersXml.cs"> <Compile Include="..\F4SD-Cockpit-ServerCore\F4SDConfigGlobalParametersXml.cs">
<Link>F4SDConfigGlobalParametersXml.cs</Link> <Link>F4SDConfigGlobalParametersXml.cs</Link>
</Compile> </Compile>
@@ -381,4 +384,4 @@
</PropertyGroup> </PropertyGroup>
<Error Condition="!Exists('..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets'))" /> <Error Condition="!Exists('..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets'))" />
</Target> </Target>
</Project> </Project>

View File

@@ -24,6 +24,22 @@
<TicketTypeProcessing Type="UnclassifiedTicket" Value="intern" /> <TicketTypeProcessing Type="UnclassifiedTicket" Value="intern" />
<TicketTypeProcessing Type="Incident" Value="intern" /> <TicketTypeProcessing Type="Incident" Value="intern" />
</TicketProcessing> </TicketProcessing>
<ActivityFilters>
<Filter Provider="Matrix42" Field="Queue" Enabled="false" Match="include" EmptyHandling="include">
<Value ID="2a7e8099-3d57-f011-1988-00155d320605" Name="HR" />
<Value ID="2a7a8099-3d47-f011-1988-00155d320505" Name="FM" />
</Filter>
<Filter Field="AssignmentGroup" Enabled="false" Match="include" EmptyHandling="exclude">
<Value ID="0a76de08-136c-764a-b410-5610e8076712" Name="HR Service Desk Agent" />
<Value ID="0b76de08-8668-a026-b410-5610e8076f95" Name="HR-Manager" />
<Value ID="0a76de08-d589-4c94-b410-5610e8076919" Name="HR-Spezialist" />
<Value ID="e87dde08-235c-04e5-b410-562b64035575" Name="Facility-Koordinator" />
</Filter>
<Filter Provider="Matrix42" Field="Workspace" Enabled="false" Match="include" EmptyHandling="exclude">
<Value ID="1676de08-27f3-550c-b410-5610e807aa02" Name="HR Service Management" />
<Value ID="ed7dde08-199e-c27b-b410-562b64037aa1" Name="Gebäudemanagement" />
</Filter>
</ActivityFilters>
<OverviewPollingPersonal Policy="Mandatory" Value="10" /> <OverviewPollingPersonal Policy="Mandatory" Value="10" />
<OverviewPollingRole Policy="Mandatory" Value="5" /> <OverviewPollingRole Policy="Mandatory" Value="5" />
</TicketConfiguration> </TicketConfiguration>

View File

@@ -132,6 +132,21 @@
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:simpleType name="ActivityFilterMatchEnum">
<xs:restriction base="xs:string">
<xs:enumeration value="include"/>
<xs:enumeration value="exclude"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="ActivityFilterEmptyHandlingEnum">
<xs:restriction base="xs:string">
<xs:enumeration value="include"/>
<xs:enumeration value="exclude"/>
<xs:enumeration value="only"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="TicketConfiguration"> <xs:element name="TicketConfiguration">
<xs:complexType> <xs:complexType>
<xs:all> <xs:all>
@@ -182,6 +197,30 @@
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="ActivityFilters" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:sequence>
<xs:element name="Filter" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="Value" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="ID" type="xs:string" use="optional"/>
<xs:attribute name="Name" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="Provider" type="xs:string" use="optional"/>
<xs:attribute name="Field" type="xs:string" use="required"/>
<xs:attribute name="Enabled" type="xs:boolean" use="optional" default="false"/>
<xs:attribute name="Match" type="ActivityFilterMatchEnum" use="optional" default="exclude"/>
<xs:attribute name="EmptyHandling" type="ActivityFilterEmptyHandlingEnum" use="optional" default="include"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="TicketProcessing" minOccurs="0" maxOccurs="1"> <xs:element name="TicketProcessing" minOccurs="0" maxOccurs="1">
<xs:complexType> <xs:complexType>
<xs:sequence> <xs:sequence>

View File

@@ -82,15 +82,11 @@
</Azure-Tenant> </Azure-Tenant>
<!--<Azure-Tenant Domain="sd0024.onmicrosoft.com" TenantID="07362148-410e-4636-a9cb-795fba1a5452" Credential="AzureAccess_Dieter"/>--> <!--<Azure-Tenant Domain="sd0024.onmicrosoft.com" TenantID="07362148-410e-4636-a9cb-795fba1a5452" Credential="AzureAccess_Dieter"/>-->
</Azure-AD> </Azure-AD>
<Matrix42-WPM Server="srvwsm001.imagoverum.com" Credential="M42API-Demo-26-1" ClosedTicketHistory="9999" DisplayName="M42 Demo server (imagoverum)" ApiTokenLifetime ="30 days" ActivityQueueFilter="showAll"> <Matrix42-WPM Server="srvwsm001.imagoverum.com" Credential="M42API-Demo-26-1" ClosedTicketHistory="9999" DisplayName="M42 Demo server (imagoverum)" ApiTokenLifetime ="30 days">
<Matrix42-Ticket DisableAutomaticTimeTracking="1" <Matrix42-Ticket DisableAutomaticTimeTracking="1"
ShowDocumentCaseDialog="ifRequired"/> ShowDocumentCaseDialog="ifRequired"/>
<DisplayName Lang="DE">M42 Demo Server (Imagoverum)</DisplayName> <DisplayName Lang="DE">M42 Demo Server (Imagoverum)</DisplayName>
<Queues> </Matrix42-WPM>
<Queue QueueName="HR" QueueID="2a7e8099-3d57-f011-1988-00155d320605" />
<Queue QueueName="FM" QueueID="2a7a8099-3d47-f011-1988-00155d320505" />
</Queues>
</Matrix42-WPM>
<!--<Matrix42-WPM Server="m42server.imagoverum.com" Credential="M42API-Demo-Dieter" ClosedTicketHistory="9999"/>--> <!--<Matrix42-WPM Server="m42server.imagoverum.com" Credential="M42API-Demo-Dieter" ClosedTicketHistory="9999"/>-->
<Nexthink> <Nexthink>
<Scan-Timing ScanInterval="24:00" ScanOffset="00:10" Timezone="W. Europe Standard Time"/> <Scan-Timing ScanInterval="24:00" ScanOffset="00:10" Timezone="W. Europe Standard Time"/>

View File

@@ -276,7 +276,8 @@ namespace C4IT.DataHistoryProvider
public readonly Dictionary<enumDataHistoryOrigin, cDataHistoryCollectorModule> Connectors = new Dictionary<enumDataHistoryOrigin, cDataHistoryCollectorModule>(); public readonly Dictionary<enumDataHistoryOrigin, cDataHistoryCollectorModule> Connectors = new Dictionary<enumDataHistoryOrigin, cDataHistoryCollectorModule>();
public readonly cDataHistoryCollectorActiveDirectory ActiveDirectory = null; public readonly cDataHistoryCollectorActiveDirectory ActiveDirectory = null;
public readonly cDataHistoryCollectorClientAgent F4sdAgent = null; public readonly cDataHistoryCollectorClientAgent F4sdAgent = null;
public cDataHistoryCollectorM42Wpm M42WpmCollector { get; private set; } = null; public cDataHistoryCollectorM42Wpm M42WpmCollector { get; private set; } = null;
public ITicketActivityProvider TicketActivityProvider { get; private set; } = null;
public cDataHistoryCollectorNxql NxqlCollector { get; private set; } = null; public cDataHistoryCollectorNxql NxqlCollector { get; private set; } = null;
public cDataHistoryCollectorCitrix CitrixCollector { get; private set; } = null; public cDataHistoryCollectorCitrix CitrixCollector { get; private set; } = null;
//public cDataHistoryCollectorEmpirum EmpirumCollector { get; private set; } = null; //public cDataHistoryCollectorEmpirum EmpirumCollector { get; private set; } = null;
@@ -409,10 +410,12 @@ namespace C4IT.DataHistoryProvider
GlobalParametersConfigFile = _cfgGlobalParameters; GlobalParametersConfigFile = _cfgGlobalParameters;
GlobalConfig = new cF4sdGlobalConfig(regPath: null); GlobalConfig = new cF4sdGlobalConfig(regPath: null);
if (GlobalParametersConfigFile.Parameters != null) if (GlobalParametersConfigFile.Parameters != null)
GlobalConfig.Load(CentralParameters: GlobalParametersConfigFile.Parameters); GlobalConfig.Load(CentralParameters: GlobalParametersConfigFile.Parameters);
} GlobalConfig.TicketConfiguration.ActivityFilters =
GlobalParametersConfigFile.ActivityFilters ?? new cActivityFilterPolicy();
}
return true; return true;
} }
@@ -514,8 +517,9 @@ namespace C4IT.DataHistoryProvider
if (cF4SDLicense.Instance?.Modules != null) if (cF4SDLicense.Instance?.Modules != null)
if (cF4SDLicense.Instance.Modules.ContainsKey(_M42WpmCollector.LicenseId)) if (cF4SDLicense.Instance.Modules.ContainsKey(_M42WpmCollector.LicenseId))
{ {
M42WpmCollector = _M42WpmCollector; M42WpmCollector = _M42WpmCollector;
Connectors.Add(enumDataHistoryOrigin.M42Wpm, M42WpmCollector); TicketActivityProvider = _M42WpmCollector;
Connectors.Add(enumDataHistoryOrigin.M42Wpm, M42WpmCollector);
RegisterSearchRelationProvider(_M42WpmCollector); RegisterSearchRelationProvider(_M42WpmCollector);
} }
} }

View File

@@ -2,8 +2,9 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net;
using System.Net.Http;
using System.Numerics; using System.Numerics;
using System.Reflection; using System.Reflection;
using System.Security.Principal; using System.Security.Principal;
@@ -31,14 +32,17 @@ using static C4IT.Matrix42.WebClient.cM42WebClient;
namespace C4IT.DataHistoryProvider namespace C4IT.DataHistoryProvider
{ {
public class cDataHistoryCollectorM42Wpm : cDataHistoryCollectorModule, ISearchResultRelationProvider public class cDataHistoryCollectorM42Wpm : cDataHistoryCollectorModule, ISearchResultRelationProvider, ITicketActivityProvider
{ {
public const string constConnectorName = "Matrix42 WPM conntector"; public const string constConnectorName = "Matrix42 WPM conntector";
public const string constLicenseId = "9CE1A6BE-6A0C-4A27-94A5-44AB997B8E62"; public const string constLicenseId = "9CE1A6BE-6A0C-4A27-94A5-44AB997B8E62";
private const string constUrlIsAlive = "m42Services/api/c4itf4sdwebapi/isalive"; private const string constUrlIsAlive = "m42Services/api/c4itf4sdwebapi/isalive";
private const string constUrlGetTickets = "m42Services/api/c4itf4sdwebapi/getticketlist?sid={0}&hours={1}&queueoption={2}"; private const string constUrlGetTickets = "m42Services/api/c4itf4sdwebapi/getticketlist?sid={0}&hours={1}&queueoption={2}";
private const string constUrlGetTicketsQueuesParam = "&queues={0}"; private const string constUrlGetTicketsQueuesParam = "&queues={0}";
private const string constUrlGetTicketsV2 = "m42Services/api/c4itf4sdwebapi/v2/getticketlist";
private const string constUrlGetTicketOverviewCountsV2 = "m42Services/api/c4itf4sdwebapi/v2/getticketoverviewcounts";
private const string constUrlGetTicketOverviewRelationsV2 = "m42Services/api/c4itf4sdwebapi/v2/getticketoverviewrelations";
private const string constUrlGetTicketDetails = "m42Services/api/c4itf4sdwebapi/getticketdetails?objectid={0}"; private const string constUrlGetTicketDetails = "m42Services/api/c4itf4sdwebapi/getticketdetails?objectid={0}";
private const string constUrlGetTicketHistory = "m42Services/api/c4itf4sdwebapi/gettickethistory?objectid={0}"; private const string constUrlGetTicketHistory = "m42Services/api/c4itf4sdwebapi/gettickethistory?objectid={0}";
private const string constUrlGetCreateTicket = "m42Services/api/c4itf4sdwebapi/getdirectlinkcreateticket?sid={0}{1}"; private const string constUrlGetCreateTicket = "m42Services/api/c4itf4sdwebapi/getdirectlinkcreateticket?sid={0}{1}";
@@ -687,24 +691,33 @@ namespace C4IT.DataHistoryProvider
if (!await CheckOnline()) if (!await CheckOnline())
return null; return null;
var objSid = new SecurityIdentifier(UserId.sid, 0); var objSid = new SecurityIdentifier(UserId.sid, 0);
var strSid = objSid.ToString(); var strSid = objSid.ToString();
var queueOption = (int)Collector.InfrastructureConfig.M42Wpm.ActivityQueueFilterOption; var _wc = await GetWebClient(requestInfo, Token);
var strUrl = string.Format(constUrlGetTickets, var request = new cTicketActivityListRequest
strSid, {
Collector.InfrastructureConfig.M42Wpm.ClosedTicketHistory, Sid = strSid,
queueOption); Hours = Collector.InfrastructureConfig.M42Wpm.ClosedTicketHistory,
Filters = GetActivityFilters()
if (queueOption != 0) };
{ var res = await PostJsonAsync(_wc, constUrlGetTicketsV2, request, Token);
var encodedQueues = GetEncodedActivityQueues(); if (res?.StatusCode == HttpStatusCode.NotFound)
if (!string.IsNullOrWhiteSpace(encodedQueues)) {
strUrl += string.Format(constUrlGetTicketsQueuesParam, encodedQueues); if (!TryGetLegacyQueueFilter(out var queueOption, out var encodedQueues))
} {
LogEntry("The activity filter policy requires the v2 ticket API; legacy fallback was rejected (fail closed).", LogLevels.Warning);
var _wc = await GetWebClient(requestInfo, Token); return null;
var res = await _wc.HttpEnh.GetAsync(strUrl); }
if (res.IsSuccessStatusCode) var strUrl = string.Format(
constUrlGetTickets,
strSid,
Collector.InfrastructureConfig.M42Wpm.ClosedTicketHistory,
queueOption);
if (!string.IsNullOrWhiteSpace(encodedQueues))
strUrl += string.Format(constUrlGetTicketsQueuesParam, encodedQueues);
res = await _wc.HttpEnh.GetAsync(strUrl);
}
if (res?.IsSuccessStatusCode == true)
{ {
var strJson = await res.Content.ReadAsStringAsync(); var strJson = await res.Content.ReadAsStringAsync();
if (!string.IsNullOrEmpty(strJson)) if (!string.IsNullOrEmpty(strJson))
@@ -1093,12 +1106,76 @@ namespace C4IT.DataHistoryProvider
return filtered; return filtered;
} }
private int GetActivityQueueOption() private int GetActivityQueueOption()
{ {
return (int)Collector.InfrastructureConfig.M42Wpm.ActivityQueueFilterOption; return (int)Collector.InfrastructureConfig.M42Wpm.ActivityQueueFilterOption;
} }
private string GetEncodedActivityQueues() private cActivityFilterPolicy GetActivityFilters()
{
return Collector?.GetGlobalConfig()?.TicketConfiguration?.ActivityFilters
?? new cActivityFilterPolicy();
}
private bool TryGetLegacyQueueFilter(out int queueOption, out string encodedQueues)
{
queueOption = 0;
encodedQueues = null;
var activeFilters = (GetActivityFilters()?.Filters ?? new List<cActivityFilter>())
.Where(entry => entry?.Enabled == true)
.ToList();
if (activeFilters.Count == 0)
return true;
if (activeFilters.Count != 1)
return false;
var activityFilter = activeFilters[0];
if (!string.Equals(activityFilter.Provider, "Matrix42", StringComparison.OrdinalIgnoreCase)
|| !string.Equals(activityFilter.Field, "Queue", StringComparison.OrdinalIgnoreCase)
|| activityFilter.Match != enumActivityFilterMatch.include
|| activityFilter.EmptyHandling == enumActivityFilterEmptyHandling.only)
{
return false;
}
queueOption = activityFilter.EmptyHandling == enumActivityFilterEmptyHandling.include ? 1 : 2;
var values = (activityFilter.Values ?? new List<cActivityFilterValue>())
.Where(value => value != null)
.Select(value =>
{
if (Guid.TryParse(value.ID, out var id) && id != Guid.Empty)
return ":" + HttpUtility.UrlEncode(id.ToString("D"));
return string.IsNullOrWhiteSpace(value.Name)
? null
: HttpUtility.UrlEncode(value.Name.Trim());
})
.Where(value => !string.IsNullOrWhiteSpace(value))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
if (values.Count == 0)
return false;
encodedQueues = string.Join("|", values);
return true;
}
private static Task<HttpResponseMessage> PostJsonAsync(
cM42WebClient webClient,
string url,
object request,
CancellationToken token)
{
var json = JsonConvert.SerializeObject(
request,
Formatting.None,
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
return webClient.HttpEnh.PostAsync(
url,
new StringContent(json, Encoding.UTF8, "application/json"),
token);
}
private string GetEncodedActivityQueues()
{ {
var queues = Collector.InfrastructureConfig.M42Wpm.ActivityQueues; var queues = Collector.InfrastructureConfig.M42Wpm.ActivityQueues;
if (queues == null || queues.Count == 0) if (queues == null || queues.Count == 0)
@@ -1140,28 +1217,36 @@ namespace C4IT.DataHistoryProvider
string scope, string scope,
IReadOnlyCollection<string> requestedKeys, IReadOnlyCollection<string> requestedKeys,
cF4sdWebRequestInfo requestInfo, cF4sdWebRequestInfo requestInfo,
CancellationToken token) CancellationToken token)
{ {
try try
{ {
var urlBuilder = new StringBuilder(constUrlGetTicketOverviewCounts); var wc = await GetWebClient(requestInfo, token);
urlBuilder.Append("?sid=").Append(HttpUtility.UrlEncode(sid)); var request = new cTicketOverviewFilterRequest
urlBuilder.Append("&scope=").Append(HttpUtility.UrlEncode(scope ?? string.Empty)); {
Sid = sid,
if (requestedKeys != null && requestedKeys.Count > 0) Scope = scope,
{ Keys = requestedKeys?.ToList() ?? new List<string>(),
urlBuilder.Append("&keys=").Append(HttpUtility.UrlEncode(string.Join(",", requestedKeys))); Filters = GetActivityFilters()
} };
var res = await PostJsonAsync(wc, constUrlGetTicketOverviewCountsV2, request, token);
var queueOption = GetActivityQueueOption(); if (res?.StatusCode == HttpStatusCode.NotFound)
var encodedQueues = queueOption != 0 ? GetEncodedActivityQueues() : null; {
AppendQueueFilterQuery(urlBuilder, queueOption, encodedQueues); if (!TryGetLegacyQueueFilter(out var queueOption, out var encodedQueues))
{
var url = urlBuilder.ToString(); LogEntry("The activity filter policy requires the v2 overview API; legacy fallback was rejected (fail closed).", LogLevels.Warning);
var wc = await GetWebClient(requestInfo, token); return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
var res = await wc.HttpEnh.GetAsync(url, token); }
if (token.IsCancellationRequested) var urlBuilder = new StringBuilder(constUrlGetTicketOverviewCounts);
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); urlBuilder.Append("?sid=").Append(HttpUtility.UrlEncode(sid));
urlBuilder.Append("&scope=").Append(HttpUtility.UrlEncode(scope ?? string.Empty));
if (requestedKeys != null && requestedKeys.Count > 0)
urlBuilder.Append("&keys=").Append(HttpUtility.UrlEncode(string.Join(",", requestedKeys)));
AppendQueueFilterQuery(urlBuilder, queueOption, encodedQueues);
res = await wc.HttpEnh.GetAsync(urlBuilder.ToString(), token);
}
if (token.IsCancellationRequested)
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
if (res?.IsSuccessStatusCode == true) if (res?.IsSuccessStatusCode == true)
{ {
@@ -1227,64 +1312,12 @@ namespace C4IT.DataHistoryProvider
if (string.IsNullOrWhiteSpace(sid)) if (string.IsNullOrWhiteSpace(sid))
return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase); return new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
var normalizedKeys = (keys ?? Enumerable.Empty<string>()) var normalizedKeys = (keys ?? Enumerable.Empty<string>())
.Where(k => !string.IsNullOrWhiteSpace(k)) .Where(k => !string.IsNullOrWhiteSpace(k))
.Distinct(StringComparer.OrdinalIgnoreCase) .Distinct(StringComparer.OrdinalIgnoreCase)
.ToList(); .ToList();
var now = DateTime.UtcNow; var scope = useRoleScope ? "role" : "personal";
var ttl = GetTicketOverviewCacheDuration(useRoleScope); return await FetchTicketOverviewCountsAsync(sid, scope, normalizedKeys, requestInfo, Token);
if (!useRoleScope)
{
// process thr personal scope
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);
}
// process the role scope
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) catch (Exception E)
{ {
@@ -1314,26 +1347,37 @@ namespace C4IT.DataHistoryProvider
return new List<cF4sdApiSearchResultRelation>(); return new List<cF4sdApiSearchResultRelation>();
var sid = requestInfo?.userInfo?.AdSid; var sid = requestInfo?.userInfo?.AdSid;
if (string.IsNullOrWhiteSpace(sid)) if (string.IsNullOrWhiteSpace(sid))
return new List<cF4sdApiSearchResultRelation>(); return new List<cF4sdApiSearchResultRelation>();
var scope = useRoleScope ? "role" : "personal"; var scope = useRoleScope ? "role" : "personal";
var urlBuilder = new StringBuilder(constUrlGetTicketOverviewRelations); var wc = await GetWebClient(requestInfo, Token);
urlBuilder.Append("?sid=").Append(HttpUtility.UrlEncode(sid)); var request = new cTicketOverviewFilterRequest
urlBuilder.Append("&scope=").Append(HttpUtility.UrlEncode(scope)); {
urlBuilder.Append("&key=").Append(HttpUtility.UrlEncode(key)); Sid = sid,
urlBuilder.Append("&count=").Append(Math.Max(0, count)); Scope = scope,
Key = key,
var queueOption = GetActivityQueueOption(); Count = Math.Max(0, count),
var encodedQueues = queueOption != 0 ? GetEncodedActivityQueues() : null; Filters = GetActivityFilters()
AppendQueueFilterQuery(urlBuilder, queueOption, encodedQueues); };
var res = await PostJsonAsync(wc, constUrlGetTicketOverviewRelationsV2, request, Token);
var url = urlBuilder.ToString(); if (res?.StatusCode == HttpStatusCode.NotFound)
{
var wc = await GetWebClient(requestInfo, Token); if (!TryGetLegacyQueueFilter(out var queueOption, out var encodedQueues))
var res = await wc.HttpEnh.GetAsync(url, Token); {
if (Token.IsCancellationRequested) LogEntry("The activity filter policy requires the v2 relation API; legacy fallback was rejected (fail closed).", LogLevels.Warning);
return new List<cF4sdApiSearchResultRelation>(); return new List<cF4sdApiSearchResultRelation>();
}
var urlBuilder = new StringBuilder(constUrlGetTicketOverviewRelations);
urlBuilder.Append("?sid=").Append(HttpUtility.UrlEncode(sid));
urlBuilder.Append("&scope=").Append(HttpUtility.UrlEncode(scope));
urlBuilder.Append("&key=").Append(HttpUtility.UrlEncode(key));
urlBuilder.Append("&count=").Append(Math.Max(0, count));
AppendQueueFilterQuery(urlBuilder, queueOption, encodedQueues);
res = await wc.HttpEnh.GetAsync(urlBuilder.ToString(), Token);
}
if (Token.IsCancellationRequested)
return new List<cF4sdApiSearchResultRelation>();
if (res?.IsSuccessStatusCode == true) if (res?.IsSuccessStatusCode == true)
{ {

View File

@@ -1,7 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Linq;
using System.Xml; using System.Reflection;
using System.Security;
using System.Text;
using System.Xml;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -21,7 +24,8 @@ namespace C4IT.DataHistoryProvider
private const string constFileNameF4sdSchema = "F4SD-Global-Configuration.xsd"; private const string constFileNameF4sdSchema = "F4SD-Global-Configuration.xsd";
private const string constConfigRootElement = "F4SD-Global-Configuration"; private const string constConfigRootElement = "F4SD-Global-Configuration";
public cConfigHelperParameterList Parameters { get; private set; } = null; public cConfigHelperParameterList Parameters { get; private set; } = null;
public cActivityFilterPolicy ActivityFilters { get; private set; } = new cActivityFilterPolicy();
private cDataHistoryConfigInfrastructure InfrastructureConfig = null; private cDataHistoryConfigInfrastructure InfrastructureConfig = null;
@@ -102,12 +106,15 @@ namespace C4IT.DataHistoryProvider
if (_overrideValues != null && _overrideValues.ValueList != null && _overrideValues.ValueList.Count > 0) if (_overrideValues != null && _overrideValues.ValueList != null && _overrideValues.ValueList.Count > 0)
_result.Items[_overrideValues.Name] = _overrideValues; _result.Items[_overrideValues.Name] = _overrideValues;
continue; continue;
case "TicketProcessing": case "TicketProcessing":
var _ticketProcessingEntry = getTicketProcessingEntry(_item, Parser); var _ticketProcessingEntry = getTicketProcessingEntry(_item, Parser);
if (_ticketProcessingEntry != null) if (_ticketProcessingEntry != null)
_result.Items[_ticketProcessingEntry.Name] = _ticketProcessingEntry; _result.Items[_ticketProcessingEntry.Name] = _ticketProcessingEntry;
continue; continue;
} case "ActivityFilters":
ActivityFilters = getActivityFilters(_item, Parser);
continue;
}
if (_attPolicy != null && _attValue != null && _ElementCount == 0) if (_attPolicy != null && _attValue != null && _ElementCount == 0)
{ {
@@ -156,8 +163,89 @@ namespace C4IT.DataHistoryProvider
if (CM != null) LogMethodEnd(CM); if (CM != null) LogMethodEnd(CM);
} }
return _result; return _result;
} }
private static cActivityFilterPolicy getActivityFilters(XmlElement node, cXmlParser parser)
{
var result = new cActivityFilterPolicy();
foreach (XmlElement filterNode in node.SelectNodes("Filter").OfType<XmlElement>())
{
parser.EnterElement(filterNode.Name);
try
{
if (!Enum.TryParse(filterNode.GetAttribute("Match"), true, out enumActivityFilterMatch match))
match = enumActivityFilterMatch.exclude;
if (!Enum.TryParse(filterNode.GetAttribute("EmptyHandling"), true, out enumActivityFilterEmptyHandling emptyHandling))
emptyHandling = enumActivityFilterEmptyHandling.include;
var filter = new cActivityFilter
{
Provider = filterNode.GetAttribute("Provider"),
Field = filterNode.GetAttribute("Field"),
Enabled = bool.TryParse(filterNode.GetAttribute("Enabled"), out var enabled) && enabled,
Match = match,
EmptyHandling = emptyHandling
};
foreach (XmlElement valueNode in filterNode.SelectNodes("Value").OfType<XmlElement>())
{
filter.Values.Add(new cActivityFilterValue
{
ID = valueNode.GetAttribute("ID"),
Name = valueNode.GetAttribute("Name")
});
}
result.Filters.Add(filter);
}
finally
{
parser.LeaveElement(filterNode.Name);
}
}
return result;
}
private string getDefaultActivityFiltersXml()
{
var queueOption = InfrastructureConfig?.M42Wpm?.ActivityQueueFilterOption
?? enumActivityQueueFilterOptions.showAll;
var queues = InfrastructureConfig?.M42Wpm?.ActivityQueues
?? new List<cApiM42TicketQueueInfo>();
var enableQueueFilter = queueOption != enumActivityQueueFilterOptions.showAll && queues.Count > 0;
var emptyHandling = queueOption == enumActivityQueueFilterOptions.onlyListedQueues
? enumActivityFilterEmptyHandling.exclude
: enumActivityFilterEmptyHandling.include;
var xml = new StringBuilder();
xml.Append("<ActivityFilters>");
xml.AppendFormat(
"<Filter Provider=\"Matrix42\" Field=\"Queue\" Enabled=\"{0}\" Match=\"include\" EmptyHandling=\"{1}\">",
enableQueueFilter.ToString().ToLowerInvariant(),
emptyHandling);
foreach (var queue in queues.Where(queue => queue != null))
{
xml.AppendFormat(
"<Value ID=\"{0}\" Name=\"{1}\" />",
queue.QueueID == Guid.Empty ? string.Empty : queue.QueueID.ToString("D"),
SecurityElement.Escape(queue.QueueName ?? string.Empty));
}
xml.Append("</Filter>");
xml.Append("<Filter Field=\"AssignmentGroup\" Enabled=\"false\" Match=\"include\" EmptyHandling=\"exclude\">");
xml.Append("<Value ID=\"0a76de08-136c-764a-b410-5610e8076712\" Name=\"HR Service Desk Agent\" />");
xml.Append("<Value ID=\"0b76de08-8668-a026-b410-5610e8076f95\" Name=\"HR-Manager\" />");
xml.Append("<Value ID=\"0a76de08-d589-4c94-b410-5610e8076919\" Name=\"HR-Spezialist\" />");
xml.Append("<Value ID=\"e87dde08-235c-04e5-b410-562b64035575\" Name=\"Facility-Koordinator\" />");
xml.Append("</Filter>");
xml.Append("<Filter Provider=\"Matrix42\" Field=\"Workspace\" Enabled=\"false\" Match=\"include\" EmptyHandling=\"exclude\">");
xml.Append("<Value ID=\"1676de08-27f3-550c-b410-5610e807aa02\" Name=\"HR Service Management\" />");
xml.Append("<Value ID=\"ed7dde08-199e-c27b-b410-562b64037aa1\" Name=\"Gebäudemanagement\" />");
xml.Append("</Filter>");
xml.Append("</ActivityFilters>");
return xml.ToString();
}
private cConfigHelperParameterEntry getInformationClassSearchPriority(XmlElement XNode, cXmlParser Parser) private cConfigHelperParameterEntry getInformationClassSearchPriority(XmlElement XNode, cXmlParser Parser)
{ {
@@ -487,11 +575,16 @@ namespace C4IT.DataHistoryProvider
, "ShowOverview" , "ShowOverview"
, "<ShowOverview Policy=\"Hidden\" Value=\"true\" />" , "<ShowOverview Policy=\"Hidden\" Value=\"true\" />"
); );
RetVal |= DoXmlInsertElement(XmlRoot RetVal |= DoXmlInsertElement(XmlRoot
, "TicketConfiguration" , "TicketConfiguration"
, "TicketProcessing" , "TicketProcessing"
, "<TicketProcessing Policy=\"Mandatory\" Value=\"both\"><TicketTypeProcessing Type=\"Ticket\" Value=\"intern\" /><TicketTypeProcessing Type=\"UnclassifiedTicket\" Value=\"intern\" /><TicketTypeProcessing Type=\"Incident\" Value=\"intern\" /></TicketProcessing>" , "<TicketProcessing Policy=\"Mandatory\" Value=\"both\"><TicketTypeProcessing Type=\"Ticket\" Value=\"intern\" /><TicketTypeProcessing Type=\"UnclassifiedTicket\" Value=\"intern\" /><TicketTypeProcessing Type=\"Incident\" Value=\"intern\" /></TicketProcessing>"
); );
RetVal |= DoXmlInsertElement(XmlRoot
, "TicketConfiguration"
, "ActivityFilters"
, getDefaultActivityFiltersXml()
);
RetVal |= DoXmlRemoveElement(XmlRoot RetVal |= DoXmlRemoveElement(XmlRoot
, "TicketConfiguration" , "TicketConfiguration"
, "OpenActivitiesExternally" , "OpenActivitiesExternally"

View File

@@ -49,7 +49,7 @@ namespace C4IT.DataHistoryProvider
{ {
} }
public override bool InstantiateProperties(XmlElement XRoot, cXmlParser Parser) public override bool InstantiateProperties(XmlElement XRoot, cXmlParser Parser)
{ {
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
try try
@@ -118,9 +118,34 @@ namespace C4IT.DataHistoryProvider
if (CM != null) LogMethodEnd(CM); if (CM != null) LogMethodEnd(CM);
} }
return false; return false;
} }
}
public override bool DoXmlUpdates(XmlElement xmlRoot, bool withM42Config = false, bool withIntuneConfig = false, bool withMobileDeviceConfig = false, bool withCitrixConfig = false)
{
// Queue filtering moved to the provider-neutral global ActivityFilters policy.
// InstantiateProperties runs first, so the values remain available for the
// one-time migration while the obsolete XML is removed.
if (!(xmlRoot.SelectSingleNode("Matrix42-WPM") is XmlElement matrix42))
return false;
var changed = false;
if (matrix42.HasAttribute("ActivityQueueFilter"))
{
matrix42.RemoveAttribute("ActivityQueueFilter");
changed = true;
}
var queues = matrix42.SelectSingleNode("Queues");
if (queues != null)
{
matrix42.RemoveChild(queues);
changed = true;
}
return changed;
}
}
public class cDataHistoryConfigDatabase : IConfigNodeValidation public class cDataHistoryConfigDatabase : IConfigNodeValidation
{ {

View File

@@ -0,0 +1,38 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using C4IT.DataHistoryProvider;
using C4IT.FASD.Base;
namespace C4IT_DataHistoryProvider_Base.DataSources
{
/// <summary>
/// Provider-neutral access to ticket-system activities used by search and overview.
/// Provider-specific filter fields remain opaque configuration values.
/// </summary>
public interface ITicketActivityProvider
{
bool TicketAndServiceRequestsEnabled { get; }
Task<List<cF4SDTicketSummary>> GetTicketsFromSid(
cF4sdConnectorIds userId,
CancellationToken token,
cF4sdWebRequestInfo requestInfo);
Task<Dictionary<string, int>> GetTicketOverviewCountsAsync(
IEnumerable<string> keys,
bool useRoleScope,
cF4sdWebRequestInfo requestInfo,
int logDeep,
CancellationToken token);
Task<List<cF4sdApiSearchResultRelation>> GetTicketOverviewRelationsAsync(
string key,
bool useRoleScope,
int count,
cF4sdWebRequestInfo requestInfo,
int logDeep,
CancellationToken token);
}
}

View File

@@ -24,6 +24,22 @@
<TicketTypeProcessing Type="UnclassifiedTicket" Value="intern" /> <TicketTypeProcessing Type="UnclassifiedTicket" Value="intern" />
<TicketTypeProcessing Type="Incident" Value="intern" /> <TicketTypeProcessing Type="Incident" Value="intern" />
</TicketProcessing> </TicketProcessing>
<ActivityFilters>
<Filter Provider="Matrix42" Field="Queue" Enabled="false" Match="include" EmptyHandling="include">
<Value ID="2a7e8099-3d57-f011-1988-00155d320605" Name="HR" />
<Value ID="2a7a8099-3d47-f011-1988-00155d320505" Name="FM" />
</Filter>
<Filter Field="AssignmentGroup" Enabled="false" Match="include" EmptyHandling="exclude">
<Value ID="0a76de08-136c-764a-b410-5610e8076712" Name="HR Service Desk Agent" />
<Value ID="0b76de08-8668-a026-b410-5610e8076f95" Name="HR-Manager" />
<Value ID="0a76de08-d589-4c94-b410-5610e8076919" Name="HR-Spezialist" />
<Value ID="e87dde08-235c-04e5-b410-562b64035575" Name="Facility-Koordinator" />
</Filter>
<Filter Provider="Matrix42" Field="Workspace" Enabled="false" Match="include" EmptyHandling="exclude">
<Value ID="1676de08-27f3-550c-b410-5610e807aa02" Name="HR Service Management" />
<Value ID="ed7dde08-199e-c27b-b410-562b64037aa1" Name="Gebäudemanagement" />
</Filter>
</ActivityFilters>
<OverviewPollingPersonal Policy="Mandatory" Value="10" /> <OverviewPollingPersonal Policy="Mandatory" Value="10" />
<OverviewPollingRole Policy="Mandatory" Value="5" /> <OverviewPollingRole Policy="Mandatory" Value="5" />
</TicketConfiguration> </TicketConfiguration>

View File

@@ -82,15 +82,11 @@
</Azure-Tenant> </Azure-Tenant>
<!--<Azure-Tenant Domain="sd0024.onmicrosoft.com" TenantID="07362148-410e-4636-a9cb-795fba1a5452" Credential="AzureAccess_Dieter"/>--> <!--<Azure-Tenant Domain="sd0024.onmicrosoft.com" TenantID="07362148-410e-4636-a9cb-795fba1a5452" Credential="AzureAccess_Dieter"/>-->
</Azure-AD> </Azure-AD>
<Matrix42-WPM Server="srvwsm001.imagoverum.com" Credential="M42API-Demo-26-1" ClosedTicketHistory="9999" DisplayName="M42 Demo server (imagoverum)" ApiTokenLifetime ="30 days" ActivityQueueFilter="showAll"> <Matrix42-WPM Server="srvwsm001.imagoverum.com" Credential="M42API-Demo-26-1" ClosedTicketHistory="9999" DisplayName="M42 Demo server (imagoverum)" ApiTokenLifetime ="30 days">
<Matrix42-Ticket DisableAutomaticTimeTracking="1" <Matrix42-Ticket DisableAutomaticTimeTracking="1"
ShowDocumentCaseDialog="ifRequired"/> ShowDocumentCaseDialog="ifRequired"/>
<DisplayName Lang="DE">M42 Demo Server (Imagoverum)</DisplayName> <DisplayName Lang="DE">M42 Demo Server (Imagoverum)</DisplayName>
<Queues> </Matrix42-WPM>
<Queue QueueName="HR" QueueID="2a7e8099-3d57-f011-1988-00155d320605" />
<Queue QueueName="FM" QueueID="2a7a8099-3d47-f011-1988-00155d320505" />
</Queues>
</Matrix42-WPM>
<!--<Matrix42-WPM Server="m42server.imagoverum.com" Credential="M42API-Demo-Dieter" ClosedTicketHistory="9999"/>--> <!--<Matrix42-WPM Server="m42server.imagoverum.com" Credential="M42API-Demo-Dieter" ClosedTicketHistory="9999"/>-->
<Nexthink> <Nexthink>
<Scan-Timing ScanInterval="24:00" ScanOffset="00:10" Timezone="W. Europe Standard Time"/> <Scan-Timing ScanInterval="24:00" ScanOffset="00:10" Timezone="W. Europe Standard Time"/>

View File

@@ -39,7 +39,7 @@ namespace FasdWebService.Controllers
return Ok(new TicketOverviewCountsResult()); return Ok(new TicketOverviewCountsResult());
} }
var collector = WebApiApplication.Collector?.M42WpmCollector; var collector = WebApiApplication.Collector?.TicketActivityProvider;
if (collector == null) if (collector == null)
{ {
return Ok(new TicketOverviewCountsResult()); return Ok(new TicketOverviewCountsResult());
@@ -95,7 +95,7 @@ namespace FasdWebService.Controllers
return Ok(new List<cF4sdApiSearchResultRelation>()); return Ok(new List<cF4sdApiSearchResultRelation>());
} }
var collector = WebApiApplication.Collector?.M42WpmCollector; var collector = WebApiApplication.Collector?.TicketActivityProvider;
if (collector == null) if (collector == null)
{ {
return Ok(new List<cF4sdApiSearchResultRelation>()); return Ok(new List<cF4sdApiSearchResultRelation>());

View File

@@ -24,6 +24,22 @@
<TicketTypeProcessing Type="UnclassifiedTicket" Value="intern" /> <TicketTypeProcessing Type="UnclassifiedTicket" Value="intern" />
<TicketTypeProcessing Type="Incident" Value="intern" /> <TicketTypeProcessing Type="Incident" Value="intern" />
</TicketProcessing> </TicketProcessing>
<ActivityFilters>
<Filter Provider="Matrix42" Field="Queue" Enabled="false" Match="include" EmptyHandling="include">
<Value ID="2a7e8099-3d57-f011-1988-00155d320605" Name="HR" />
<Value ID="2a7a8099-3d47-f011-1988-00155d320505" Name="FM" />
</Filter>
<Filter Field="AssignmentGroup" Enabled="false" Match="include" EmptyHandling="exclude">
<Value ID="0a76de08-136c-764a-b410-5610e8076712" Name="HR Service Desk Agent" />
<Value ID="0b76de08-8668-a026-b410-5610e8076f95" Name="HR-Manager" />
<Value ID="0a76de08-d589-4c94-b410-5610e8076919" Name="HR-Spezialist" />
<Value ID="e87dde08-235c-04e5-b410-562b64035575" Name="Facility-Koordinator" />
</Filter>
<Filter Provider="Matrix42" Field="Workspace" Enabled="false" Match="include" EmptyHandling="exclude">
<Value ID="1676de08-27f3-550c-b410-5610e807aa02" Name="HR Service Management" />
<Value ID="ed7dde08-199e-c27b-b410-562b64037aa1" Name="Gebäudemanagement" />
</Filter>
</ActivityFilters>
<OverviewPollingPersonal Policy="Mandatory" Value="10" /> <OverviewPollingPersonal Policy="Mandatory" Value="10" />
<OverviewPollingRole Policy="Mandatory" Value="5" /> <OverviewPollingRole Policy="Mandatory" Value="5" />
</TicketConfiguration> </TicketConfiguration>

View File

@@ -132,6 +132,21 @@
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:simpleType name="ActivityFilterMatchEnum">
<xs:restriction base="xs:string">
<xs:enumeration value="include"/>
<xs:enumeration value="exclude"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="ActivityFilterEmptyHandlingEnum">
<xs:restriction base="xs:string">
<xs:enumeration value="include"/>
<xs:enumeration value="exclude"/>
<xs:enumeration value="only"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="TicketConfiguration"> <xs:element name="TicketConfiguration">
<xs:complexType> <xs:complexType>
<xs:all> <xs:all>
@@ -182,6 +197,30 @@
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:element name="ActivityFilters" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:sequence>
<xs:element name="Filter" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="Value" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="ID" type="xs:string" use="optional"/>
<xs:attribute name="Name" type="xs:string" use="optional"/>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="Provider" type="xs:string" use="optional"/>
<xs:attribute name="Field" type="xs:string" use="required"/>
<xs:attribute name="Enabled" type="xs:boolean" use="optional" default="false"/>
<xs:attribute name="Match" type="ActivityFilterMatchEnum" use="optional" default="exclude"/>
<xs:attribute name="EmptyHandling" type="ActivityFilterEmptyHandlingEnum" use="optional" default="include"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="TicketProcessing" minOccurs="0" maxOccurs="1"> <xs:element name="TicketProcessing" minOccurs="0" maxOccurs="1">
<xs:complexType> <xs:complexType>
<xs:sequence> <xs:sequence>

View File

@@ -82,15 +82,11 @@
</Azure-Tenant> </Azure-Tenant>
<!--<Azure-Tenant Domain="sd0024.onmicrosoft.com" TenantID="07362148-410e-4636-a9cb-795fba1a5452" Credential="AzureAccess_Dieter"/>--> <!--<Azure-Tenant Domain="sd0024.onmicrosoft.com" TenantID="07362148-410e-4636-a9cb-795fba1a5452" Credential="AzureAccess_Dieter"/>-->
</Azure-AD> </Azure-AD>
<Matrix42-WPM Server="srvwsm001.imagoverum.com" Credential="M42API-Demo-26-1" ClosedTicketHistory="9999" DisplayName="M42 Demo server (imagoverum)" ApiTokenLifetime ="30 days" ActivityQueueFilter="showAll"> <Matrix42-WPM Server="srvwsm001.imagoverum.com" Credential="M42API-Demo-26-1" ClosedTicketHistory="9999" DisplayName="M42 Demo server (imagoverum)" ApiTokenLifetime ="30 days">
<Matrix42-Ticket DisableAutomaticTimeTracking="1" <Matrix42-Ticket DisableAutomaticTimeTracking="1"
ShowDocumentCaseDialog="ifRequired"/> ShowDocumentCaseDialog="ifRequired"/>
<DisplayName Lang="DE">M42 Demo Server (Imagoverum)</DisplayName> <DisplayName Lang="DE">M42 Demo Server (Imagoverum)</DisplayName>
<Queues> </Matrix42-WPM>
<Queue QueueName="HR" QueueID="2a7e8099-3d57-f011-1988-00155d320605" />
<Queue QueueName="FM" QueueID="2a7a8099-3d47-f011-1988-00155d320505" />
</Queues>
</Matrix42-WPM>
<!--<Matrix42-WPM Server="m42server.imagoverum.com" Credential="M42API-Demo-Dieter" ClosedTicketHistory="9999"/>--> <!--<Matrix42-WPM Server="m42server.imagoverum.com" Credential="M42API-Demo-Dieter" ClosedTicketHistory="9999"/>-->
<Nexthink> <Nexthink>
<Scan-Timing ScanInterval="24:00" ScanOffset="00:10" Timezone="W. Europe Standard Time"/> <Scan-Timing ScanInterval="24:00" ScanOffset="00:10" Timezone="W. Europe Standard Time"/>