From 2f141407aa477e0648c6b7675a70e24b52a2ecf2 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Wed, 6 Aug 2025 14:25:06 -0400 Subject: [PATCH] Add setting to ignore merge sets with only protected words --- .../Controllers/MergeControllerTests.cs | 2 +- Backend.Tests/Models/ProjectTests.cs | 1 + Backend/Controllers/MergeController.cs | 14 +-- Backend/Helper/DuplicateFinder.cs | 15 +++- Backend/Interfaces/IMergeService.cs | 3 +- Backend/Models/Project.cs | 7 ++ Backend/Repositories/ProjectRepository.cs | 1 + Backend/Services/MergeService.cs | 10 +-- docs/user_guide/docs/project.md | 15 +++- docs/user_guide/docs/project.zh.md | 15 +++- public/locales/en/translation.json | 13 ++- src/api/api/merge-api.ts | 37 +++++++- src/api/models/project.ts | 6 ++ src/backend/index.ts | 6 +- .../ProjectSettings/ProjectProtectedData.tsx | 90 +++++++++++++++++++ .../ProjectProtectedOverride.tsx | 48 ---------- src/components/ProjectSettings/index.tsx | 8 +- src/goals/Redux/GoalActions.ts | 11 ++- src/types/project.ts | 1 + 19 files changed, 217 insertions(+), 86 deletions(-) create mode 100644 src/components/ProjectSettings/ProjectProtectedData.tsx delete mode 100644 src/components/ProjectSettings/ProjectProtectedOverride.tsx diff --git a/Backend.Tests/Controllers/MergeControllerTests.cs b/Backend.Tests/Controllers/MergeControllerTests.cs index 72b97fb32d..3aeb59a8b5 100644 --- a/Backend.Tests/Controllers/MergeControllerTests.cs +++ b/Backend.Tests/Controllers/MergeControllerTests.cs @@ -130,7 +130,7 @@ public void TestGraylistAdd() public void TestFindPotentialDuplicatesNoPermission() { _mergeController.ControllerContext.HttpContext = PermissionServiceMock.UnauthorizedHttpContext(); - var result = _mergeController.FindPotentialDuplicates("projId", 2, 1).Result; + var result = _mergeController.FindPotentialDuplicates("projId", 2, 1, false).Result; Assert.That(result, Is.InstanceOf()); } diff --git a/Backend.Tests/Models/ProjectTests.cs b/Backend.Tests/Models/ProjectTests.cs index ac35b34c4c..d06dff49ac 100644 --- a/Backend.Tests/Models/ProjectTests.cs +++ b/Backend.Tests/Models/ProjectTests.cs @@ -17,6 +17,7 @@ public void TestClone() DefinitionsEnabled = true, GrammaticalInfoEnabled = true, AutocompleteSetting = OffOnSetting.On, + ProtectedDataMergeAvoidEnabled = OffOnSetting.Off, ProtectedDataOverrideEnabled = OffOnSetting.Off, SemDomWritingSystem = new("fr", "Français"), VernacularWritingSystem = new("en", "English", "Calibri"), diff --git a/Backend/Controllers/MergeController.cs b/Backend/Controllers/MergeController.cs index 6944ecaa00..528a748d76 100644 --- a/Backend/Controllers/MergeController.cs +++ b/Backend/Controllers/MergeController.cs @@ -114,10 +114,12 @@ public async Task GraylistAdd(string projectId, [FromBody, BindRe /// Id of project in which to search the frontier for potential duplicates. /// Max number of words allowed within a list of potential duplicates. /// Max number of lists of potential duplicates. - [HttpGet("finddups/{maxInList:int}/{maxLists:int}", Name = "FindPotentialDuplicates")] + /// Whether to require each set to have at least one unprotected word. + [HttpGet("finddups/{maxInList:int}/{maxLists:int}/{ignoreProtected:bool}", Name = "FindPotentialDuplicates")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task FindPotentialDuplicates(string projectId, int maxInList, int maxLists) + public async Task FindPotentialDuplicates( + string projectId, int maxInList, int maxLists, bool ignoreProtected) { if (!await _permissionService.HasProjectPermission( HttpContext, Permission.MergeAndReviewEntries, projectId)) @@ -132,14 +134,16 @@ public async Task FindPotentialDuplicates(string projectId, int m // Run the task without waiting for completion. // This Task will be scheduled within the existing Async executor thread pool efficiently. // See: https://stackoverflow.com/a/64614779/1398841 - _ = Task.Run(() => GetDuplicatesThenSignal(projectId, maxInList, maxLists, userId)); + _ = Task.Run(() => GetDuplicatesThenSignal(projectId, maxInList, maxLists, userId, ignoreProtected)); return Ok(); } - internal async Task GetDuplicatesThenSignal(string projectId, int maxInList, int maxLists, string userId) + internal async Task GetDuplicatesThenSignal( + string projectId, int maxInList, int maxLists, string userId, bool ignoreProtected = false) { - var success = await _mergeService.GetAndStorePotentialDuplicates(projectId, maxInList, maxLists, userId); + var success = await _mergeService.GetAndStorePotentialDuplicates( + projectId, maxInList, maxLists, userId, ignoreProtected); if (success) { await _notifyService.Clients.All.SendAsync(CombineHub.MethodSuccess, userId); diff --git a/Backend/Helper/DuplicateFinder.cs b/Backend/Helper/DuplicateFinder.cs index 5a6b52b082..331fd23f90 100644 --- a/Backend/Helper/DuplicateFinder.cs +++ b/Backend/Helper/DuplicateFinder.cs @@ -27,7 +27,7 @@ public DuplicateFinder(int maxInList, int maxLists, double maxScore) /// each with multiple s having a common Vernacular. /// public async Task>> GetIdenticalVernWords( - List collection, Func, Task> isUnavailableSet) + List collection, Func, Task> isUnavailableSet, bool ignoreProtected = false) { var wordLists = new List> { Capacity = _maxLists }; while (collection.Count > 0 && wordLists.Count < _maxLists) @@ -48,6 +48,13 @@ public async Task>> GetIdenticalVernWords( continue; } + if (ignoreProtected && word.Accessibility == Status.Protected + && similarWords.All(w => w.Accessibility == Status.Protected)) + { + // If all the words are protected, skip this set. + continue; + } + // Remove from collection and add to main list. var idsToRemove = similarWords.Select(w => w.Id); collection.RemoveAll(w => idsToRemove.Contains(w.Id)); @@ -81,11 +88,13 @@ await isUnavailableSet(ids[..Math.Min(ids.Count, _maxInList)])) /// the outer list is ordered by similarity of the first two items in each inner List. /// public async Task>> GetSimilarWords( - List collection, Func, Task> isUnavailableSet) + List collection, Func, Task> isUnavailableSet, bool ignoreProtected = false) { var similarWordsLists = collection.AsParallel() .Select(w => GetSimilarToWord(w, collection)) - .Where(wl => wl.Count > 1).ToList(); + .Where( + wl => wl.Count > 1 && (!ignoreProtected || wl.Any(w => w.Item2.Accessibility != Status.Protected))) + .ToList(); var best = new List>(); var bestIds = new List(); diff --git a/Backend/Interfaces/IMergeService.cs b/Backend/Interfaces/IMergeService.cs index 55c66d6ed5..7bcda787dc 100644 --- a/Backend/Interfaces/IMergeService.cs +++ b/Backend/Interfaces/IMergeService.cs @@ -15,7 +15,8 @@ public interface IMergeService Task IsInMergeGraylist(string projectId, List wordIds, string? userId = null); Task UpdateMergeBlacklist(string projectId); Task UpdateMergeGraylist(string projectId); - Task GetAndStorePotentialDuplicates(string projectId, int maxInList, int maxLists, string userId); + Task GetAndStorePotentialDuplicates( + string projectId, int maxInList, int maxLists, string userId, bool ignoreProtected = false); List>? RetrieveDups(string userId); Task HasGraylistEntries(string projectId, string? userId = null); Task>> GetGraylistEntries(string projectId, int maxLists, string? userId = null); diff --git a/Backend/Models/Project.cs b/Backend/Models/Project.cs index ca639dccfe..f53af5ac5c 100644 --- a/Backend/Models/Project.cs +++ b/Backend/Models/Project.cs @@ -40,6 +40,11 @@ public class Project [BsonRepresentation(BsonType.String)] public OffOnSetting AutocompleteSetting { get; set; } + [Required] + [BsonElement("protectedDataMergeAvoidEnabled")] + [BsonRepresentation(BsonType.String)] + public OffOnSetting ProtectedDataMergeAvoidEnabled { get; set; } + [Required] [BsonElement("protectedDataOverrideEnabled")] [BsonRepresentation(BsonType.String)] @@ -98,6 +103,7 @@ public Project() DefinitionsEnabled = false; GrammaticalInfoEnabled = false; AutocompleteSetting = OffOnSetting.On; + ProtectedDataMergeAvoidEnabled = OffOnSetting.Off; ProtectedDataOverrideEnabled = OffOnSetting.Off; SemDomWritingSystem = new(); VernacularWritingSystem = new(); @@ -124,6 +130,7 @@ public Project Clone() DefinitionsEnabled = DefinitionsEnabled, GrammaticalInfoEnabled = GrammaticalInfoEnabled, AutocompleteSetting = AutocompleteSetting, + ProtectedDataMergeAvoidEnabled = ProtectedDataMergeAvoidEnabled, ProtectedDataOverrideEnabled = ProtectedDataOverrideEnabled, SemDomWritingSystem = SemDomWritingSystem.Clone(), VernacularWritingSystem = VernacularWritingSystem.Clone(), diff --git a/Backend/Repositories/ProjectRepository.cs b/Backend/Repositories/ProjectRepository.cs index 3baa99b512..293dc33670 100644 --- a/Backend/Repositories/ProjectRepository.cs +++ b/Backend/Repositories/ProjectRepository.cs @@ -99,6 +99,7 @@ public async Task Update(string projectId, Project project) .Set(x => x.DefinitionsEnabled, project.DefinitionsEnabled) .Set(x => x.GrammaticalInfoEnabled, project.GrammaticalInfoEnabled) .Set(x => x.AutocompleteSetting, project.AutocompleteSetting) + .Set(x => x.ProtectedDataMergeAvoidEnabled, project.ProtectedDataMergeAvoidEnabled) .Set(x => x.ProtectedDataOverrideEnabled, project.ProtectedDataOverrideEnabled) .Set(x => x.SemDomWritingSystem, project.SemDomWritingSystem) .Set(x => x.VernacularWritingSystem, project.VernacularWritingSystem) diff --git a/Backend/Services/MergeService.cs b/Backend/Services/MergeService.cs index d9e065a589..ec75ea17a7 100644 --- a/Backend/Services/MergeService.cs +++ b/Backend/Services/MergeService.cs @@ -379,14 +379,14 @@ public async Task>> GetGraylistEntries( /// /// bool: true if successful or false if a newer request has begun. public async Task GetAndStorePotentialDuplicates( - string projectId, int maxInList, int maxLists, string userId) + string projectId, int maxInList, int maxLists, string userId, bool ignoreProtected = false) { var counter = Interlocked.Increment(ref _mergeCounter); if (StoreDups(userId, counter, null) != counter) { return false; } - var dups = await GetPotentialDuplicates(projectId, maxInList, maxLists, userId); + var dups = await GetPotentialDuplicates(projectId, maxInList, maxLists, userId, ignoreProtected); // Store the potential duplicates for user to retrieve later. return StoreDups(userId, counter, dups) == counter; } @@ -395,7 +395,7 @@ public async Task GetAndStorePotentialDuplicates( /// Get Lists of potential duplicate s in specified 's frontier. /// private async Task>> GetPotentialDuplicates( - string projectId, int maxInList, int maxLists, string? userId = null) + string projectId, int maxInList, int maxLists, string? userId = null, bool ignoreProtected = false) { var dupFinder = new DuplicateFinder(maxInList, maxLists, 2); @@ -405,13 +405,13 @@ async Task isUnavailableSet(List wordIds) => (await IsInMergeGraylist(projectId, wordIds, userId)); // First pass, only look for words with identical vernacular. - var wordLists = await dupFinder.GetIdenticalVernWords(collection, isUnavailableSet); + var wordLists = await dupFinder.GetIdenticalVernWords(collection, isUnavailableSet, ignoreProtected); // If no such sets found, look for similar words. if (wordLists.Count == 0) { collection = await _wordRepo.GetFrontier(projectId); - wordLists = await dupFinder.GetSimilarWords(collection, isUnavailableSet); + wordLists = await dupFinder.GetSimilarWords(collection, isUnavailableSet, ignoreProtected); } return wordLists; diff --git a/docs/user_guide/docs/project.md b/docs/user_guide/docs/project.md index b0948a582a..877aee64fc 100644 --- a/docs/user_guide/docs/project.md +++ b/docs/user_guide/docs/project.md @@ -62,11 +62,18 @@ entry, rather than creating a (mostly) duplicate to something previously entered (This does not affect spelling suggestions for the gloss, since those suggestions are based on a dictionary independent of existing project data.) -#### Protected Data Override +#### Protected Data Management -The default setting is Off. Change this to On to allow project users in Merge Duplicates to override the -[protection](goals.md#protected-entries-and-senses) of words and senses that were imported with data not handled by The -Combine. +This section has two Off/On setting toggles related to the [protection](goals.md#protected-entries-and-senses) of words +and senses that were imported with data not handled by The Combine. Both settings are off by default. + +Turn on "Avoid protected sets in Merge Duplicates" to make the Merge Duplicates tool only show sets of potential +duplicates with at least one word that isn't protected. This will avoid sets of mature entries imported from FieldWorks +and promote merging entries collected in The Combine. + +Turn on "Allow data protection override in Merge Duplicates" to allow project users in Merge Duplicates to manually +override protection of words and senses. If anybody tries to merge or delete a protected entry or senses, The Combine +warns them of the fields that will be lost. #### Archive Project diff --git a/docs/user_guide/docs/project.zh.md b/docs/user_guide/docs/project.zh.md index 538c95d2c7..fbb4f02991 100644 --- a/docs/user_guide/docs/project.zh.md +++ b/docs/user_guide/docs/project.zh.md @@ -50,11 +50,18 @@ Combine 顶部应用框的中间位置看到一个齿轮图标或该项目名。 (这不影响对注释的拼写建议,因为这些建议是基于独立于现有项目数据的字典的)。 -#### Protected Data Override +#### Protected Data Management -The default setting is Off. Change this to On to allow project users in Merge Duplicates to override the -[protection](goals.md#protected-entries-and-senses) of words and senses that were imported with data not handled by The -Combine. +This section has two Off/On setting toggles related to the [protection](goals.md#protected-entries-and-senses) of words +and senses that were imported with data not handled by The Combine. Both settings are off by default. + +Turn on "Avoid protected sets in Merge Duplicates" to make the Merge Duplicates tool only show sets of potential +duplicates with at least one word that isn't protected. This will avoid sets of mature entries imported from FieldWorks +and promote merging entries collected in The Combine. + +Turn on "Allow data protection override in Merge Duplicates" to allow project users in Merge Duplicates to manually +override protection of words and senses. If anybody tries to merge or delete a protected entry or senses, The Combine +warns them of the fields that will be lost. #### 存档项目 diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 759ed34594..40fd67697c 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -275,9 +275,16 @@ "on": "On", "hint": "In Data Entry, suggest existing Vernaculars similar to the Vernacular being typed." }, - "protectedDataOverride": { - "hint": "In Merge Duplicates, allow overriding protection of protected words and senses.", - "label": "Protected Data Override" + "protectedData": { + "label": "Protected Data Management", + "avoidInMerge": { + "hint": "In Merge Duplicates, skip sets that only have protected words. This will avoid sets of mature entries imported from FieldWorks and promote merging entries collected in The Combine.", + "label": "Avoid protected sets in Merge Duplicates" + }, + "override": { + "hint": "In Merge Duplicates, allow overriding protection of protected words and senses.", + "label": "Allow data protection override in Merge Duplicates" + } }, "invite": { "inviteByEmailLabel": "Invite by Email", diff --git a/src/api/api/merge-api.ts b/src/api/api/merge-api.ts index 8b719cd2f0..2b098697c1 100644 --- a/src/api/api/merge-api.ts +++ b/src/api/api/merge-api.ts @@ -112,6 +112,7 @@ export const MergeApiAxiosParamCreator = function ( * @param {string} projectId * @param {number} maxInList * @param {number} maxLists + * @param {boolean} ignoreProtected * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -119,6 +120,7 @@ export const MergeApiAxiosParamCreator = function ( projectId: string, maxInList: number, maxLists: number, + ignoreProtected: boolean, options: any = {} ): Promise => { // verify required parameter 'projectId' is not null or undefined @@ -127,11 +129,21 @@ export const MergeApiAxiosParamCreator = function ( assertParamExists("findPotentialDuplicates", "maxInList", maxInList); // verify required parameter 'maxLists' is not null or undefined assertParamExists("findPotentialDuplicates", "maxLists", maxLists); + // verify required parameter 'ignoreProtected' is not null or undefined + assertParamExists( + "findPotentialDuplicates", + "ignoreProtected", + ignoreProtected + ); const localVarPath = - `/v1/projects/{projectId}/merge/finddups/{maxInList}/{maxLists}` + `/v1/projects/{projectId}/merge/finddups/{maxInList}/{maxLists}/{ignoreProtected}` .replace(`{${"projectId"}}`, encodeURIComponent(String(projectId))) .replace(`{${"maxInList"}}`, encodeURIComponent(String(maxInList))) - .replace(`{${"maxLists"}}`, encodeURIComponent(String(maxLists))); + .replace(`{${"maxLists"}}`, encodeURIComponent(String(maxLists))) + .replace( + `{${"ignoreProtected"}}`, + encodeURIComponent(String(ignoreProtected)) + ); // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); let baseOptions; @@ -519,6 +531,7 @@ export const MergeApiFp = function (configuration?: Configuration) { * @param {string} projectId * @param {number} maxInList * @param {number} maxLists + * @param {boolean} ignoreProtected * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -526,6 +539,7 @@ export const MergeApiFp = function (configuration?: Configuration) { projectId: string, maxInList: number, maxLists: number, + ignoreProtected: boolean, options?: any ): Promise< (axios?: AxiosInstance, basePath?: string) => AxiosPromise @@ -535,6 +549,7 @@ export const MergeApiFp = function (configuration?: Configuration) { projectId, maxInList, maxLists, + ignoreProtected, options ); return createRequestFunction( @@ -744,6 +759,7 @@ export const MergeApiFactory = function ( * @param {string} projectId * @param {number} maxInList * @param {number} maxLists + * @param {boolean} ignoreProtected * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -751,10 +767,17 @@ export const MergeApiFactory = function ( projectId: string, maxInList: number, maxLists: number, + ignoreProtected: boolean, options?: any ): AxiosPromise { return localVarFp - .findPotentialDuplicates(projectId, maxInList, maxLists, options) + .findPotentialDuplicates( + projectId, + maxInList, + maxLists, + ignoreProtected, + options + ) .then((request) => request(axios, basePath)); }, /** @@ -903,6 +926,13 @@ export interface MergeApiFindPotentialDuplicatesRequest { * @memberof MergeApiFindPotentialDuplicates */ readonly maxLists: number; + + /** + * + * @type {boolean} + * @memberof MergeApiFindPotentialDuplicates + */ + readonly ignoreProtected: boolean; } /** @@ -1074,6 +1104,7 @@ export class MergeApi extends BaseAPI { requestParameters.projectId, requestParameters.maxInList, requestParameters.maxLists, + requestParameters.ignoreProtected, options ) .then((request) => request(this.axios, this.basePath)); diff --git a/src/api/models/project.ts b/src/api/models/project.ts index 898556b259..335c341d6c 100644 --- a/src/api/models/project.ts +++ b/src/api/models/project.ts @@ -66,6 +66,12 @@ export interface Project { * @memberof Project */ autocompleteSetting: OffOnSetting; + /** + * + * @type {OffOnSetting} + * @memberof Project + */ + protectedDataMergeAvoidEnabled: OffOnSetting; /** * * @type {OffOnSetting} diff --git a/src/backend/index.ts b/src/backend/index.ts index 55080c1fdf..2bb5ceb76e 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -354,10 +354,12 @@ export async function graylistAdd(wordIds: string[]): Promise { /** Start finding list of potential duplicates for merging. */ export async function findDuplicates( maxInList: number, - maxLists: number + maxLists: number, + ignoreProtected = false ): Promise { + const projectId = LocalStorage.getProjectId(); await mergeApi.findPotentialDuplicates( - { projectId: LocalStorage.getProjectId(), maxInList, maxLists }, + { ignoreProtected, maxInList, maxLists, projectId }, defaultOptions() ); } diff --git a/src/components/ProjectSettings/ProjectProtectedData.tsx b/src/components/ProjectSettings/ProjectProtectedData.tsx new file mode 100644 index 0000000000..998e8570b9 --- /dev/null +++ b/src/components/ProjectSettings/ProjectProtectedData.tsx @@ -0,0 +1,90 @@ +import { HelpOutline } from "@mui/icons-material"; +import { MenuItem, Select, Stack, Tooltip, Typography } from "@mui/material"; +import { type ReactElement } from "react"; +import { useTranslation } from "react-i18next"; + +import { OffOnSetting } from "api/models"; +import { type ProjectSettingProps } from "components/ProjectSettings/ProjectSettingsTypes"; + +export default function ProjectProtectedOverride({ + project, + setProject, +}: ProjectSettingProps): ReactElement { + const { t } = useTranslation(); + + const updateProtectMergeAvoidSetting = async ( + protectedDataMergeAvoidEnabled: OffOnSetting + ): Promise => { + await setProject({ ...project, protectedDataMergeAvoidEnabled }); + }; + + const updateProtectOverrideSetting = async ( + protectedDataOverrideEnabled: OffOnSetting + ): Promise => { + await setProject({ ...project, protectedDataOverrideEnabled }); + }; + + return ( + +
+ + + {t("projectSettings.protectedData.avoidInMerge.label")} + + + + + + + + +
+ +
+ + + {t("projectSettings.protectedData.override.label")} + + + + + + + + +
+
+ ); +} diff --git a/src/components/ProjectSettings/ProjectProtectedOverride.tsx b/src/components/ProjectSettings/ProjectProtectedOverride.tsx deleted file mode 100644 index 89b03f82b9..0000000000 --- a/src/components/ProjectSettings/ProjectProtectedOverride.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { HelpOutline } from "@mui/icons-material"; -import { Grid, MenuItem, Select, Tooltip } from "@mui/material"; -import { type ReactElement } from "react"; -import { useTranslation } from "react-i18next"; - -import { OffOnSetting } from "api/models"; -import { type ProjectSettingProps } from "components/ProjectSettings/ProjectSettingsTypes"; - -export default function ProjectProtectedOverride( - props: ProjectSettingProps -): ReactElement { - const { t } = useTranslation(); - - const updateProtectOverrideSetting = async ( - protectedDataOverrideEnabled: OffOnSetting - ): Promise => { - await props.setProject({ ...props.project, protectedDataOverrideEnabled }); - }; - - return ( - - - - - - - - - - - ); -} diff --git a/src/components/ProjectSettings/index.tsx b/src/components/ProjectSettings/index.tsx index 92d36342f9..2f1111b527 100644 --- a/src/components/ProjectSettings/index.tsx +++ b/src/components/ProjectSettings/index.tsx @@ -54,7 +54,7 @@ import ProjectLanguages, { SemanticDomainLanguage, } from "components/ProjectSettings/ProjectLanguages"; import ProjectName from "components/ProjectSettings/ProjectName"; -import ProjectProtectedOverride from "components/ProjectSettings/ProjectProtectedOverride"; +import ProjectProtectedData from "components/ProjectSettings/ProjectProtectedData"; import ProjectSchedule from "components/ProjectSettings/ProjectSchedule"; import ProjectSelect from "components/ProjectSettings/ProjectSelect"; import ActiveProjectUsers from "components/ProjectUsers/ActiveProjectUsers"; @@ -179,13 +179,13 @@ export default function ProjectSettingsComponent(): ReactElement { /> )} - {/* Protected data override toggle */} + {/* Protected data management */} {permissions.includes(Permission.DeleteEditSettingsAndUsers) && ( } - title={t("projectSettings.protectedDataOverride.label")} + title={t("projectSettings.protectedData.label")} body={ - diff --git a/src/goals/Redux/GoalActions.ts b/src/goals/Redux/GoalActions.ts index e5409d9ce0..f1feef31ea 100644 --- a/src/goals/Redux/GoalActions.ts +++ b/src/goals/Redux/GoalActions.ts @@ -1,6 +1,6 @@ import { Action, PayloadAction } from "@reduxjs/toolkit"; -import { MergeUndoIds, Word } from "api/models"; +import { MergeUndoIds, OffOnSetting, Word } from "api/models"; import * as Backend from "backend"; import { getCurrentUser, getProjectId } from "backend/localStorage"; import { CharInvChanges } from "goals/CharacterInventory/CharacterInventoryTypes"; @@ -72,7 +72,7 @@ export function updateStepFromData(): Action { // Dispatch Functions export function asyncAddGoal(goal: Goal) { - return async (dispatch: StoreStateDispatch) => { + return async (dispatch: StoreStateDispatch, getState: () => StoreState) => { const userEditId = getUserEditId(); if (userEditId) { dispatch(setCurrentGoal(goal)); @@ -86,7 +86,12 @@ export function asyncAddGoal(goal: Goal) { if (goal.goalType === GoalType.MergeDups) { // Initialize data loading in the backend. dispatch(setDataLoadStatus(DataLoadStatus.Loading)); - await Backend.findDuplicates(5, maxNumSteps(goal.goalType)); + const currentProj = getState().currentProjectState.project; + await Backend.findDuplicates( + 5, // More than 5 entries doesn't fit well. + maxNumSteps(goal.goalType), + currentProj.protectedDataMergeAvoidEnabled === OffOnSetting.On + ); // Don't load goal data, since it'll be triggered by a signal from the backend when data is ready. } else { // Load the goal data, but don't await, to allow a loading screen. diff --git a/src/types/project.ts b/src/types/project.ts index 0bd6b05350..e881961ffd 100644 --- a/src/types/project.ts +++ b/src/types/project.ts @@ -11,6 +11,7 @@ export function newProject(name = ""): Project { definitionsEnabled: false, grammaticalInfoEnabled: false, autocompleteSetting: OffOnSetting.On, + protectedDataMergeAvoidEnabled: OffOnSetting.Off, protectedDataOverrideEnabled: OffOnSetting.Off, semanticDomains: [], semDomWritingSystem: newWritingSystem(),