refactor expression caching; add document fields

This commit is contained in:
Gary Sharp
2023-04-16 14:02:35 +10:00
parent d75663a219
commit cfe4c4b912
15 changed files with 182 additions and 144 deletions
@@ -6,12 +6,9 @@ using Disco.Services.Documents;
using Disco.Services.Documents.ManagedGroups;
using Disco.Services.Expressions;
using Disco.Services.Interop.ActiveDirectory;
using Disco.Services.Tasks;
using iTextSharp.text.pdf;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Entity;
using System.Drawing;
using System.IO;
@@ -21,41 +18,58 @@ namespace Disco.BI.Extensions
{
public static class DocumentTemplateExtensions
{
internal const string CacheTemplate = "DocumentTemplate_{0}";
public static ConcurrentDictionary<string, Expression> PdfExpressionsFromCache(this DocumentTemplate dt, DiscoDataContext Database)
private static Tuple<Dictionary<string, Expression>, List<DocumentField>> CreateExpressions(DocumentTemplate dt, DiscoDataContext database)
{
string cacheModuleKey = string.Format(CacheTemplate, dt.Id);
var module = ExpressionCache.GetModule(cacheModuleKey);
if (module == null)
Dictionary<string, Expression> expressions = new Dictionary<string, Expression>();
List<DocumentField> fields = new List<DocumentField>();
string templateFilename = dt.RepositoryFilename(database);
PdfReader pdfReader = new PdfReader(templateFilename);
int pdfFieldOrdinal = 0;
foreach (string pdfFieldKey in pdfReader.AcroFields.Fields.Keys)
{
// Cache
string templateFilename = dt.RepositoryFilename(Database);
PdfReader pdfReader = new PdfReader(templateFilename);
int pdfFieldOrdinal = 0;
foreach (string pdfFieldKey in pdfReader.AcroFields.Fields.Keys)
var pdfField = pdfReader.AcroFields.Fields[pdfFieldKey];
var pdfFieldPositions = pdfReader.AcroFields.GetFieldPositions(pdfFieldKey);
var pdfFieldFlags = pdfField.GetMerged(0).GetAsNumber(PdfName.FF)?.IntValue ?? 0;
var isRequired = (pdfFieldFlags & 2) == 2;
var isReadOnly = (pdfFieldFlags & 1) == 1;
var pdfFieldValue = pdfReader.AcroFields.GetField(pdfFieldKey);
var pdfFieldPosition = default(RectangleF?);
if (pdfFieldPositions != null && pdfFieldPositions.Count > 0)
{
var pdfField = pdfReader.AcroFields.Fields[pdfFieldKey];
var pdfFieldPositions = pdfReader.AcroFields.GetFieldPositions(pdfFieldKey);
var pdfFieldFlags = pdfField.GetMerged(0).GetAsNumber(PdfName.FF)?.IntValue ?? 0;
var isRequired = (pdfFieldFlags & 2) == 2;
var isReadOnly = (pdfFieldFlags & 1) == 1;
var pdfFieldValue = pdfReader.AcroFields.GetField(pdfFieldKey);
var pdfFieldPosition = default(RectangleF?);
if (pdfFieldPositions != null && pdfFieldPositions.Count > 0)
{
var position = pdfFieldPositions.First().position;
pdfFieldPosition = new RectangleF(position.Left, position.Top, position.Width, position.Height);
}
ExpressionCache.SetValue(cacheModuleKey, pdfFieldKey, Expression.Tokenize(pdfFieldKey, pdfFieldValue, pdfFieldOrdinal, isRequired, isReadOnly, pdfFieldPosition));
pdfFieldOrdinal++;
var position = pdfFieldPositions.First().position;
pdfFieldPosition = new RectangleF(position.Left, position.Top, position.Width, position.Height);
}
pdfReader.Close();
module = ExpressionCache.GetModule(cacheModuleKey, true);
var fieldTypeId = pdfReader.AcroFields.GetFieldType(pdfFieldKey);
var fieldType = DocumentFieldType.None;
if (fieldTypeId <= 8 && fieldTypeId > 0)
fieldType = (DocumentFieldType)fieldTypeId;
var fixedValues = default(List<string>);
if (fieldType == DocumentFieldType.RadioButton || fieldType == DocumentFieldType.Checkbox)
{
fixedValues = pdfReader.AcroFields.GetAppearanceStates(pdfFieldKey).ToList();
}
expressions[pdfFieldKey] = Expression.Tokenize(pdfFieldKey, pdfFieldValue, pdfFieldOrdinal, isRequired, isReadOnly, pdfFieldPosition);
fields.Add(new DocumentField(pdfFieldKey, pdfFieldValue, pdfFieldOrdinal, fieldType, isRequired, isReadOnly, fixedValues));
pdfFieldOrdinal++;
}
return module;
pdfReader.Close();
return Tuple.Create(expressions, fields);
}
public static Dictionary<string, Expression> PdfExpressionsFromCache(this DocumentTemplate dt, DiscoDataContext Database)
{
return ExpressionCache.GetOrCreateExpressions(dt, () => CreateExpressions(dt, Database));
}
public static List<DocumentField> PdfFieldsFromCache(this DocumentTemplate dt, DiscoDataContext Database)
{
return ExpressionCache.GetOrCreateFields(dt, () => CreateExpressions(dt, Database));
}
public static List<Expression> ExtractPdfExpressions(this DocumentTemplate dt, DiscoDataContext Database)
+1 -1
View File
@@ -214,7 +214,7 @@ namespace Disco.BI.Interop.Pdf
if (dt.FlattenForm)
FlattenFields = true;
ConcurrentDictionary<string, Expression> expressionCache = dt.PdfExpressionsFromCache(Database);
var expressionCache = dt.PdfExpressionsFromCache(Database);
string templateFilename = dt.RepositoryFilename(Database);
PdfReader pdfReader = new PdfReader(templateFilename);
+2
View File
@@ -68,6 +68,8 @@
<Compile Include="Services\Devices\Exporting\DeviceExportFieldMetadata.cs" />
<Compile Include="Services\Devices\Importing\IDeviceImportColumn.cs" />
<Compile Include="Services\Devices\Importing\IDeviceImportDataReader.cs" />
<Compile Include="Services\Documents\DocumentField.cs" />
<Compile Include="Services\Documents\DocumentFieldType.cs" />
<Compile Include="Services\Documents\DocumentTemplatePackage.cs" />
<Compile Include="Services\Documents\OnImportUserFlagRule.cs" />
<Compile Include="Services\Jobs\LocationModes.cs" />
@@ -0,0 +1,28 @@
using System.Collections.Generic;
namespace Disco.Models.Services.Documents
{
public class DocumentField
{
public string Name { get; }
public string Value { get; }
public int Ordinal { get; }
public DocumentFieldType Type { get; }
public bool IsRequired { get; }
public bool IsReadOnly { get; }
public List<string> FixedValues { get; }
public DocumentField(string name, string value, int ordinal, DocumentFieldType type, bool isRequired, bool isReadOnly, List<string> fixedValues)
{
Name = name;
Value = value;
Ordinal = ordinal;
Type = type;
IsRequired = isRequired;
IsReadOnly = isReadOnly;
FixedValues = fixedValues;
}
}
}
@@ -0,0 +1,14 @@
namespace Disco.Models.Services.Documents
{
public enum DocumentFieldType
{
None = 0,
PushButton = 1,
Checkbox = 2,
RadioButton = 3,
Text = 4,
List = 5,
Combo = 6,
Signature = 7,
}
}
+2 -6
View File
@@ -21,12 +21,8 @@ namespace Disco.Services
var deviceProfile = device.DeviceProfile;
Expression computerNameTemplateExpression = null;
computerNameTemplateExpression = ExpressionCache.GetValue(DeviceProfileExtensions.ComputerNameExpressionCacheModule, deviceProfile.Id.ToString(), () =>
{
// Removed 2012-06-14 G# - Properties moved to DeviceProfile model & DB Migrated in DBv3.
//return Expressions.Expression.TokenizeSingleDynamic(null, deviceProfile.Configuration(context).ComputerNameTemplate, 0);
return Expression.TokenizeSingleDynamic(null, deviceProfile.ComputerNameTemplate, 0);
});
computerNameTemplateExpression = ExpressionCache.GetOrCreateSingleExpressions(string.Format(DeviceProfileExtensions.ComputerNameExpressionCacheTemplate, deviceProfile.Id),
() => Expression.TokenizeSingleDynamic(null, deviceProfile.ComputerNameTemplate, 0));
var evaluatorVariables = Expression.StandardVariables(null, Database, UserService.CurrentUser, DateTime.Now, null, device);
string rendered;
try
@@ -18,11 +18,11 @@ namespace Disco.Services
{
public static class DeviceProfileExtensions
{
public const string ComputerNameExpressionCacheModule = "ComputerNameTemplate";
public const string ComputerNameExpressionCacheTemplate = "ComputerNameTemplate_{0}";
public static void ComputerNameInvalidateCache(this DeviceProfile deviceProfile)
{
ExpressionCache.InvalidateKey(ComputerNameExpressionCacheModule, deviceProfile.Id.ToString());
ExpressionCache.InvalidateSingleCache(string.Format(ComputerNameExpressionCacheTemplate, deviceProfile.Id));
}
public static OrganisationAddress DefaultOrganisationAddressDetails(this DeviceProfile deviceProfile, DiscoDataContext Database)
@@ -8,8 +8,9 @@ namespace Disco.Services
{
public static string RepositoryFilename(this DocumentTemplate dt, DiscoDataContext Database)
{
return Path.Combine(DataStore.CreateLocation(Database, "DocumentTemplates"), string.Format("{0}.pdf", dt.Id));
return Path.Combine(DataStore.CreateLocation(Database, "DocumentTemplates"), $"{dt.Id}.pdf");
}
public static string SavePdfTemplate(this DocumentTemplate dt, DiscoDataContext Database, Stream TemplateFile)
{
string filePath = dt.RepositoryFilename(Database);
@@ -17,7 +18,7 @@ namespace Disco.Services
{
TemplateFile.CopyTo(fs);
}
Expressions.ExpressionCache.InvalidModule(string.Format(DocumentTemplateExpressionExtensions.CacheTemplate, dt.Id));
Expressions.ExpressionCache.InvalidateCache(dt);
return filePath;
}
}
@@ -10,19 +10,17 @@ namespace Disco.Services
{
public static class DocumentTemplateExpressionExtensions
{
internal const string CacheTemplate = "DocumentTemplate_{0}";
public static Expression FilterExpressionFromCache(this DocumentTemplate dt)
{
return ExpressionCache.GetValue("DocumentTemplate_FilterExpression", dt.Id, () => { return Expression.TokenizeSingleDynamic(null, dt.FilterExpression, 0); });
return ExpressionCache.GetOrCreateSingleExpressions($"DocumentTemplate_FilterExpression_{dt.Id}", () => Expression.TokenizeSingleDynamic(null, dt.FilterExpression, 0));
}
public static void FilterExpressionInvalidateCache(this DocumentTemplate dt)
{
ExpressionCache.InvalidateKey("DocumentTemplate_FilterExpression", dt.Id);
ExpressionCache.InvalidateSingleCache($"DocumentTemplate_FilterExpression_{dt.Id}");
}
public static bool FilterExpressionMatches(this DocumentTemplate dt, IAttachmentTarget Data, DiscoDataContext Database, User User, System.DateTime TimeStamp, DocumentState State)
public static bool FilterExpressionMatches(this DocumentTemplate dt, IAttachmentTarget Data, DiscoDataContext Database, User User, DateTime TimeStamp, DocumentState State)
{
if (!string.IsNullOrEmpty(dt.FilterExpression))
{
@@ -51,12 +49,12 @@ namespace Disco.Services
public static Expression OnImportAttachmentExpressionFromCache(this DocumentTemplate dt)
{
return ExpressionCache.GetValue("DocumentTemplate_OnImportExpression", dt.Id, () => { return Expression.TokenizeSingleDynamic(null, dt.OnImportAttachmentExpression, 0); });
return ExpressionCache.GetOrCreateSingleExpressions($"DocumentTemplate_OnImportExpression_{dt.Id}", () => Expression.TokenizeSingleDynamic(null, dt.OnImportAttachmentExpression, 0));
}
public static void OnImportAttachmentExpressionInvalidateCache(this DocumentTemplate dt)
{
ExpressionCache.InvalidateKey("DocumentTemplate_OnImportExpression", dt.Id);
ExpressionCache.InvalidateSingleCache($"DocumentTemplate_OnImportExpression_{dt.Id}");
}
public static string EvaluateOnAttachmentImportExpression(this DocumentTemplate dt, IAttachment Data, IAttachmentTarget AttachmentTarget, DiscoDataContext Database, User User, DateTime TimeStamp, List<DocumentUniqueIdentifier> PageIdentifiers)
@@ -77,12 +75,12 @@ namespace Disco.Services
public static Expression OnGenerateExpressionFromCache(this DocumentTemplate dt)
{
return ExpressionCache.GetValue("DocumentTemplate_OnGenerateExpression", dt.Id, () => { return Expression.TokenizeSingleDynamic(null, dt.OnGenerateExpression, 0); });
return ExpressionCache.GetOrCreateSingleExpressions($"DocumentTemplate_OnGenerateExpression_{dt.Id}", () => Expression.TokenizeSingleDynamic(null, dt.OnGenerateExpression, 0));
}
public static void OnGenerateExpressionInvalidateCache(this DocumentTemplate dt)
{
ExpressionCache.InvalidateKey("DocumentTemplate_OnGenerateExpression", dt.Id);
ExpressionCache.InvalidateSingleCache($"DocumentTemplate_OnGenerateExpression_{dt.Id}");
}
public static string EvaluateOnGenerateExpression(this DocumentTemplate dt, IAttachmentTarget Data, DiscoDataContext Database, User User, DateTime TimeStamp, DocumentState State)
@@ -73,12 +73,12 @@ namespace Disco.Services
public static Expression FilterExpressionFromCache(this DocumentTemplatePackage package)
{
return ExpressionCache.GetValue("DocumentTemplatePackage_FilterExpression", package.Id, () => { return Expression.TokenizeSingleDynamic(null, package.FilterExpression, 0); });
return ExpressionCache.GetOrCreateSingleExpressions($"DocumentTemplatePackage_FilterExpression_{package.Id}", () => Expression.TokenizeSingleDynamic(null, package.FilterExpression, 0));
}
public static void FilterExpressionInvalidateCache(this DocumentTemplatePackage package)
{
ExpressionCache.InvalidateKey("DocumentTemplatePackage_FilterExpression", package.Id);
ExpressionCache.InvalidateSingleCache($"DocumentTemplatePackage_FilterExpression_{package.Id}");
}
public static bool FilterExpressionMatches(this DocumentTemplatePackage package, IAttachmentTarget Data, DiscoDataContext Database, User User, DateTime TimeStamp, DocumentState State)
@@ -111,12 +111,12 @@ namespace Disco.Services
public static Expression OnGenerateExpressionFromCache(this DocumentTemplatePackage package)
{
return ExpressionCache.GetValue("DocumentTemplatePackage_OnGenerateExpression", package.Id, () => { return Expression.TokenizeSingleDynamic(null, package.OnGenerateExpression, 0); });
return ExpressionCache.GetOrCreateSingleExpressions($"DocumentTemplatePackage_OnGenerateExpression_{package.Id}", () => Expression.TokenizeSingleDynamic(null, package.OnGenerateExpression, 0));
}
public static void OnGenerateExpressionInvalidateCache(this DocumentTemplatePackage package)
{
ExpressionCache.InvalidateKey("DocumentTemplatePackage_OnGenerateExpression", package.Id);
ExpressionCache.InvalidateSingleCache($"DocumentTemplatePackage_OnGenerateExpression_{package.Id}");
}
public static string EvaluateOnGenerateExpression(this DocumentTemplatePackage package, IAttachmentTarget Data, DiscoDataContext Database, User User, DateTime TimeStamp, DocumentState State)
-1
View File
@@ -9,7 +9,6 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
namespace Disco.Services.Expressions
+62 -78
View File
@@ -1,99 +1,83 @@
using System.Collections.Concurrent;
using Disco.Models.Repository;
using Disco.Models.Services.Documents;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace Disco.Services.Expressions
{
public static class ExpressionCache
{
private static ConcurrentDictionary<string, ConcurrentDictionary<string, Expression>> _Cache = new ConcurrentDictionary<string, ConcurrentDictionary<string, Expression>>();
private static ConcurrentDictionary<string, Expression> singleCache = new ConcurrentDictionary<string, Expression>();
private static ConcurrentDictionary<string, Dictionary<string, Expression>> cache = new ConcurrentDictionary<string, Dictionary<string, Expression>>();
private static ConcurrentDictionary<string, List<DocumentField>> fieldCache = new ConcurrentDictionary<string, List<DocumentField>>();
public delegate Expression CreateValueDelegate();
private const string DocumentTemplateCacheTemplate = "DocumentTemplate_{0}";
public static ConcurrentDictionary<string, Expression> GetModule(string Module, bool Create = false)
public static Expression GetOrCreateSingleExpressions(string key, Func<Expression> create)
{
ConcurrentDictionary<string, Expression> moduleCache;
if (_Cache.TryGetValue(Module, out moduleCache))
return moduleCache;
if (singleCache.TryGetValue(key, out var result))
return result;
else
{
if (Create)
{
moduleCache = new ConcurrentDictionary<string, Expression>();
_Cache.TryAdd(Module, moduleCache);
return moduleCache;
}
else
return null;
result = create();
singleCache.TryAdd(key, result);
return result;
}
}
private static Expression GetModuleValue(string Module, string Key, CreateValueDelegate CreateValue)
{
ConcurrentDictionary<string, Expression> moduleCache = GetModule(Module, (CreateValue != null));
if (moduleCache != null)
{
Expression expression;
if (moduleCache.TryGetValue(Key, out expression))
{
return expression;
}
if (CreateValue != null)
{
expression = CreateValue();
Expression oldExpression;
if (moduleCache.TryGetValue(Key, out oldExpression))
moduleCache.TryUpdate(Key, expression, oldExpression);
else
moduleCache.TryAdd(Key, expression);
return expression;
}
}
return null;
}
public static Expression GetValue(string Module, string Key, CreateValueDelegate CreateValue)
public static Dictionary<string, Expression> GetOrCreateExpressions(string module, Func<Tuple<Dictionary<string, Expression>, List<DocumentField>>> create)
{
return GetModuleValue(Module, Key, CreateValue);
}
public static Expression GetValue(string Module, string Key)
{
return GetModuleValue(Module, Key, null);
}
public static bool InvalidModule(string Module)
{
ConcurrentDictionary<string, Expression> moduleCache;
return _Cache.TryRemove(Module, out moduleCache);
}
public static bool InvalidateKey(string Module, string Key)
{
Expression expression;
ConcurrentDictionary<string, Expression> moduleCache = GetModule(Module, false);
if (moduleCache != null)
{
bool removeResult = moduleCache.TryRemove(Key, out expression);
if (moduleCache.Count == 0)
InvalidModule(Module);
return removeResult;
}
if (cache.TryGetValue(module, out var result))
return result;
else
return false;
}
public static bool SetValue(string Module, string Key, Expression Expression)
{
ConcurrentDictionary<string, Expression> moduleCache = GetModule(Module, true);
if (moduleCache.ContainsKey(Key))
{
Expression oldExpression;
if (moduleCache.TryGetValue(Key, out oldExpression))
{
return moduleCache.TryUpdate(Key, Expression, oldExpression);
}
return Create(module, create).Item1;
}
return moduleCache.TryAdd(Key, Expression);
}
public static List<DocumentField> GetOrCreateFields(string module, Func<Tuple<Dictionary<string, Expression>, List<DocumentField>>> create)
{
if (fieldCache.TryGetValue(module, out var result))
return result;
else
{
return Create(module, create).Item2;
}
}
public static void InvalidateCache(DocumentTemplate template)
{
InvalidateCache(string.Format(DocumentTemplateCacheTemplate, template.Id));
}
public static void InvalidateCache(string module)
{
cache.TryRemove(module, out _);
fieldCache.TryRemove(module, out _);
}
public static void InvalidateSingleCache(string key)
{
singleCache.TryRemove(key, out _);
}
public static Dictionary<string, Expression> GetOrCreateExpressions(DocumentTemplate template, Func<Tuple<Dictionary<string, Expression>, List<DocumentField>>> create)
{
return GetOrCreateExpressions(string.Format(DocumentTemplateCacheTemplate, template.Id), create);
}
public static List<DocumentField> GetOrCreateFields(DocumentTemplate template, Func<Tuple<Dictionary<string, Expression>, List<DocumentField>>> create)
{
return GetOrCreateFields(string.Format(DocumentTemplateCacheTemplate, template.Id), create);
}
private static Tuple<Dictionary<string, Expression>, List<DocumentField>> Create(string module, Func<Tuple<Dictionary<string, Expression>, List<DocumentField>>> create)
{
var results = create();
cache.TryAdd(module, results.Item1);
fieldCache.TryAdd(module, results.Item2);
return results;
}
}
}
+4 -4
View File
@@ -123,22 +123,22 @@ namespace Disco.Services.Jobs
public static Expression OnCreateExpressionFromCache(DiscoDataContext Database)
{
return ExpressionCache.GetValue("Job_OnCreateExpression", string.Empty, () => { return Expression.TokenizeSingleDynamic(null, Database.DiscoConfiguration.JobPreferences.OnCreateExpression, 0); });
return ExpressionCache.GetOrCreateSingleExpressions("Job_OnCreateExpression", () => Expression.TokenizeSingleDynamic(null, Database.DiscoConfiguration.JobPreferences.OnCreateExpression, 0));
}
public static void OnCreateExpressionInvalidateCache()
{
ExpressionCache.InvalidateKey("Job_OnCreateExpression", string.Empty);
ExpressionCache.InvalidateSingleCache("Job_OnCreateExpression");
}
public static Expression OnCloseExpressionFromCache(DiscoDataContext Database)
{
return ExpressionCache.GetValue("Job_OnCloseExpression", string.Empty, () => { return Expression.TokenizeSingleDynamic(null, Database.DiscoConfiguration.JobPreferences.OnCloseExpression, 0); });
return ExpressionCache.GetOrCreateSingleExpressions("Job_OnCloseExpression", () => Expression.TokenizeSingleDynamic(null, Database.DiscoConfiguration.JobPreferences.OnCloseExpression, 0));
}
public static void OnCloseExpressionInvalidateCache()
{
ExpressionCache.InvalidateKey("Job_OnCloseExpression", string.Empty);
ExpressionCache.InvalidateSingleCache("Job_OnCloseExpression");
}
}
@@ -133,12 +133,12 @@ namespace Disco.Services
public static Expression OnAssignmentExpressionFromCache(this UserFlag uf)
{
return ExpressionCache.GetValue("UserFlag_OnAssignmentExpression", uf.Id.ToString(), () => { return Expression.TokenizeSingleDynamic(null, uf.OnAssignmentExpression, 0); });
return ExpressionCache.GetOrCreateSingleExpressions($"UserFlag_OnAssignmentExpression_{uf.Id}", () => Expression.TokenizeSingleDynamic(null, uf.OnAssignmentExpression, 0));
}
public static void OnAssignmentExpressionInvalidateCache(this UserFlag uf)
{
ExpressionCache.InvalidateKey("UserFlag_OnAssignmentExpression", uf.Id.ToString());
ExpressionCache.InvalidateSingleCache($"UserFlag_OnAssignmentExpression_{uf.Id}");
}
public static string EvaluateOnAssignmentExpression(this UserFlagAssignment ufa, DiscoDataContext Database, User AddingUser, DateTime TimeStamp)
@@ -158,12 +158,12 @@ namespace Disco.Services
public static Expression OnUnassignmentExpressionFromCache(this UserFlag uf)
{
return ExpressionCache.GetValue("UserFlag_OnUnassignmentExpression", uf.Id.ToString(), () => { return Expression.TokenizeSingleDynamic(null, uf.OnUnassignmentExpression, 0); });
return ExpressionCache.GetOrCreateSingleExpressions($"UserFlag_OnUnassignmentExpression_{uf.Id}", () => Expression.TokenizeSingleDynamic(null, uf.OnUnassignmentExpression, 0));
}
public static void OnUnassignmentExpressionInvalidateCache(this UserFlag uf)
{
ExpressionCache.InvalidateKey("UserFlag_OnUnassignmentExpression", uf.Id.ToString());
ExpressionCache.InvalidateSingleCache($"UserFlag_OnUnassignmentExpression_{uf.Id}");
}
public static string EvaluateOnUnassignmentExpression(this UserFlagAssignment ufa, DiscoDataContext Database, User RemovingUser, DateTime TimeStamp)
@@ -1231,6 +1231,8 @@ namespace Disco.Web.Areas.API.Controllers
}
}
[HttpPost, ValidateAntiForgeryToken]
[DiscoAuthorizeAll(Claims.Config.DocumentTemplate.Configure, Claims.Config.UserFlag.Configure)]
public virtual ActionResult AddOnImportUserFlagRule([Required] string id, bool? addFlag = null, int? userFlagId = null, string comments = null)
{
try