diff --git a/Dockerfile b/Dockerfile index 838f4e2b..153b338c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,6 @@ # syntax=docker/dockerfile:experimental ARG DbVersion=7000072 -FROM sillsdev/web-languageforge:latest AS lf-build -# No changes needed, LF app result in /var/www/html - FROM mcr.microsoft.com/dotnet/sdk:5.0 AS lfmerge-builder-base WORKDIR /build/lfmerge @@ -61,9 +58,6 @@ ENV NUNIT_VERSION_MAJOR=3 FROM lfmerge-build-${DbVersion} AS lfmerge-build -# LanguageForge repo expected to be in /var/www/html (will be copied into ./data/php/src before running unit tests) -COPY --chown=builder:users --from=lf-build /var/www/html /var/www/html - USER builder # Git repo should be mounted under ${HOME}/packages/lfmerge when run diff --git a/docker/scripts/build-and-test.sh b/docker/scripts/build-and-test.sh index a5057cea..04058cb8 100755 --- a/docker/scripts/build-and-test.sh +++ b/docker/scripts/build-and-test.sh @@ -24,9 +24,6 @@ echo After setup-workspace.sh, pwd is $(pwd) "$SCRIPT_DIR"/compile-lfmerge-combined.sh ${DbVersion} if [ -n "$RUN_UNIT_TESTS" -a "$RUN_UNIT_TESTS" -ne 0 ]; then - rm -rf "$SCRIPT_DIR"/data/php - mkdir -p "$SCRIPT_DIR"/data/php - cp -a /var/www/html "$SCRIPT_DIR"/data/php/src "$SCRIPT_DIR"/test-lfmerge-combined.sh ${DbVersion} fi diff --git a/src/LfMerge.Core.Tests/LanguageForge/Infrastructure/LanguageForgeProxyTests.cs b/src/LfMerge.Core.Tests/LanguageForge/Infrastructure/LanguageForgeProxyTests.cs deleted file mode 100644 index a4b3e780..00000000 --- a/src/LfMerge.Core.Tests/LanguageForge/Infrastructure/LanguageForgeProxyTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2016 SIL International -// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) -using System; -using System.Collections.Generic; -using LfMerge.Core.LanguageForge.Infrastructure; -using Newtonsoft.Json; -using NUnit.Framework; -using SIL.TestUtilities; - -namespace LfMerge.Core.Tests.LanguageForge.Infrastructure -{ - [TestFixture] - [Category("IntegrationTests")] - public class LanguageForgeProxyTests - { - private const string testProjectCode = "testlangproj"; - private const int originalNumOfLcmEntries = 63; - private TemporaryFolder LanguageForgeFolder; - private TestEnvironment _env; - - [OneTimeSetUp] - public void FixtureSetUp() - { - LanguageForgeFolder = new TemporaryFolder("LcmTestFixture"); - _env = new TestEnvironment( - resetLfProjectsDuringCleanup: false, - languageForgeServerFolder: LanguageForgeFolder, - registerLfProxyMock: false - ); - } - - [OneTimeTearDown] - public void FixtureTearDown() - { - try - { - LanguageForgeProjectAccessor.Reset(); // This disposes of lfProj - LanguageForgeFolder.Dispose(); - _env.Dispose(); - } - catch (Exception) - { - // This can happen if the objects already got disposed somewhere else. - // It doesn't really matter since we're in the process of doing cleanup anyways. - // So just ignore the exception. - } - } - - [Test] - [Explicit("Requires users in the mongo database, i.e. requires setup languageforge database")] - public void ListUsers_CanCallPhpClass() - { - // Setup - var sut = new LanguageForgeProxy(); - - // Exercise - string output = sut.ListUsers(); - - // Verify - var result = JsonConvert.DeserializeObject>(output); - Assert.That(output, Is.Not.Empty); - Assert.That(result["count"], Is.GreaterThan(0)); - } - - [Test] - [Explicit("Assumes PHP unit tests have been run once")] - public void UpdateCustomFieldViews_ReturnsProjectId() - { - // Setup - var customFieldSpecs = new List(); - customFieldSpecs.Add(new CustomFieldSpec("customField_entry_testMultiPara", "OwningAtom")); - customFieldSpecs.Add(new CustomFieldSpec("customField_examples_testOptionList", "ReferenceAtom")); - var sut = new LanguageForgeProxy(); - - // Exercise - string output = sut.UpdateCustomFieldViews("TestCode1", customFieldSpecs, true); - - // Verify - var result = JsonConvert.DeserializeObject(output); - Assert.That(output, Is.Not.Empty); - Assert.That(output, Is.Not.EqualTo("false")); - Assert.That(result, Is.Not.Empty); - } - } -} - diff --git a/src/LfMerge.Core.Tests/LanguageForgeProxyMock.cs b/src/LfMerge.Core.Tests/LanguageForgeProxyMock.cs deleted file mode 100644 index 7dab500b..00000000 --- a/src/LfMerge.Core.Tests/LanguageForgeProxyMock.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2016 SIL International -// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) -using LfMerge.Core.LanguageForge.Infrastructure; -using System.Collections.Generic; - -namespace LfMerge.Core.Tests -{ - public class LanguageForgeProxyMock: ILanguageForgeProxy - { - #region ILanguageForgeProxy implementation - - public string UpdateCustomFieldViews(string projectCode, List customFieldSpecs) - { - return UpdateCustomFieldViews(projectCode, customFieldSpecs, false); - } - - public string UpdateCustomFieldViews(string projectCode, List customFieldSpecs, bool isTest) - { - return "true"; - } - - public string ListUsers() - { - return null; - } - - #endregion - } -} - diff --git a/src/LfMerge.Core.Tests/TestDoubles.cs b/src/LfMerge.Core.Tests/TestDoubles.cs index ee530329..16cd3eac 100644 --- a/src/LfMerge.Core.Tests/TestDoubles.cs +++ b/src/LfMerge.Core.Tests/TestDoubles.cs @@ -182,7 +182,7 @@ public bool SetInputSystems(ILfProject project, Dictionary lfCustomFieldList) + public bool SetCustomFieldConfig(ILfProject project, Dictionary lfCustomFieldList, Dictionary lfCustomFieldTypes) { if (lfCustomFieldList == null) _storedCustomFieldConfig = new Dictionary(); diff --git a/src/LfMerge.Core.Tests/TestEnvironment.cs b/src/LfMerge.Core.Tests/TestEnvironment.cs index 2dbe5240..cae38056 100644 --- a/src/LfMerge.Core.Tests/TestEnvironment.cs +++ b/src/LfMerge.Core.Tests/TestEnvironment.cs @@ -38,15 +38,13 @@ static TestEnvironment() public TestEnvironment(bool registerSettingsModelDouble = true, bool registerProcessingStateDouble = true, bool resetLfProjectsDuringCleanup = true, - TemporaryFolder languageForgeServerFolder = null, - bool registerLfProxyMock = true) + TemporaryFolder languageForgeServerFolder = null) { _resetLfProjectsDuringCleanup = resetLfProjectsDuringCleanup; _languageForgeServerFolder = languageForgeServerFolder ?? new TemporaryFolder(TestName + Path.GetRandomFileName()); Environment.SetEnvironmentVariable("FW_CommonAppData", _languageForgeServerFolder.Path); MainClass.Container = RegisterTypes(registerSettingsModelDouble, - registerProcessingStateDouble, _languageForgeServerFolder.Path, - registerLfProxyMock).Build(); + registerProcessingStateDouble, _languageForgeServerFolder.Path).Build(); Settings = MainClass.Container.Resolve(); MainClass.Logger = MainClass.Container.Resolve(); Directory.CreateDirectory(Settings.LcmDirectorySettings.ProjectsDirectory); @@ -71,7 +69,7 @@ private string TestName } private ContainerBuilder RegisterTypes(bool registerSettingsModel, - bool registerProcessingStateDouble, string temporaryFolder, bool registerLfProxyMock) + bool registerProcessingStateDouble, string temporaryFolder) { var containerBuilder = MainClass.RegisterTypes(); containerBuilder.RegisterType() @@ -83,9 +81,6 @@ private ContainerBuilder RegisterTypes(bool registerSettingsModel, containerBuilder.RegisterType().As().SingleInstance(); - if (registerLfProxyMock) - containerBuilder.RegisterType().As(); - if (registerSettingsModel) { containerBuilder.RegisterType().As().SingleInstance(); diff --git a/src/LfMerge.Core/DataConverters/ConvertLcmToMongoCustomField.cs b/src/LfMerge.Core/DataConverters/ConvertLcmToMongoCustomField.cs index 731d5e4c..3a736398 100644 --- a/src/LfMerge.Core/DataConverters/ConvertLcmToMongoCustomField.cs +++ b/src/LfMerge.Core/DataConverters/ConvertLcmToMongoCustomField.cs @@ -48,7 +48,6 @@ public class ConvertLcmToMongoCustomField }; private Dictionary GuidToListCode; - private Dictionary _fieldNameToFieldType; public ConvertLcmToMongoCustomField(LcmCache cache, FwServiceLocatorCache serviceLocator, ILogger logger) { @@ -69,28 +68,6 @@ public ConvertLcmToMongoCustomField(LcmCache cache, FwServiceLocatorCache servic {servLoc.LanguageProject.StatusOA.Guid, MagicStrings.LfOptionListCodeForStatus}, {servLoc.LanguageProject.LexDbOA.UsageTypesOA.Guid, MagicStrings.LfOptionListCodeForUsageTypes} }; - _fieldNameToFieldType = new Dictionary(); - } - - public bool CreateCustomFieldsConfigViews(ILfProject project, Dictionary lfCustomFieldList, Dictionary lfCustomFieldTypes) - { - return CreateCustomFieldsConfigViews(project, lfCustomFieldList, lfCustomFieldTypes, false); - } - - public bool CreateCustomFieldsConfigViews(ILfProject project, Dictionary lfCustomFieldList, Dictionary lfCustomFieldTypes, bool isTest) - { - var customFieldSpecs = new List(); - foreach (string lfCustomFieldName in lfCustomFieldList.Keys) - { - customFieldSpecs.Add(new CustomFieldSpec(lfCustomFieldName, _fieldNameToFieldType[lfCustomFieldName])); - } - - var lfproxy = MainClass.Container.Resolve(); - string output = lfproxy.UpdateCustomFieldViews(project.ProjectCode, customFieldSpecs, isTest); - - if (string.IsNullOrEmpty(output) || output == "false") - return false; - return true; } /// @@ -157,7 +134,6 @@ Dictionary lfCustomFieldTypes continue; string lfCustomFieldName = ConvertUtilities.NormalizedFieldName(label, fieldSourceType); CellarPropertyType LcmFieldType = (CellarPropertyType)LcmMetaData.GetFieldType(flid); - _fieldNameToFieldType[lfCustomFieldName] = LcmFieldType.ToString(); // TODO: Comment this one OUT. Bad design. lfCustomFieldTypes[lfCustomFieldName] = LcmFieldType.ToString(); string lfCustomFieldType; if (CellarPropertyTypeToLfCustomFieldType.TryGetValue(LcmFieldType, out lfCustomFieldType)) diff --git a/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs b/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs index 21692017..73412eab 100644 --- a/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs +++ b/src/LfMerge.Core/DataConverters/ConvertLcmToMongoLexicon.cs @@ -108,8 +108,7 @@ public void RunConversion() Dictionary lfCustomFieldList = new Dictionary(); Dictionary lfCustomFieldTypes = new Dictionary(); _convertCustomField.WriteCustomFieldConfig(lfCustomFieldList, lfCustomFieldTypes); - Connection.SetCustomFieldConfig(LfProject, lfCustomFieldList); - _convertCustomField.CreateCustomFieldsConfigViews(LfProject, lfCustomFieldList, lfCustomFieldTypes); + Connection.SetCustomFieldConfig(LfProject, lfCustomFieldList, lfCustomFieldTypes); Dictionary previousModificationDates = Connection.GetAllModifiedDatesForEntries(LfProject); diff --git a/src/LfMerge.Core/LanguageForge/Config/LfRoleOrUserViewConfig.cs b/src/LfMerge.Core/LanguageForge/Config/LfRoleOrUserViewConfig.cs new file mode 100644 index 00000000..a1749ce3 --- /dev/null +++ b/src/LfMerge.Core/LanguageForge/Config/LfRoleOrUserViewConfig.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using MongoDB.Bson; + +namespace LfMerge.Core.LanguageForge.Config +{ + public class LexRoleOrUserViewConfig + { + public string[] InputSystems; + public Dictionary Fields; + public Dictionary ShowTasks; + } + + public class LexViewFieldConfig + { + public bool Show; + public string Type; + + public LexViewFieldConfig(string type, bool show = true) + { + Show = show; + Type = type; + } + public LexViewFieldConfig(bool show = true) : this("basic", show) {} + } + + public class LexViewMultiTextFieldConfig: LexViewFieldConfig + { + public bool OverrideInputSystems; + public string[] InputSystems; + + public LexViewMultiTextFieldConfig(bool show = true) : base("multitext", show) + { + this.OverrideInputSystems = false; + this.InputSystems = new string[]{}; + } + } + + public static class LexViewFieldConfigFactory + { + public static LexViewFieldConfig CreateByType(string lfCustomFieldType, bool show = true) { + if (lfCustomFieldType == "MultiUnicode" || lfCustomFieldType == "MultiString" || lfCustomFieldType == "String") { + return new LexViewMultiTextFieldConfig(show); + } else { + return new LexViewFieldConfig(show); + } + } + + public static BsonDocument CreateBsonDocumentByType(string lfCustomFieldType, bool show = true) { + LexViewFieldConfig config = CreateByType(lfCustomFieldType, show); + var result = new BsonDocument(); + result.Set("show", new BsonBoolean(config.Show)); + result.Set("type", new BsonString(config.Type)); + var multiTextConfig = config as LexViewMultiTextFieldConfig; + if (multiTextConfig != null) { + result.Set("overrideInputSystems", new BsonBoolean(multiTextConfig.OverrideInputSystems)); + if (multiTextConfig.InputSystems != null && multiTextConfig.InputSystems.Length > 0) { + result.Set("inputSystems", new BsonArray(multiTextConfig.InputSystems)); + } + } + return result; + } + } +} diff --git a/src/LfMerge.Core/LanguageForge/Infrastructure/ILanguageForgeProxy.cs b/src/LfMerge.Core/LanguageForge/Infrastructure/ILanguageForgeProxy.cs deleted file mode 100644 index 4e7e6ba0..00000000 --- a/src/LfMerge.Core/LanguageForge/Infrastructure/ILanguageForgeProxy.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2016 SIL International -// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) -using System.Collections.Generic; - -namespace LfMerge.Core.LanguageForge.Infrastructure -{ - public interface ILanguageForgeProxy - { - string UpdateCustomFieldViews(string projectCode, List customFieldSpecs); - string UpdateCustomFieldViews(string projectCode, List customFieldSpecs, bool isTest); - - string ListUsers(); - } -} - diff --git a/src/LfMerge.Core/LanguageForge/Infrastructure/LanguageForgeProxy.cs b/src/LfMerge.Core/LanguageForge/Infrastructure/LanguageForgeProxy.cs deleted file mode 100644 index 7e0cdb8e..00000000 --- a/src/LfMerge.Core/LanguageForge/Infrastructure/LanguageForgeProxy.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2016 SIL International -// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using Autofac; -using LfMerge.Core.Settings; -using Newtonsoft.Json; - -namespace LfMerge.Core.LanguageForge.Infrastructure -{ - public class LanguageForgeProxy: ILanguageForgeProxy - { - public string UpdateCustomFieldViews(string projectCode, List customFieldSpecs) - { - return UpdateCustomFieldViews(projectCode, customFieldSpecs, false); - } - - public string UpdateCustomFieldViews(string projectCode, List customFieldSpecs, bool isTest) - { - const string className = "Api\\Model\\Languageforge\\Lexicon\\Command\\LexProjectCommands"; - const string methodName = "updateCustomFieldViews"; - var parameters = new List(); - parameters.Add(projectCode); - parameters.Add(customFieldSpecs); - return RunClass(className, methodName, parameters, isTest); - } - - public string ListUsers() - { - const string className = "Api\\Model\\Command\\UserCommands"; - const string methodName = "listUsers"; - var parameters = new List(); - - return RunClass(className, methodName, parameters); - } - - private static string RunClass(string className, string methodName, List parameters) - { - return RunClass(className, methodName, parameters, false); - } - - private static string RunClass(string className, string methodName, List parameters, bool isTest) - { - var runClassParameters = new RunClassParameters(className, methodName, parameters); - runClassParameters.isTest = isTest; - string runClassParametersJson = JsonConvert.SerializeObject(runClassParameters); - - var settings = MainClass.Container.Resolve(); - - string output; - using (var p = new Process()) - { - p.StartInfo.UseShellExecute = false; - p.StartInfo.RedirectStandardInput = true; - p.StartInfo.RedirectStandardOutput = true; - p.StartInfo.FileName = "php"; - p.StartInfo.Arguments = Path.Combine(settings.PhpSourcePath, - "Api/Library/Shared/CLI/RunClass.php"); - p.Start(); - p.StandardInput.Write(runClassParametersJson); - p.StandardInput.Close(); - - output = p.StandardOutput.ReadToEnd(); - p.WaitForExit(); - if (p.ExitCode != 0) - { - throw new Exception("RunClass non-zero exit code!\n" + output); - } - p.Close(); - } - - return output; - } - } -} - diff --git a/src/LfMerge.Core/LanguageForge/Infrastructure/RunClassParameters.cs b/src/LfMerge.Core/LanguageForge/Infrastructure/RunClassParameters.cs deleted file mode 100644 index 769e9676..00000000 --- a/src/LfMerge.Core/LanguageForge/Infrastructure/RunClassParameters.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2016 SIL International -// This software is licensed under the MIT license (http://opensource.org/licenses/MIT) -using System; -using System.Collections.Generic; - -namespace LfMerge.Core.LanguageForge.Infrastructure -{ - public class RunClassParameters - { - public RunClassParameters(string _className, string _methodName, List _parameters) - { - className = _className; - methodName = _methodName; - parameters = _parameters; - isTest = false; - } - - public string className { get; set; } - public string methodName { get; set; } - public List parameters { get; set; } - public bool isTest { get; set; } - } -} - diff --git a/src/LfMerge.Core/MainClass.cs b/src/LfMerge.Core/MainClass.cs index d9fad797..01007ab4 100644 --- a/src/LfMerge.Core/MainClass.cs +++ b/src/LfMerge.Core/MainClass.cs @@ -59,7 +59,6 @@ internal static ContainerBuilder RegisterTypes() containerBuilder.RegisterType().AsSelf(); containerBuilder.RegisterType().AsSelf(); containerBuilder.RegisterType().As(); - containerBuilder.RegisterType().As(); Action.Register(containerBuilder); Queue.Register(containerBuilder); return containerBuilder; diff --git a/src/LfMerge.Core/MongoConnector/IMongoConnection.cs b/src/LfMerge.Core/MongoConnector/IMongoConnection.cs index 336244f8..44914b38 100644 --- a/src/LfMerge.Core/MongoConnector/IMongoConnection.cs +++ b/src/LfMerge.Core/MongoConnector/IMongoConnection.cs @@ -29,7 +29,7 @@ public interface IMongoConnection void UpdateCommentStatuses(ILfProject project, List>> statusChanges); bool SetInputSystems(ILfProject project, Dictionary inputSystems, List vernacularWss, List analysisWss, List pronunciationWss); - bool SetCustomFieldConfig(ILfProject project, Dictionary lfCustomFieldList); + bool SetCustomFieldConfig(ILfProject project, Dictionary lfCustomFieldList, Dictionary lfCustomFieldTypes); Dictionary GetCustomFieldConfig(ILfProject project); bool SetLastSyncedDate(ILfProject project, DateTime? newSyncedDate); // TODO: Decide if this is really where this method belongs void SetCommentReplyGuids(ILfProject project, IDictionary uniqIdToGuidMappings); diff --git a/src/LfMerge.Core/MongoConnector/MongoConnection.cs b/src/LfMerge.Core/MongoConnector/MongoConnection.cs index 7b37a4d0..7ef2d82b 100644 --- a/src/LfMerge.Core/MongoConnector/MongoConnection.cs +++ b/src/LfMerge.Core/MongoConnector/MongoConnection.cs @@ -645,6 +645,22 @@ public bool SetInputSystems(ILfProject project, Dictionary /// Remove previous project custom field configurations that no longer exist, /// and then update them at the appropriate entry, senses, and examples level. @@ -653,7 +669,7 @@ public bool SetInputSystems(ILfProject project, DictionaryLF project /// Dictionary of LF custom field settings /// True if mongodb was updated - public bool SetCustomFieldConfig(ILfProject project, Dictionary lfCustomFieldList) + public bool SetCustomFieldConfig(ILfProject project, Dictionary lfCustomFieldList, Dictionary lfCustomFieldTypes) { // Logger.Debug("Setting {0} custom field setting(s)", lfCustomFieldList.Count()); @@ -672,8 +688,16 @@ public bool SetCustomFieldConfig(ILfProject project, Dictionary senseCustomFieldOrder = new List(); List exampleCustomFieldOrder = new List(); + Dictionary customFieldConfig = GetCustomFieldConfig(project); + BsonDocument roleViews = GetRoleViews(project); + BsonDocument userViews = GetUserViews(project); + + List roleViewNames = roleViews?.Names.ToList() ?? new List(); + List userViewNames = userViews?.Names.ToList() ?? new List(); + // Note that userViewNames doesn't contain usernames like "rmunn", but ObjectId strings like "54c780ea863f1c2127635ca9" + // Clean out previous fields and fieldOrders that no longer exist (removed from LCM) - foreach (string customFieldNameToRemove in GetCustomFieldConfig(project).Keys.Except(lfCustomFieldList.Keys.ToList())) + foreach (string customFieldNameToRemove in customFieldConfig.Keys.Except(lfCustomFieldList.Keys.ToList())) { if (customFieldNameToRemove.StartsWith(MagicStrings.LfCustomFieldEntryPrefix)) { @@ -690,6 +714,13 @@ public bool SetCustomFieldConfig(ILfProject project, Dictionary 0) previousUpdates.Add(builder.PullAll("config.entry.fieldOrder", entryCustomFieldOrder)); @@ -705,6 +736,35 @@ public bool SetCustomFieldConfig(ILfProject project, Dictionary(); foreach (var customFieldKVP in lfCustomFieldList) { + string fieldName = customFieldKVP.Key; + LfConfigFieldBase fieldConfig = customFieldKVP.Value; + + string fieldType; + if (!lfCustomFieldTypes.TryGetValue(fieldName, out fieldType)) { + fieldType = "basic"; + } + BsonDocument viewFieldConfig = LexViewFieldConfigFactory.CreateBsonDocumentByType(fieldType); + + foreach (var viewName in roleViewNames) { + BsonValue value = roleViews.GetValue(viewName); + if (value != null && value.IsBsonDocument) { + BsonDocument view = value.AsBsonDocument; + if (!view.Contains(fieldName)) { + currentUpdates.Add(builder.Set(String.Format("config.roleViews.{0}.fields.{1}", viewName, fieldName), viewFieldConfig)); + } + } + } + + foreach (var viewName in userViewNames) { + BsonValue value = userViews.GetValue(viewName); + if (value != null && value.IsBsonDocument) { + BsonDocument view = value.AsBsonDocument; + if (!view.Contains(fieldName)) { + currentUpdates.Add(builder.Set(String.Format("config.userViews.{0}.fields.{1}", viewName, fieldName), viewFieldConfig)); + } + } + } + if (customFieldKVP.Key.StartsWith(MagicStrings.LfCustomFieldEntryPrefix)) { currentUpdates.Add(builder.Set(String.Format("config.entry.fields.{0}", customFieldKVP.Key), customFieldKVP.Value)); @@ -721,6 +781,7 @@ public bool SetCustomFieldConfig(ILfProject project, Dictionary 0) currentUpdates.Add(builder.AddToSetEach("config.entry.fieldOrder", entryCustomFieldOrder)); if (senseCustomFieldOrder.Count > 0)