Add provider-aware activity filter API
This commit is contained in:
179
F4SDM42WebApi/ActivityFilterService.cs
Normal file
179
F4SDM42WebApi/ActivityFilterService.cs
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using C4IT.FASD.Base;
|
||||||
|
using C4IT.Logging;
|
||||||
|
|
||||||
|
using update4u.SPS.DataLayer;
|
||||||
|
|
||||||
|
using static C4IT.Logging.cLogManager;
|
||||||
|
|
||||||
|
namespace C4IT.F4SD
|
||||||
|
{
|
||||||
|
public partial class F4SDHelperService
|
||||||
|
{
|
||||||
|
private sealed class WorkspaceRoleCacheEntry
|
||||||
|
{
|
||||||
|
public DateTime ExpiresAtUtc { get; set; }
|
||||||
|
public List<Guid> RoleIds { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly object workspaceRoleCacheLock = new object();
|
||||||
|
private static readonly Dictionary<string, WorkspaceRoleCacheEntry> workspaceRoleCache =
|
||||||
|
new Dictionary<string, WorkspaceRoleCacheEntry>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
internal async Task<string> ApplyActivityFiltersAsync(string baseFilter, cActivityFilterPolicy policy)
|
||||||
|
{
|
||||||
|
var result = await ActivityFilterCompiler.CompileAsync(policy, ResolveWorkspaceRolesAsync);
|
||||||
|
if (!result.Success)
|
||||||
|
{
|
||||||
|
LogEntry($"Activity filter rejected (fail closed): {result.Error}", LogLevels.Error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.IsNullOrWhiteSpace(result.Clause)
|
||||||
|
? baseFilter
|
||||||
|
: $"({baseFilter}) AND ({result.Clause})";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal cTicketActivityFilterCapabilities GetActivityFilterCapabilities()
|
||||||
|
{
|
||||||
|
var capabilities = new cTicketActivityFilterCapabilities
|
||||||
|
{
|
||||||
|
Provider = ActivityFilterCompiler.ProviderName,
|
||||||
|
CommonFields = new List<string> { ActivityFilterCompiler.AssignmentGroupField },
|
||||||
|
ProviderFields = new List<string> { ActivityFilterCompiler.QueueField }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (SPSDataEngineSchemaReader.ClassGetIDFromName("ESMWorkspaceClassBase") != Guid.Empty)
|
||||||
|
capabilities.ProviderFields.Add(ActivityFilterCompiler.WorkspaceField);
|
||||||
|
|
||||||
|
return capabilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<ActivityFilterResolution> ResolveWorkspaceRolesAsync(cActivityFilter filter)
|
||||||
|
{
|
||||||
|
await Task.Delay(0);
|
||||||
|
var workspaceClassId = SPSDataEngineSchemaReader.ClassGetIDFromName("ESMWorkspaceClassBase");
|
||||||
|
if (workspaceClassId == Guid.Empty)
|
||||||
|
return WorkspaceFailure("ESMWorkspaceClassBase is not available.");
|
||||||
|
|
||||||
|
var roleIds = new HashSet<Guid>();
|
||||||
|
foreach (var value in filter?.Values ?? new List<cActivityFilterValue>())
|
||||||
|
{
|
||||||
|
var key = !string.IsNullOrWhiteSpace(value?.ID)
|
||||||
|
? "id:" + value.ID.Trim()
|
||||||
|
: "name:" + value?.Name?.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(key) || key.EndsWith(":", StringComparison.Ordinal))
|
||||||
|
return WorkspaceFailure("A Workspace filter value has neither ID nor Name.");
|
||||||
|
|
||||||
|
if (TryGetCachedWorkspaceRoles(key, out var cached))
|
||||||
|
{
|
||||||
|
roleIds.UnionWith(cached);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataTable table;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
table = LoadWorkspaceRoles(
|
||||||
|
workspaceClassId,
|
||||||
|
!string.IsNullOrWhiteSpace(value.ID)
|
||||||
|
? $"ID = '{Escape(value.ID.Trim())}'"
|
||||||
|
: $"Title = '{Escape(value.Name.Trim())}'");
|
||||||
|
|
||||||
|
// IDs are stable and therefore preferred. The name is kept as a
|
||||||
|
// portable fallback for configurations imported into another ESM.
|
||||||
|
if ((table?.Rows == null || table.Rows.Count == 0)
|
||||||
|
&& !string.IsNullOrWhiteSpace(value.ID)
|
||||||
|
&& !string.IsNullOrWhiteSpace(value.Name))
|
||||||
|
{
|
||||||
|
table = LoadWorkspaceRoles(
|
||||||
|
workspaceClassId,
|
||||||
|
$"Title = '{Escape(value.Name.Trim())}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
LogException(exception);
|
||||||
|
return WorkspaceFailure($"Workspace relation ESMWorkspaceClassBase.Roles could not be loaded for '{key}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (table?.Rows == null || table.Rows.Count == 0)
|
||||||
|
return WorkspaceFailure($"Workspace '{key}' was not found or has no resolvable Roles relation.");
|
||||||
|
|
||||||
|
var workspaceIds = table.Rows.Cast<DataRow>()
|
||||||
|
.Select(row => getGuidFromObject(row["WorkspaceID"]))
|
||||||
|
.Where(id => id != Guid.Empty)
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
if (workspaceIds.Count != 1)
|
||||||
|
return WorkspaceFailure($"Workspace '{key}' is not unique.");
|
||||||
|
|
||||||
|
var resolved = table.Rows.Cast<DataRow>()
|
||||||
|
.Select(row => getGuidFromObject(row["RoleID"]))
|
||||||
|
.Where(id => id != Guid.Empty)
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
if (resolved.Count == 0)
|
||||||
|
return WorkspaceFailure($"Workspace '{key}' contains no roles.");
|
||||||
|
|
||||||
|
SetCachedWorkspaceRoles(key, resolved);
|
||||||
|
roleIds.UnionWith(resolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ActivityFilterResolution
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
RoleIds = roleIds.ToList()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DataTable LoadWorkspaceRoles(Guid workspaceClassId, string where)
|
||||||
|
{
|
||||||
|
return FragmentRequestBase.SimpleLoad(
|
||||||
|
workspaceClassId,
|
||||||
|
"ID as WorkspaceID, Title as WorkspaceTitle, Roles.T(SPSSecurityClassRole).ID as RoleID",
|
||||||
|
where);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetCachedWorkspaceRoles(string key, out List<Guid> roleIds)
|
||||||
|
{
|
||||||
|
lock (workspaceRoleCacheLock)
|
||||||
|
{
|
||||||
|
if (workspaceRoleCache.TryGetValue(key, out var entry) && entry.ExpiresAtUtc > DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
roleIds = new List<Guid>(entry.RoleIds);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
roleIds = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetCachedWorkspaceRoles(string key, List<Guid> roleIds)
|
||||||
|
{
|
||||||
|
lock (workspaceRoleCacheLock)
|
||||||
|
{
|
||||||
|
workspaceRoleCache[key] = new WorkspaceRoleCacheEntry
|
||||||
|
{
|
||||||
|
ExpiresAtUtc = DateTime.UtcNow.AddMinutes(5),
|
||||||
|
RoleIds = new List<Guid>(roleIds)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ActivityFilterResolution WorkspaceFailure(string error)
|
||||||
|
{
|
||||||
|
return new ActivityFilterResolution
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Error = error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,10 +91,14 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="cM42LogEntry.cs" />
|
<Compile Include="cM42LogEntry.cs" />
|
||||||
|
<Compile Include="ActivityFilterService.cs" />
|
||||||
<Compile Include="F4SDM42DependencyRegistrator.cs" />
|
<Compile Include="F4SDM42DependencyRegistrator.cs" />
|
||||||
<Compile Include="F4SDHelperService.cs" />
|
<Compile Include="F4SDHelperService.cs" />
|
||||||
<Compile Include="F4SDHelperService.UserId.cs" />
|
<Compile Include="F4SDHelperService.UserId.cs" />
|
||||||
<Compile Include="F4SDM42WebApiController.cs" />
|
<Compile Include="F4SDM42WebApiController.cs" />
|
||||||
|
<Compile Include="..\Shared\ActivityFilterCompiler.cs">
|
||||||
|
<Link>Shared\ActivityFilterCompiler.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="..\SharedAssemblyInfo.cs">
|
<Compile Include="..\SharedAssemblyInfo.cs">
|
||||||
<Link>Properties\SharedAssemblyInfo.cs</Link>
|
<Link>Properties\SharedAssemblyInfo.cs</Link>
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ namespace C4IT.F4SD
|
|||||||
Guid userId,
|
Guid userId,
|
||||||
int hours,
|
int hours,
|
||||||
int queueoption,
|
int queueoption,
|
||||||
List<cApiM42TicketQueueInfo> queues)
|
List<cApiM42TicketQueueInfo> queues,
|
||||||
|
cActivityFilterPolicy filters = null)
|
||||||
{
|
{
|
||||||
var method = MethodBase.GetCurrentMethod();
|
var method = MethodBase.GetCurrentMethod();
|
||||||
LogMethodBegin(method);
|
LogMethodBegin(method);
|
||||||
@@ -33,6 +34,9 @@ namespace C4IT.F4SD
|
|||||||
return new List<cF4SDTicketSummary>();
|
return new List<cF4SDTicketSummary>();
|
||||||
|
|
||||||
var activityFilter = GetActivityFilter(userId, hours, ticketAndServiceRequestEnabled(), queueoption, queues);
|
var activityFilter = GetActivityFilter(userId, hours, ticketAndServiceRequestEnabled(), queueoption, queues);
|
||||||
|
activityFilter = await ApplyActivityFiltersAsync(activityFilter, filters);
|
||||||
|
if (string.IsNullOrWhiteSpace(activityFilter))
|
||||||
|
return new List<cF4SDTicketSummary>();
|
||||||
LogEntry($"Generating ticket list for userId '{userId}'. ASQL filter: {activityFilter}", LogLevels.Debug);
|
LogEntry($"Generating ticket list for userId '{userId}'. ASQL filter: {activityFilter}", LogLevels.Debug);
|
||||||
|
|
||||||
var activityTable = FragmentRequestBase.SimpleLoad(
|
var activityTable = FragmentRequestBase.SimpleLoad(
|
||||||
@@ -91,7 +95,8 @@ namespace C4IT.F4SD
|
|||||||
string scope,
|
string scope,
|
||||||
IEnumerable<string> keys,
|
IEnumerable<string> keys,
|
||||||
int queueoption,
|
int queueoption,
|
||||||
List<cApiM42TicketQueueInfo> queues)
|
List<cApiM42TicketQueueInfo> queues,
|
||||||
|
cActivityFilterPolicy filters = null)
|
||||||
{
|
{
|
||||||
var method = MethodBase.GetCurrentMethod();
|
var method = MethodBase.GetCurrentMethod();
|
||||||
LogMethodBegin(method);
|
LogMethodBegin(method);
|
||||||
@@ -106,10 +111,10 @@ namespace C4IT.F4SD
|
|||||||
if (normalizedKeys.Count == 0)
|
if (normalizedKeys.Count == 0)
|
||||||
normalizedKeys.AddRange(TicketOverviewKeys);
|
normalizedKeys.AddRange(TicketOverviewKeys);
|
||||||
|
|
||||||
var entries = await LoadTicketOverviewEntries(userId, useRoleScope, queueoption, queues);
|
var entries = await LoadTicketOverviewEntries(userId, useRoleScope, queueoption, queues, filters);
|
||||||
List<TicketOverviewEntry> unassignedEntries = null;
|
List<TicketOverviewEntry> unassignedEntries = null;
|
||||||
if (!useRoleScope && normalizedKeys.Any(IsUnassignedOverviewKey))
|
if (!useRoleScope && normalizedKeys.Any(IsUnassignedOverviewKey))
|
||||||
unassignedEntries = await LoadTicketOverviewUnassignedEntriesForPersonalScope(userId, queueoption, queues);
|
unassignedEntries = await LoadTicketOverviewUnassignedEntriesForPersonalScope(userId, queueoption, queues, filters);
|
||||||
|
|
||||||
var counts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
var counts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||||
foreach (var key in normalizedKeys)
|
foreach (var key in normalizedKeys)
|
||||||
@@ -138,10 +143,11 @@ namespace C4IT.F4SD
|
|||||||
IEnumerable<Guid> roleGuids,
|
IEnumerable<Guid> roleGuids,
|
||||||
IEnumerable<string> keys,
|
IEnumerable<string> keys,
|
||||||
int queueoption,
|
int queueoption,
|
||||||
List<cApiM42TicketQueueInfo> queues)
|
List<cApiM42TicketQueueInfo> queues,
|
||||||
|
cActivityFilterPolicy filters = null)
|
||||||
{
|
{
|
||||||
var roleIds = await ResolveTicketOverviewRoleIdsAsync(userId, roleGuids);
|
var roleIds = await ResolveTicketOverviewRoleIdsAsync(userId, roleGuids);
|
||||||
return await getTicketOverviewCountsByRoles(null, roleIds, keys, queueoption, queues);
|
return await getTicketOverviewCountsByRoles(null, roleIds, keys, queueoption, queues, filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<List<TicketOverviewRelationDto>> getTicketOverviewRelations(
|
internal async Task<List<TicketOverviewRelationDto>> getTicketOverviewRelations(
|
||||||
@@ -150,7 +156,8 @@ namespace C4IT.F4SD
|
|||||||
string key,
|
string key,
|
||||||
int count,
|
int count,
|
||||||
int queueoption,
|
int queueoption,
|
||||||
List<cApiM42TicketQueueInfo> queues)
|
List<cApiM42TicketQueueInfo> queues,
|
||||||
|
cActivityFilterPolicy filters = null)
|
||||||
{
|
{
|
||||||
var method = MethodBase.GetCurrentMethod();
|
var method = MethodBase.GetCurrentMethod();
|
||||||
LogMethodBegin(method);
|
LogMethodBegin(method);
|
||||||
@@ -161,8 +168,8 @@ namespace C4IT.F4SD
|
|||||||
|
|
||||||
var useRoleScope = string.Equals(scope, "role", StringComparison.OrdinalIgnoreCase);
|
var useRoleScope = string.Equals(scope, "role", StringComparison.OrdinalIgnoreCase);
|
||||||
var entries = !useRoleScope && IsUnassignedOverviewKey(key)
|
var entries = !useRoleScope && IsUnassignedOverviewKey(key)
|
||||||
? await LoadTicketOverviewUnassignedEntriesForPersonalScope(userId, queueoption, queues)
|
? await LoadTicketOverviewUnassignedEntriesForPersonalScope(userId, queueoption, queues, filters)
|
||||||
: await LoadTicketOverviewEntries(userId, useRoleScope, queueoption, queues);
|
: await LoadTicketOverviewEntries(userId, useRoleScope, queueoption, queues, filters);
|
||||||
|
|
||||||
var filtered = entries
|
var filtered = entries
|
||||||
.Where(entry => MatchesTicketOverviewKey(entry, key))
|
.Where(entry => MatchesTicketOverviewKey(entry, key))
|
||||||
@@ -212,25 +219,28 @@ namespace C4IT.F4SD
|
|||||||
Guid userId,
|
Guid userId,
|
||||||
bool useRoleScope,
|
bool useRoleScope,
|
||||||
int queueoption,
|
int queueoption,
|
||||||
List<cApiM42TicketQueueInfo> queues)
|
List<cApiM42TicketQueueInfo> queues,
|
||||||
|
cActivityFilterPolicy filters = null)
|
||||||
{
|
{
|
||||||
if (userId == Guid.Empty)
|
if (userId == Guid.Empty)
|
||||||
return new List<TicketOverviewEntry>();
|
return new List<TicketOverviewEntry>();
|
||||||
|
|
||||||
var filter = await BuildTicketOverviewFilterAsync(userId, useRoleScope, queueoption, queues);
|
var filter = await BuildTicketOverviewFilterAsync(userId, useRoleScope, queueoption, queues, filters);
|
||||||
return await LoadTicketOverviewEntriesByFilter(filter);
|
return await LoadTicketOverviewEntriesByFilter(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<TicketOverviewEntry>> LoadTicketOverviewUnassignedEntriesForPersonalScope(
|
private async Task<List<TicketOverviewEntry>> LoadTicketOverviewUnassignedEntriesForPersonalScope(
|
||||||
Guid userId,
|
Guid userId,
|
||||||
int queueoption,
|
int queueoption,
|
||||||
List<cApiM42TicketQueueInfo> queues)
|
List<cApiM42TicketQueueInfo> queues,
|
||||||
|
cActivityFilterPolicy filters = null)
|
||||||
{
|
{
|
||||||
if (userId == Guid.Empty)
|
if (userId == Guid.Empty)
|
||||||
return new List<TicketOverviewEntry>();
|
return new List<TicketOverviewEntry>();
|
||||||
|
|
||||||
var roleIds = await ResolveTicketOverviewRoleIdsAsync(userId, null);
|
var roleIds = await ResolveTicketOverviewRoleIdsAsync(userId, null);
|
||||||
var filter = BuildTicketOverviewFilterForRoleIds(roleIds, null, queueoption, queues);
|
var filter = BuildTicketOverviewFilterForRoleIds(roleIds, null, queueoption, queues);
|
||||||
|
filter = await ApplyActivityFiltersAsync(filter, filters);
|
||||||
if (string.IsNullOrWhiteSpace(filter))
|
if (string.IsNullOrWhiteSpace(filter))
|
||||||
return new List<TicketOverviewEntry>();
|
return new List<TicketOverviewEntry>();
|
||||||
|
|
||||||
@@ -241,14 +251,16 @@ namespace C4IT.F4SD
|
|||||||
Guid userId,
|
Guid userId,
|
||||||
bool useRoleScope,
|
bool useRoleScope,
|
||||||
int queueoption,
|
int queueoption,
|
||||||
List<cApiM42TicketQueueInfo> queues)
|
List<cApiM42TicketQueueInfo> queues,
|
||||||
|
cActivityFilterPolicy filters = null)
|
||||||
{
|
{
|
||||||
var filter = BuildTicketOverviewBaseFilter(queueoption, queues);
|
var filter = BuildTicketOverviewBaseFilter(queueoption, queues);
|
||||||
if (!useRoleScope)
|
if (!useRoleScope)
|
||||||
return filter + $" AND (Recipient = '{Escape(userId.ToString("D"))}')";
|
return await ApplyActivityFiltersAsync(filter + $" AND (Recipient = '{Escape(userId.ToString("D"))}')", filters);
|
||||||
|
|
||||||
var roleIds = await ResolveTicketOverviewRoleIdsAsync(userId, null);
|
var roleIds = await ResolveTicketOverviewRoleIdsAsync(userId, null);
|
||||||
return BuildTicketOverviewFilterForRoleIds(roleIds, filter);
|
filter = BuildTicketOverviewFilterForRoleIds(roleIds, filter);
|
||||||
|
return await ApplyActivityFiltersAsync(filter, filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<Guid>> ResolveTicketOverviewRoleIdsAsync(Guid userId, IEnumerable<Guid> roleGuids)
|
private async Task<List<Guid>> ResolveTicketOverviewRoleIdsAsync(Guid userId, IEnumerable<Guid> roleGuids)
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ namespace C4IT.F4SD
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal async Task<List<cF4SDTicketSummary>> getTicketListByUser(string userSid, int hours, int queueoption, List<cApiM42TicketQueueInfo> queues)
|
internal async Task<List<cF4SDTicketSummary>> getTicketListByUser(string userSid, int hours, int queueoption, List<cApiM42TicketQueueInfo> queues, cActivityFilterPolicy filters = null)
|
||||||
{
|
{
|
||||||
var CM = MethodBase.GetCurrentMethod();
|
var CM = MethodBase.GetCurrentMethod();
|
||||||
LogMethodBegin(CM);
|
LogMethodBegin(CM);
|
||||||
@@ -197,6 +197,9 @@ namespace C4IT.F4SD
|
|||||||
|
|
||||||
var tickets = new List<cF4SDTicketSummary>();
|
var tickets = new List<cF4SDTicketSummary>();
|
||||||
var activityFilter = GetActivityFilter(userSid, hours, ticketAndServiceRequestEnabled(), queueoption, queues);
|
var activityFilter = GetActivityFilter(userSid, hours, ticketAndServiceRequestEnabled(), queueoption, queues);
|
||||||
|
activityFilter = await ApplyActivityFiltersAsync(activityFilter, filters);
|
||||||
|
if (string.IsNullOrWhiteSpace(activityFilter))
|
||||||
|
return new List<cF4SDTicketSummary>();
|
||||||
LogEntry($"ASql Filter: {activityFilter}");
|
LogEntry($"ASql Filter: {activityFilter}");
|
||||||
|
|
||||||
var activityTable = FragmentRequestBase.SimpleLoad(SPSActivityClassBaseID,
|
var activityTable = FragmentRequestBase.SimpleLoad(SPSActivityClassBaseID,
|
||||||
@@ -379,7 +382,8 @@ namespace C4IT.F4SD
|
|||||||
string scope,
|
string scope,
|
||||||
IEnumerable<string> keys,
|
IEnumerable<string> keys,
|
||||||
int queueoption,
|
int queueoption,
|
||||||
List<cApiM42TicketQueueInfo> queues)
|
List<cApiM42TicketQueueInfo> queues,
|
||||||
|
cActivityFilterPolicy filters = null)
|
||||||
{
|
{
|
||||||
var CM = MethodBase.GetCurrentMethod();
|
var CM = MethodBase.GetCurrentMethod();
|
||||||
LogMethodBegin(CM);
|
LogMethodBegin(CM);
|
||||||
@@ -396,11 +400,11 @@ namespace C4IT.F4SD
|
|||||||
normalizedKeys.AddRange(TicketOverviewKeys);
|
normalizedKeys.AddRange(TicketOverviewKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
var entries = await LoadTicketOverviewEntries(sid, useRoleScope, queueoption, queues);
|
var entries = await LoadTicketOverviewEntries(sid, useRoleScope, queueoption, queues, filters);
|
||||||
List<TicketOverviewEntry> unassignedEntries = null;
|
List<TicketOverviewEntry> unassignedEntries = null;
|
||||||
if (!useRoleScope && normalizedKeys.Any(IsUnassignedOverviewKey))
|
if (!useRoleScope && normalizedKeys.Any(IsUnassignedOverviewKey))
|
||||||
{
|
{
|
||||||
unassignedEntries = await LoadTicketOverviewUnassignedEntriesForPersonalScope(sid, queueoption, queues);
|
unassignedEntries = await LoadTicketOverviewUnassignedEntriesForPersonalScope(sid, queueoption, queues, filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
var counts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
var counts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -435,7 +439,8 @@ namespace C4IT.F4SD
|
|||||||
IEnumerable<Guid> roleGuids,
|
IEnumerable<Guid> roleGuids,
|
||||||
IEnumerable<string> keys,
|
IEnumerable<string> keys,
|
||||||
int queueoption,
|
int queueoption,
|
||||||
List<cApiM42TicketQueueInfo> queues)
|
List<cApiM42TicketQueueInfo> queues,
|
||||||
|
cActivityFilterPolicy filters = null)
|
||||||
{
|
{
|
||||||
var CM = MethodBase.GetCurrentMethod();
|
var CM = MethodBase.GetCurrentMethod();
|
||||||
LogMethodBegin(CM);
|
LogMethodBegin(CM);
|
||||||
@@ -455,7 +460,7 @@ namespace C4IT.F4SD
|
|||||||
if (roleIds.Count == 0)
|
if (roleIds.Count == 0)
|
||||||
return new TicketOverviewCountsByRoleResult();
|
return new TicketOverviewCountsByRoleResult();
|
||||||
|
|
||||||
var entries = await LoadTicketOverviewEntriesByRoleIds(roleIds, queueoption, queues);
|
var entries = await LoadTicketOverviewEntriesByRoleIds(roleIds, queueoption, queues, filters);
|
||||||
var entriesByRole = entries
|
var entriesByRole = entries
|
||||||
.GroupBy(entry => entry.RecipientRoleId)
|
.GroupBy(entry => entry.RecipientRoleId)
|
||||||
.ToDictionary(group => group.Key, group => group.ToList());
|
.ToDictionary(group => group.Key, group => group.ToList());
|
||||||
@@ -499,7 +504,8 @@ namespace C4IT.F4SD
|
|||||||
string key,
|
string key,
|
||||||
int count,
|
int count,
|
||||||
int queueoption,
|
int queueoption,
|
||||||
List<cApiM42TicketQueueInfo> queues)
|
List<cApiM42TicketQueueInfo> queues,
|
||||||
|
cActivityFilterPolicy filters = null)
|
||||||
{
|
{
|
||||||
var CM = MethodBase.GetCurrentMethod();
|
var CM = MethodBase.GetCurrentMethod();
|
||||||
LogMethodBegin(CM);
|
LogMethodBegin(CM);
|
||||||
@@ -512,11 +518,11 @@ namespace C4IT.F4SD
|
|||||||
List<TicketOverviewEntry> entries;
|
List<TicketOverviewEntry> entries;
|
||||||
if (!useRoleScope && IsUnassignedOverviewKey(key))
|
if (!useRoleScope && IsUnassignedOverviewKey(key))
|
||||||
{
|
{
|
||||||
entries = await LoadTicketOverviewUnassignedEntriesForPersonalScope(sid, queueoption, queues);
|
entries = await LoadTicketOverviewUnassignedEntriesForPersonalScope(sid, queueoption, queues, filters);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
entries = await LoadTicketOverviewEntries(sid, useRoleScope, queueoption, queues);
|
entries = await LoadTicketOverviewEntries(sid, useRoleScope, queueoption, queues, filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
var filtered = entries
|
var filtered = entries
|
||||||
@@ -576,7 +582,8 @@ namespace C4IT.F4SD
|
|||||||
string sid,
|
string sid,
|
||||||
bool useRoleScope,
|
bool useRoleScope,
|
||||||
int queueoption,
|
int queueoption,
|
||||||
List<cApiM42TicketQueueInfo> queues)
|
List<cApiM42TicketQueueInfo> queues,
|
||||||
|
cActivityFilterPolicy filters = null)
|
||||||
{
|
{
|
||||||
var CM = MethodBase.GetCurrentMethod();
|
var CM = MethodBase.GetCurrentMethod();
|
||||||
LogMethodBegin(CM);
|
LogMethodBegin(CM);
|
||||||
@@ -587,7 +594,7 @@ namespace C4IT.F4SD
|
|||||||
if (string.IsNullOrWhiteSpace(sid))
|
if (string.IsNullOrWhiteSpace(sid))
|
||||||
return new List<TicketOverviewEntry>();
|
return new List<TicketOverviewEntry>();
|
||||||
|
|
||||||
var filter = await BuildTicketOverviewFilterAsync(sid, useRoleScope, queueoption, queues);
|
var filter = await BuildTicketOverviewFilterAsync(sid, useRoleScope, queueoption, queues, filters);
|
||||||
return await LoadTicketOverviewEntriesByFilter(filter);
|
return await LoadTicketOverviewEntriesByFilter(filter);
|
||||||
}
|
}
|
||||||
catch (Exception E)
|
catch (Exception E)
|
||||||
@@ -604,7 +611,8 @@ namespace C4IT.F4SD
|
|||||||
private async Task<List<TicketOverviewEntry>> LoadTicketOverviewUnassignedEntriesForPersonalScope(
|
private async Task<List<TicketOverviewEntry>> LoadTicketOverviewUnassignedEntriesForPersonalScope(
|
||||||
string sid,
|
string sid,
|
||||||
int queueoption,
|
int queueoption,
|
||||||
List<cApiM42TicketQueueInfo> queues)
|
List<cApiM42TicketQueueInfo> queues,
|
||||||
|
cActivityFilterPolicy filters = null)
|
||||||
{
|
{
|
||||||
var CM = MethodBase.GetCurrentMethod();
|
var CM = MethodBase.GetCurrentMethod();
|
||||||
LogMethodBegin(CM);
|
LogMethodBegin(CM);
|
||||||
@@ -617,6 +625,7 @@ namespace C4IT.F4SD
|
|||||||
|
|
||||||
var roleIds = await ResolveTicketOverviewRoleIdsAsync(sid, null);
|
var roleIds = await ResolveTicketOverviewRoleIdsAsync(sid, null);
|
||||||
var filter = BuildTicketOverviewFilterForRoleIds(roleIds, null, queueoption, queues);
|
var filter = BuildTicketOverviewFilterForRoleIds(roleIds, null, queueoption, queues);
|
||||||
|
filter = await ApplyActivityFiltersAsync(filter, filters);
|
||||||
if (string.IsNullOrWhiteSpace(filter))
|
if (string.IsNullOrWhiteSpace(filter))
|
||||||
return new List<TicketOverviewEntry>();
|
return new List<TicketOverviewEntry>();
|
||||||
|
|
||||||
@@ -637,7 +646,8 @@ namespace C4IT.F4SD
|
|||||||
private async Task<List<TicketOverviewEntry>> LoadTicketOverviewEntriesByRoleIds(
|
private async Task<List<TicketOverviewEntry>> LoadTicketOverviewEntriesByRoleIds(
|
||||||
IEnumerable<Guid> roleIds,
|
IEnumerable<Guid> roleIds,
|
||||||
int queueoption,
|
int queueoption,
|
||||||
List<cApiM42TicketQueueInfo> queues)
|
List<cApiM42TicketQueueInfo> queues,
|
||||||
|
cActivityFilterPolicy filters = null)
|
||||||
{
|
{
|
||||||
var CM = MethodBase.GetCurrentMethod();
|
var CM = MethodBase.GetCurrentMethod();
|
||||||
LogMethodBegin(CM);
|
LogMethodBegin(CM);
|
||||||
@@ -646,6 +656,7 @@ namespace C4IT.F4SD
|
|||||||
await Task.Delay(0);
|
await Task.Delay(0);
|
||||||
|
|
||||||
var filter = BuildTicketOverviewFilterForRoleIds(roleIds, null, queueoption, queues);
|
var filter = BuildTicketOverviewFilterForRoleIds(roleIds, null, queueoption, queues);
|
||||||
|
filter = await ApplyActivityFiltersAsync(filter, filters);
|
||||||
return await LoadTicketOverviewEntriesByFilter(filter);
|
return await LoadTicketOverviewEntriesByFilter(filter);
|
||||||
}
|
}
|
||||||
catch (Exception E)
|
catch (Exception E)
|
||||||
@@ -788,7 +799,8 @@ namespace C4IT.F4SD
|
|||||||
string sid,
|
string sid,
|
||||||
bool useRoleScope,
|
bool useRoleScope,
|
||||||
int queueoption,
|
int queueoption,
|
||||||
List<cApiM42TicketQueueInfo> queues)
|
List<cApiM42TicketQueueInfo> queues,
|
||||||
|
cActivityFilterPolicy filters = null)
|
||||||
{
|
{
|
||||||
var filter = BuildTicketOverviewBaseFilter(queueoption, queues);
|
var filter = BuildTicketOverviewBaseFilter(queueoption, queues);
|
||||||
|
|
||||||
@@ -796,11 +808,12 @@ namespace C4IT.F4SD
|
|||||||
{
|
{
|
||||||
var recipientFilter = $"Recipient.Accounts.T(SPSAccountClassAd).Sid = '{Escape(sid)}'";
|
var recipientFilter = $"Recipient.Accounts.T(SPSAccountClassAd).Sid = '{Escape(sid)}'";
|
||||||
filter += $" AND ({recipientFilter})";
|
filter += $" AND ({recipientFilter})";
|
||||||
return filter;
|
return await ApplyActivityFiltersAsync(filter, filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
var roleIds = await ResolveTicketOverviewRoleIdsAsync(sid, null);
|
var roleIds = await ResolveTicketOverviewRoleIdsAsync(sid, null);
|
||||||
return BuildTicketOverviewFilterForRoleIds(roleIds, filter);
|
filter = BuildTicketOverviewFilterForRoleIds(roleIds, filter);
|
||||||
|
return await ApplyActivityFiltersAsync(filter, filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string BuildTicketOverviewBaseFilter(int queueoption, List<cApiM42TicketQueueInfo> queues)
|
private string BuildTicketOverviewBaseFilter(int queueoption, List<cApiM42TicketQueueInfo> queues)
|
||||||
|
|||||||
@@ -154,6 +154,25 @@ namespace C4IT.F4SD
|
|||||||
ParseQueues(queues)) ?? new List<cF4SDTicketSummary>();
|
ParseQueues(queues)) ?? new List<cF4SDTicketSummary>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("v2/getTicketList"), HttpPost]
|
||||||
|
public async Task<List<cF4SDTicketSummary>> getTicketListV2([FromBody] cTicketActivityListRequest request)
|
||||||
|
{
|
||||||
|
if (request?.UserId != Guid.Empty)
|
||||||
|
return await _f4stHelperService.getTicketListByUser(
|
||||||
|
request.UserId,
|
||||||
|
request.Hours,
|
||||||
|
0,
|
||||||
|
new List<cApiM42TicketQueueInfo>(),
|
||||||
|
request.Filters) ?? new List<cF4SDTicketSummary>();
|
||||||
|
|
||||||
|
return await _f4stHelperService.getTicketListByUser(
|
||||||
|
request?.Sid ?? string.Empty,
|
||||||
|
request?.Hours ?? 0,
|
||||||
|
0,
|
||||||
|
new List<cApiM42TicketQueueInfo>(),
|
||||||
|
request?.Filters) ?? new List<cF4SDTicketSummary>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[Route("getTicketDetails"), HttpGet]
|
[Route("getTicketDetails"), HttpGet]
|
||||||
public async Task<cF4SDTicket> getTicketDetails([FromUri] Guid objectId)
|
public async Task<cF4SDTicket> getTicketDetails([FromUri] Guid objectId)
|
||||||
@@ -227,6 +246,28 @@ namespace C4IT.F4SD
|
|||||||
ParseQueues(queues)) ?? new F4SDHelperService.TicketOverviewCountsResult();
|
ParseQueues(queues)) ?? new F4SDHelperService.TicketOverviewCountsResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("v2/getTicketOverviewCounts"), HttpPost]
|
||||||
|
public async Task<F4SDHelperService.TicketOverviewCountsResult> getTicketOverviewCountsV2(
|
||||||
|
[FromBody] cTicketOverviewFilterRequest request)
|
||||||
|
{
|
||||||
|
if (request?.UserId != Guid.Empty)
|
||||||
|
return await _f4stHelperService.getTicketOverviewCounts(
|
||||||
|
request.UserId,
|
||||||
|
request.Scope,
|
||||||
|
request.Keys,
|
||||||
|
0,
|
||||||
|
new List<cApiM42TicketQueueInfo>(),
|
||||||
|
request.Filters) ?? new F4SDHelperService.TicketOverviewCountsResult();
|
||||||
|
|
||||||
|
return await _f4stHelperService.getTicketOverviewCounts(
|
||||||
|
request?.Sid ?? string.Empty,
|
||||||
|
request?.Scope ?? "personal",
|
||||||
|
request?.Keys ?? new List<string>(),
|
||||||
|
0,
|
||||||
|
new List<cApiM42TicketQueueInfo>(),
|
||||||
|
request?.Filters) ?? new F4SDHelperService.TicketOverviewCountsResult();
|
||||||
|
}
|
||||||
|
|
||||||
[Obsolete("Use getTicketOverviewCountsByRolesForUser with a Matrix42 user ID.")]
|
[Obsolete("Use getTicketOverviewCountsByRolesForUser with a Matrix42 user ID.")]
|
||||||
[Route("getTicketOverviewCountsByRoles"), HttpPost]
|
[Route("getTicketOverviewCountsByRoles"), HttpPost]
|
||||||
public async Task<F4SDHelperService.TicketOverviewCountsByRoleResult> getTicketOverviewCountsByRoles([FromBody] TicketOverviewCountsByRolesRequest request)
|
public async Task<F4SDHelperService.TicketOverviewCountsByRoleResult> getTicketOverviewCountsByRoles([FromBody] TicketOverviewCountsByRolesRequest request)
|
||||||
@@ -322,6 +363,36 @@ namespace C4IT.F4SD
|
|||||||
queueoption,
|
queueoption,
|
||||||
ParseQueues(queues)) ?? new List<F4SDHelperService.TicketOverviewRelationDto>();
|
ParseQueues(queues)) ?? new List<F4SDHelperService.TicketOverviewRelationDto>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("v2/getTicketOverviewRelations"), HttpPost]
|
||||||
|
public async Task<List<F4SDHelperService.TicketOverviewRelationDto>> getTicketOverviewRelationsV2(
|
||||||
|
[FromBody] cTicketOverviewFilterRequest request)
|
||||||
|
{
|
||||||
|
if (request?.UserId != Guid.Empty)
|
||||||
|
return await _f4stHelperService.getTicketOverviewRelations(
|
||||||
|
request.UserId,
|
||||||
|
request.Scope,
|
||||||
|
request.Key,
|
||||||
|
request.Count,
|
||||||
|
0,
|
||||||
|
new List<cApiM42TicketQueueInfo>(),
|
||||||
|
request.Filters) ?? new List<F4SDHelperService.TicketOverviewRelationDto>();
|
||||||
|
|
||||||
|
return await _f4stHelperService.getTicketOverviewRelations(
|
||||||
|
request?.Sid ?? string.Empty,
|
||||||
|
request?.Scope ?? "personal",
|
||||||
|
request?.Key ?? string.Empty,
|
||||||
|
request?.Count ?? 0,
|
||||||
|
0,
|
||||||
|
new List<cApiM42TicketQueueInfo>(),
|
||||||
|
request?.Filters) ?? new List<F4SDHelperService.TicketOverviewRelationDto>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("v2/getActivityFilterCapabilities"), HttpGet]
|
||||||
|
public cTicketActivityFilterCapabilities getActivityFilterCapabilitiesV2()
|
||||||
|
{
|
||||||
|
return _f4stHelperService.GetActivityFilterCapabilities();
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
[Route("updateActivitySolution/{objectId}"), HttpPost]
|
[Route("updateActivitySolution/{objectId}"), HttpPost]
|
||||||
public async Task<HttpResponseMessage> updateActivitySolution(Guid objectId, [FromBody] string SolutionHtml)
|
public async Task<HttpResponseMessage> updateActivitySolution(Guid objectId, [FromBody] string SolutionHtml)
|
||||||
|
|||||||
63
Legacy/F4SDM42WebApi/ActivityFilterService.cs
Normal file
63
Legacy/F4SDM42WebApi/ActivityFilterService.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using C4IT.FASD.Base;
|
||||||
|
using C4IT.Logging;
|
||||||
|
|
||||||
|
using static C4IT.Logging.cLogManager;
|
||||||
|
|
||||||
|
namespace C4IT.F4SD
|
||||||
|
{
|
||||||
|
public partial class F4SDHelperService
|
||||||
|
{
|
||||||
|
private static readonly AsyncLocal<cActivityFilterPolicy> currentActivityFilters =
|
||||||
|
new AsyncLocal<cActivityFilterPolicy>();
|
||||||
|
|
||||||
|
private sealed class ActivityFilterScope : IDisposable
|
||||||
|
{
|
||||||
|
private readonly cActivityFilterPolicy previous;
|
||||||
|
|
||||||
|
public ActivityFilterScope(cActivityFilterPolicy filters)
|
||||||
|
{
|
||||||
|
previous = currentActivityFilters.Value;
|
||||||
|
currentActivityFilters.Value = filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
currentActivityFilters.Value = previous;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal IDisposable BeginActivityFilterScope(cActivityFilterPolicy filters)
|
||||||
|
{
|
||||||
|
return new ActivityFilterScope(filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<string> ApplyActivityFiltersAsync(string baseFilter, cActivityFilterPolicy policy)
|
||||||
|
{
|
||||||
|
var result = await ActivityFilterCompiler.CompileAsync(policy ?? currentActivityFilters.Value, null);
|
||||||
|
if (!result.Success)
|
||||||
|
{
|
||||||
|
LogEntry($"Activity filter rejected (fail closed): {result.Error}", LogLevels.Error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.IsNullOrWhiteSpace(result.Clause)
|
||||||
|
? baseFilter
|
||||||
|
: $"({baseFilter}) AND ({result.Clause})";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal cTicketActivityFilterCapabilities GetActivityFilterCapabilities()
|
||||||
|
{
|
||||||
|
return new cTicketActivityFilterCapabilities
|
||||||
|
{
|
||||||
|
Provider = ActivityFilterCompiler.ProviderName,
|
||||||
|
CommonFields = new List<string> { ActivityFilterCompiler.AssignmentGroupField },
|
||||||
|
ProviderFields = new List<string> { ActivityFilterCompiler.QueueField }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,8 +54,10 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="cM42LogEntry.cs" />
|
<Compile Include="cM42LogEntry.cs" />
|
||||||
|
<Compile Include="ActivityFilterService.cs" />
|
||||||
<Compile Include="F4SDHelperService.cs" />
|
<Compile Include="F4SDHelperService.cs" />
|
||||||
<Compile Include="F4SDM42WebApiController.cs" />
|
<Compile Include="F4SDM42WebApiController.cs" />
|
||||||
|
<Compile Include="..\..\Shared\ActivityFilterCompiler.cs"><Link>Shared\ActivityFilterCompiler.cs</Link></Compile>
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="..\..\SharedAssemblyInfo.cs"><Link>Properties\SharedAssemblyInfo.cs</Link></Compile>
|
<Compile Include="..\..\SharedAssemblyInfo.cs"><Link>Properties\SharedAssemblyInfo.cs</Link></Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ using static C4IT.Logging.cLogManager;
|
|||||||
|
|
||||||
namespace C4IT.F4SD
|
namespace C4IT.F4SD
|
||||||
{
|
{
|
||||||
public class F4SDHelperService
|
public partial class F4SDHelperService
|
||||||
{
|
{
|
||||||
private static Guid SPSUserClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSUserClassBase");
|
private static Guid SPSUserClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSUserClassBase");
|
||||||
private static Guid SPSAccountClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSAccountClassBase");
|
private static Guid SPSAccountClassBaseID = SPSDataEngineSchemaReader.ClassGetIDFromName("SPSAccountClassBase");
|
||||||
@@ -166,6 +166,9 @@ namespace C4IT.F4SD
|
|||||||
|
|
||||||
var tickets = new List<cF4SDTicketSummary>();
|
var tickets = new List<cF4SDTicketSummary>();
|
||||||
var activityFilter = GetActivityFilter(userSid, hours, ticketAndServiceRequestEnabled(), queueoption, queues);
|
var activityFilter = GetActivityFilter(userSid, hours, ticketAndServiceRequestEnabled(), queueoption, queues);
|
||||||
|
activityFilter = await ApplyActivityFiltersAsync(activityFilter, null);
|
||||||
|
if (string.IsNullOrWhiteSpace(activityFilter))
|
||||||
|
return new List<cF4SDTicketSummary>();
|
||||||
LogEntry($"ASql Filter: {activityFilter}");
|
LogEntry($"ASql Filter: {activityFilter}");
|
||||||
|
|
||||||
var activityTable = FragmentRequestBase.SimpleLoad(SPSActivityClassBaseID,
|
var activityTable = FragmentRequestBase.SimpleLoad(SPSActivityClassBaseID,
|
||||||
@@ -279,6 +282,9 @@ namespace C4IT.F4SD
|
|||||||
|
|
||||||
var tickets = new List<cF4SDTicketSummary>();
|
var tickets = new List<cF4SDTicketSummary>();
|
||||||
var activityFilter = GetActivityFilter(userId, hours, ticketAndServiceRequestEnabled(), queueoption, queues);
|
var activityFilter = GetActivityFilter(userId, hours, ticketAndServiceRequestEnabled(), queueoption, queues);
|
||||||
|
activityFilter = await ApplyActivityFiltersAsync(activityFilter, null);
|
||||||
|
if (string.IsNullOrWhiteSpace(activityFilter))
|
||||||
|
return new List<cF4SDTicketSummary>();
|
||||||
LogEntry($"ASql Filter: {activityFilter}");
|
LogEntry($"ASql Filter: {activityFilter}");
|
||||||
|
|
||||||
var activityTable = FragmentRequestBase.SimpleLoad(SPSActivityClassBaseID,
|
var activityTable = FragmentRequestBase.SimpleLoad(SPSActivityClassBaseID,
|
||||||
@@ -982,6 +988,7 @@ namespace C4IT.F4SD
|
|||||||
|
|
||||||
var roleIds = await ResolveTicketOverviewRoleIdsAsync(sid, null);
|
var roleIds = await ResolveTicketOverviewRoleIdsAsync(sid, null);
|
||||||
var filter = BuildTicketOverviewFilterForRoleIds(roleIds, null, queueoption, queues);
|
var filter = BuildTicketOverviewFilterForRoleIds(roleIds, null, queueoption, queues);
|
||||||
|
filter = await ApplyActivityFiltersAsync(filter, null);
|
||||||
if (string.IsNullOrWhiteSpace(filter))
|
if (string.IsNullOrWhiteSpace(filter))
|
||||||
return new List<TicketOverviewEntry>();
|
return new List<TicketOverviewEntry>();
|
||||||
|
|
||||||
@@ -1015,6 +1022,7 @@ namespace C4IT.F4SD
|
|||||||
|
|
||||||
var roleIds = await ResolveTicketOverviewRoleIdsAsync(userId, null);
|
var roleIds = await ResolveTicketOverviewRoleIdsAsync(userId, null);
|
||||||
var filter = BuildTicketOverviewFilterForRoleIds(roleIds, null, queueoption, queues);
|
var filter = BuildTicketOverviewFilterForRoleIds(roleIds, null, queueoption, queues);
|
||||||
|
filter = await ApplyActivityFiltersAsync(filter, null);
|
||||||
if (string.IsNullOrWhiteSpace(filter))
|
if (string.IsNullOrWhiteSpace(filter))
|
||||||
return new List<TicketOverviewEntry>();
|
return new List<TicketOverviewEntry>();
|
||||||
|
|
||||||
@@ -1044,6 +1052,7 @@ namespace C4IT.F4SD
|
|||||||
await Task.Delay(0);
|
await Task.Delay(0);
|
||||||
|
|
||||||
var filter = BuildTicketOverviewFilterForRoleIds(roleIds, null, queueoption, queues);
|
var filter = BuildTicketOverviewFilterForRoleIds(roleIds, null, queueoption, queues);
|
||||||
|
filter = await ApplyActivityFiltersAsync(filter, null);
|
||||||
return await LoadTicketOverviewEntriesByFilter(filter);
|
return await LoadTicketOverviewEntriesByFilter(filter);
|
||||||
}
|
}
|
||||||
catch (Exception E)
|
catch (Exception E)
|
||||||
@@ -1195,11 +1204,12 @@ namespace C4IT.F4SD
|
|||||||
{
|
{
|
||||||
var recipientFilter = $"Recipient.Accounts.T(SPSAccountClassAd).Sid = '{Escape(sid)}'";
|
var recipientFilter = $"Recipient.Accounts.T(SPSAccountClassAd).Sid = '{Escape(sid)}'";
|
||||||
filter += $" AND ({recipientFilter})";
|
filter += $" AND ({recipientFilter})";
|
||||||
return filter;
|
return await ApplyActivityFiltersAsync(filter, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
var roleIds = await ResolveTicketOverviewRoleIdsAsync(sid, null);
|
var roleIds = await ResolveTicketOverviewRoleIdsAsync(sid, null);
|
||||||
return BuildTicketOverviewFilterForRoleIds(roleIds, filter);
|
filter = BuildTicketOverviewFilterForRoleIds(roleIds, filter);
|
||||||
|
return await ApplyActivityFiltersAsync(filter, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> BuildTicketOverviewFilterAsync(
|
private async Task<string> BuildTicketOverviewFilterAsync(
|
||||||
@@ -1214,11 +1224,12 @@ namespace C4IT.F4SD
|
|||||||
{
|
{
|
||||||
var recipientFilter = $"Recipient = '{Escape(userId.ToString("D"))}'";
|
var recipientFilter = $"Recipient = '{Escape(userId.ToString("D"))}'";
|
||||||
filter += $" AND ({recipientFilter})";
|
filter += $" AND ({recipientFilter})";
|
||||||
return filter;
|
return await ApplyActivityFiltersAsync(filter, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
var roleIds = await ResolveTicketOverviewRoleIdsAsync(userId, null);
|
var roleIds = await ResolveTicketOverviewRoleIdsAsync(userId, null);
|
||||||
return BuildTicketOverviewFilterForRoleIds(roleIds, filter);
|
filter = BuildTicketOverviewFilterForRoleIds(roleIds, filter);
|
||||||
|
return await ApplyActivityFiltersAsync(filter, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string BuildTicketOverviewBaseFilter(int queueoption, List<cApiM42TicketQueueInfo> queues)
|
private string BuildTicketOverviewBaseFilter(int queueoption, List<cApiM42TicketQueueInfo> queues)
|
||||||
|
|||||||
@@ -138,6 +138,26 @@ namespace C4IT.F4SD
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("v2/getTicketList"), HttpPost]
|
||||||
|
public async Task<List<cF4SDTicketSummary>> getTicketListV2([FromBody] cTicketActivityListRequest request)
|
||||||
|
{
|
||||||
|
using (_f4stHelperService.BeginActivityFilterScope(request?.Filters))
|
||||||
|
{
|
||||||
|
if (request?.UserId != Guid.Empty)
|
||||||
|
return await _f4stHelperService.getTicketListByUser(
|
||||||
|
request.UserId,
|
||||||
|
request.Hours,
|
||||||
|
0,
|
||||||
|
new List<cApiM42TicketQueueInfo>()) ?? new List<cF4SDTicketSummary>();
|
||||||
|
|
||||||
|
return await _f4stHelperService.getTicketListByUser(
|
||||||
|
request?.Sid ?? string.Empty,
|
||||||
|
request?.Hours ?? 0,
|
||||||
|
0,
|
||||||
|
new List<cApiM42TicketQueueInfo>()) ?? new List<cF4SDTicketSummary>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[Route("getTicketDetails"), HttpGet]
|
[Route("getTicketDetails"), HttpGet]
|
||||||
public async Task<F4SDHelperService.cF4SDTicket> getTicketDetails(Guid objectId)
|
public async Task<F4SDHelperService.cF4SDTicket> getTicketDetails(Guid objectId)
|
||||||
@@ -193,6 +213,29 @@ namespace C4IT.F4SD
|
|||||||
return await _f4stHelperService.getTicketOverviewCounts(userId, scope, parsedKeys, queueoption, decodedQueues);
|
return await _f4stHelperService.getTicketOverviewCounts(userId, scope, parsedKeys, queueoption, decodedQueues);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("v2/getTicketOverviewCounts"), HttpPost]
|
||||||
|
public async Task<F4SDHelperService.TicketOverviewCountsResult> getTicketOverviewCountsV2(
|
||||||
|
[FromBody] cTicketOverviewFilterRequest request)
|
||||||
|
{
|
||||||
|
using (_f4stHelperService.BeginActivityFilterScope(request?.Filters))
|
||||||
|
{
|
||||||
|
if (request?.UserId != Guid.Empty)
|
||||||
|
return await _f4stHelperService.getTicketOverviewCounts(
|
||||||
|
request.UserId,
|
||||||
|
request.Scope,
|
||||||
|
request.Keys,
|
||||||
|
0,
|
||||||
|
new List<cApiM42TicketQueueInfo>()) ?? new F4SDHelperService.TicketOverviewCountsResult();
|
||||||
|
|
||||||
|
return await _f4stHelperService.getTicketOverviewCounts(
|
||||||
|
request?.Sid ?? string.Empty,
|
||||||
|
request?.Scope ?? "personal",
|
||||||
|
request?.Keys ?? new List<string>(),
|
||||||
|
0,
|
||||||
|
new List<cApiM42TicketQueueInfo>()) ?? new F4SDHelperService.TicketOverviewCountsResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Obsolete]
|
[Obsolete]
|
||||||
[Route("getTicketOverviewCountsByRoles"), HttpPost]
|
[Route("getTicketOverviewCountsByRoles"), HttpPost]
|
||||||
public async Task<F4SDHelperService.TicketOverviewCountsByRoleResult> getTicketOverviewCountsByRoles([FromBody] TicketOverviewCountsByRolesRequest request)
|
public async Task<F4SDHelperService.TicketOverviewCountsByRoleResult> getTicketOverviewCountsByRoles([FromBody] TicketOverviewCountsByRolesRequest request)
|
||||||
@@ -269,6 +312,37 @@ namespace C4IT.F4SD
|
|||||||
return await _f4stHelperService.getTicketOverviewRelations(userId, scope, key, count, queueoption, decodedQueues);
|
return await _f4stHelperService.getTicketOverviewRelations(userId, scope, key, count, queueoption, decodedQueues);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("v2/getTicketOverviewRelations"), HttpPost]
|
||||||
|
public async Task<List<F4SDHelperService.TicketOverviewRelationDto>> getTicketOverviewRelationsV2(
|
||||||
|
[FromBody] cTicketOverviewFilterRequest request)
|
||||||
|
{
|
||||||
|
using (_f4stHelperService.BeginActivityFilterScope(request?.Filters))
|
||||||
|
{
|
||||||
|
if (request?.UserId != Guid.Empty)
|
||||||
|
return await _f4stHelperService.getTicketOverviewRelations(
|
||||||
|
request.UserId,
|
||||||
|
request.Scope,
|
||||||
|
request.Key,
|
||||||
|
request.Count,
|
||||||
|
0,
|
||||||
|
new List<cApiM42TicketQueueInfo>()) ?? new List<F4SDHelperService.TicketOverviewRelationDto>();
|
||||||
|
|
||||||
|
return await _f4stHelperService.getTicketOverviewRelations(
|
||||||
|
request?.Sid ?? string.Empty,
|
||||||
|
request?.Scope ?? "personal",
|
||||||
|
request?.Key ?? string.Empty,
|
||||||
|
request?.Count ?? 0,
|
||||||
|
0,
|
||||||
|
new List<cApiM42TicketQueueInfo>()) ?? new List<F4SDHelperService.TicketOverviewRelationDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("v2/getActivityFilterCapabilities"), HttpGet]
|
||||||
|
public cTicketActivityFilterCapabilities getActivityFilterCapabilitiesV2()
|
||||||
|
{
|
||||||
|
return _f4stHelperService.GetActivityFilterCapabilities();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
[Route("updateActivitySolution/{objectId}"), HttpPost]
|
[Route("updateActivitySolution/{objectId}"), HttpPost]
|
||||||
|
|||||||
151
Shared/ActivityFilterCompiler.cs
Normal file
151
Shared/ActivityFilterCompiler.cs
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using C4IT.FASD.Base;
|
||||||
|
|
||||||
|
namespace C4IT.F4SD
|
||||||
|
{
|
||||||
|
internal sealed class ActivityFilterResolution
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Error { get; set; }
|
||||||
|
public List<Guid> RoleIds { get; set; } = new List<Guid>();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class ActivityFilterCompilationResult
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Clause { get; set; }
|
||||||
|
public string Error { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class ActivityFilterCompiler
|
||||||
|
{
|
||||||
|
internal const string ProviderName = "Matrix42";
|
||||||
|
internal const string AssignmentGroupField = "AssignmentGroup";
|
||||||
|
internal const string QueueField = "Queue";
|
||||||
|
internal const string WorkspaceField = "Workspace";
|
||||||
|
|
||||||
|
internal static async Task<ActivityFilterCompilationResult> CompileAsync(
|
||||||
|
cActivityFilterPolicy policy,
|
||||||
|
Func<cActivityFilter, Task<ActivityFilterResolution>> workspaceResolver)
|
||||||
|
{
|
||||||
|
var clauses = new List<string>();
|
||||||
|
foreach (var filter in policy?.Filters ?? new List<cActivityFilter>())
|
||||||
|
{
|
||||||
|
if (filter == null || !filter.Enabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var providerSpecific = !string.IsNullOrWhiteSpace(filter.Provider);
|
||||||
|
if (providerSpecific && !string.Equals(filter.Provider, ProviderName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return Fail($"Unsupported activity filter provider '{filter.Provider}'.");
|
||||||
|
|
||||||
|
var field = filter.Field?.Trim();
|
||||||
|
string emptyClause;
|
||||||
|
string valueClause;
|
||||||
|
|
||||||
|
if (!providerSpecific && string.Equals(field, AssignmentGroupField, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
emptyClause = "RecipientRole IS NULL";
|
||||||
|
valueClause = BuildValueClause("RecipientRole.T(SPSSecurityClassRole).ID", "RecipientRole.T(SPSSecurityClassRole).Name", filter.Values);
|
||||||
|
}
|
||||||
|
else if (providerSpecific && string.Equals(field, QueueField, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
emptyClause = "Queue IS NULL";
|
||||||
|
valueClause = BuildValueClause("Queue.ID", "Queue.Name", filter.Values);
|
||||||
|
}
|
||||||
|
else if (providerSpecific && string.Equals(field, WorkspaceField, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (workspaceResolver == null)
|
||||||
|
return Fail("The Matrix42 Workspace filter is not supported by this product version.");
|
||||||
|
|
||||||
|
var resolution = await workspaceResolver(filter);
|
||||||
|
if (resolution?.Success != true)
|
||||||
|
return Fail(resolution?.Error ?? "The Matrix42 Workspace filter could not be resolved.");
|
||||||
|
|
||||||
|
emptyClause = "RecipientRole IS NULL";
|
||||||
|
valueClause = BuildGuidClause("RecipientRole.T(SPSSecurityClassRole).ID", resolution.RoleIds);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Fail($"Unsupported activity filter field '{filter.Provider}:{field}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.EmptyHandling == enumActivityFilterEmptyHandling.only)
|
||||||
|
{
|
||||||
|
clauses.Add($"({emptyClause})");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(valueClause))
|
||||||
|
return Fail($"Activity filter '{filter.Provider}:{field}' has no valid values.");
|
||||||
|
|
||||||
|
var matchClause = filter.Match == enumActivityFilterMatch.exclude
|
||||||
|
? $"NOT ({valueClause})"
|
||||||
|
: $"({valueClause})";
|
||||||
|
|
||||||
|
clauses.Add(filter.EmptyHandling == enumActivityFilterEmptyHandling.include
|
||||||
|
? $"(({emptyClause}) OR ({matchClause}))"
|
||||||
|
: $"((NOT ({emptyClause})) AND ({matchClause}))");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ActivityFilterCompilationResult
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Clause = string.Join(" AND ", clauses)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildValueClause(string idField, string nameField, IEnumerable<cActivityFilterValue> values)
|
||||||
|
{
|
||||||
|
var items = (values ?? Enumerable.Empty<cActivityFilterValue>())
|
||||||
|
.Where(value => value != null)
|
||||||
|
.ToList();
|
||||||
|
var ids = items
|
||||||
|
.Select(value => value.ID?.Trim())
|
||||||
|
.Where(value => !string.IsNullOrWhiteSpace(value))
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Select(Quote)
|
||||||
|
.ToList();
|
||||||
|
var names = items
|
||||||
|
.Select(value => value.Name?.Trim())
|
||||||
|
.Where(value => !string.IsNullOrWhiteSpace(value))
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Select(Quote)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var clauses = new List<string>();
|
||||||
|
if (ids.Count > 0)
|
||||||
|
clauses.Add($"{idField} IN ({string.Join(", ", ids)})");
|
||||||
|
if (names.Count > 0)
|
||||||
|
clauses.Add($"{nameField} IN ({string.Join(", ", names)})");
|
||||||
|
return string.Join(" OR ", clauses);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildGuidClause(string field, IEnumerable<Guid> values)
|
||||||
|
{
|
||||||
|
var ids = (values ?? Enumerable.Empty<Guid>())
|
||||||
|
.Where(value => value != Guid.Empty)
|
||||||
|
.Distinct()
|
||||||
|
.Select(value => Quote(value.ToString("D")))
|
||||||
|
.ToList();
|
||||||
|
return ids.Count == 0 ? null : $"{field} IN ({string.Join(", ", ids)})";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Quote(string value)
|
||||||
|
{
|
||||||
|
return $"'{(value ?? string.Empty).Replace("'", "''")}'";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ActivityFilterCompilationResult Fail(string error)
|
||||||
|
{
|
||||||
|
return new ActivityFilterCompilationResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Error = error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1022,6 +1022,80 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "06 Activity Filter APIs v2",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "get activity filter capabilities",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [{ "key": "Accept", "value": "application/json" }],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/c4itf4sdwebapi/v2/getActivityFilterCapabilities",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "c4itf4sdwebapi", "v2", "getActivityFilterCapabilities"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "get filtered ticket list",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{ "key": "Accept", "value": "application/json" },
|
||||||
|
{ "key": "Content-Type", "value": "application/json" }
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\r\n \"Sid\": \"{{sid}}\",\r\n \"Hours\": {{hours}},\r\n \"Filters\": {\r\n \"Filters\": [\r\n {\r\n \"Provider\": \"Matrix42\",\r\n \"Field\": \"Queue\",\r\n \"Enabled\": true,\r\n \"Match\": \"include\",\r\n \"EmptyHandling\": \"exclude\",\r\n \"Values\": [{ \"Name\": \"{{queueName}}\" }]\r\n }\r\n ]\r\n }\r\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/c4itf4sdwebapi/v2/getTicketList",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "c4itf4sdwebapi", "v2", "getTicketList"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "get filtered ticket overview counts",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{ "key": "Accept", "value": "application/json" },
|
||||||
|
{ "key": "Content-Type", "value": "application/json" }
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\r\n \"UserId\": \"{{userId}}\",\r\n \"Scope\": \"{{scope}}\",\r\n \"Keys\": [\"TicketsNew\", \"TicketsActive\"],\r\n \"Filters\": { \"Filters\": [] }\r\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/c4itf4sdwebapi/v2/getTicketOverviewCounts",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "c4itf4sdwebapi", "v2", "getTicketOverviewCounts"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "get filtered ticket overview relations",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{ "key": "Accept", "value": "application/json" },
|
||||||
|
{ "key": "Content-Type", "value": "application/json" }
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\r\n \"UserId\": \"{{userId}}\",\r\n \"Scope\": \"{{scope}}\",\r\n \"Key\": \"{{overviewKey}}\",\r\n \"Count\": {{overviewRelationExpectedCount}},\r\n \"Filters\": { \"Filters\": [] }\r\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/api/c4itf4sdwebapi/v2/getTicketOverviewRelations",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["api", "c4itf4sdwebapi", "v2", "getTicketOverviewRelations"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user