From bacdfcdba3dafc7c20f66e504b2aa9458ead53ae Mon Sep 17 00:00:00 2001 From: Alex Eng Date: Tue, 17 Mar 2015 16:05:44 +1000 Subject: [PATCH] Implement merge translation from existing version: https://bugzilla.redhat.com/show_bug.cgi?id=1133989 Squashed commit of the following: commit 59b3e0449a48abbdb76929534a26ea9a045bf790 Merge: 7f09ae2 4ea3f29 Author: Alex Eng Date: Tue Mar 17 15:47:28 2015 +1000 Merge branch 'integration/master' into rhbz1133989 Conflicts: zanata-war/src/main/resources/db/changelogs/db.changelog-3.7.xml commit 7f09ae2a06aa02c9a942597215170f7a4a8481c7 Author: Alex Eng Date: Tue Mar 17 13:51:51 2015 +1000 Limit to maintainer's projects to merge from in UI. commit 2e96c942b4f2fd71dea8fd5f6725c40d09116cf9 Author: Alex Eng Date: Mon Mar 16 16:09:57 2015 +1000 Disable merge button if there's no version available to copy commit 8be5f3606c74ffbf7532dd92524b28c5d7684f74 Author: Alex Eng Date: Mon Mar 16 13:07:07 2015 +1000 Update docs printscreen and messages commit f8680c0263d19b916850b612f669e15c2906c88c Author: Alex Eng Date: Mon Mar 16 11:23:42 2015 +1000 Add docs for merge-trans feature commit 1b6e2b2a6c48d18984153cf491edcab470c429f3 Author: Alex Eng Date: Mon Mar 16 06:43:19 2015 +1000 Fix unit test commit 4e7d7bebc74c88a98dffbfc1d9f29cb07239df04 Author: Alex Eng Date: Sun Mar 15 14:43:26 2015 +1000 Fix load source versions for merge trans dialog commit 9e7c75df3d7e96df1106b52a4150a6696e395fb9 Merge: f7eaffb 541fc6c Author: Alex Eng Date: Sun Mar 15 14:23:55 2015 +1000 Merge branch 'integration/master' into rhbz1133989 commit f7eaffb27a1def5a427865cd3fb08e53951a197d Author: Alex Eng Date: Sun Mar 15 14:21:10 2015 +1000 Refactor and changes on PR review commit 6d4c388833248785d3b19da52a0bc35d9b65ff00 Author: Alex Eng Date: Fri Mar 13 16:38:32 2015 +1000 Fix unit test commit 032013279dfaa040571b46b1ebb2eb821bab2820 Merge: f0419d1 baef61a Author: Alex Eng Date: Fri Mar 13 15:59:58 2015 +1000 Merge branch 'integration/master' into rhbz1133989 commit f0419d1e34877b75d82f12ded3fc2ff0427662b7 Author: Alex Eng Date: Fri Mar 13 15:29:04 2015 +1000 Fix checkstyle commit 16d58ba99edbf748ae3e59b670e9b53984cf5c1c Author: Alex Eng Date: Fri Mar 13 15:10:15 2015 +1000 Fixed project selector issue for merge trans commit 5d1ce6f68ab50f01da3af966d688bf48efee5853 Author: Alex Eng Date: Thu Mar 12 15:57:35 2015 +1000 WIP: implement project selector for merge translation dialog commit fa50a891bc03a86ac5677735d24109ac243dcd75 Author: Alex Eng Date: Thu Mar 12 14:09:04 2015 +1000 Refactor query, UI and test commit 43e9f0736fa4b68bd0cedbb829d068502c14661c Author: Alex Eng Date: Wed Mar 11 18:32:42 2015 +1000 Fix preload of progressBar component commit 43bf35defdce8f9884c9a2cfff4ed9b0fc3b72b0 Author: Alex Eng Date: Wed Mar 11 16:02:20 2015 +1000 Rename js name unique commit d42b55873f4eba08708f4bdcaaccf2d43880a743 Author: Alex Eng Date: Tue Mar 10 15:57:38 2015 +1000 Create loading component for copy actions commit 511ed109830792cd98df57415a829b0a761c4380 Author: Alex Eng Date: Tue Mar 10 15:26:42 2015 +1000 Create modal for merge-trans operations from version page commit c05f4e6d1cda679ef926ed1b5b4b795b787243aa Author: Alex Eng Date: Mon Mar 9 15:13:12 2015 +1000 Add unit test, fix shouldMerge to check for same content commit b8065176d8d69f506cbe9ef0a7ef3a2c04a8c270 Author: Alex Eng Date: Fri Mar 6 16:34:55 2015 +1000 WIP: add unit test for merge translation commit f8705176892deca12f548c33f47202eab9074dce Author: Alex Eng Date: Fri Mar 6 11:10:21 2015 +1000 Add unit test for merge translations commit fd06390f48f423b6d1ab4d9a4fc7c52c0d4cf156 Author: Alex Eng Date: Thu Mar 5 15:38:07 2015 +1000 Work in progress: add time log commit 16b295dbc94eaa458c63b94d86b78f2f199ac122 Author: Alex Eng Date: Thu Mar 5 15:04:49 2015 +1000 Implement merge translation from and to existing version: https://bugzilla.redhat.com/show_bug.cgi?id=1133989 --- docs/images/version-merge-trans-cancel.png | Bin 0 -> 14647 bytes docs/images/version-merge-trans-dialog.png | Bin 0 -> 51915 bytes docs/images/version-merge-trans-progress.png | Bin 0 -> 39598 bytes docs/images/version-more-action-menu.png | Bin 0 -> 39289 bytes docs/release-notes.md | 4 + docs/user-guide/merge-translations.md | 59 ++++ mkdocs.yml | 1 + .../org/zanata/model/HTextFlowTarget.java | 19 ++ .../zanata/model/HTextFlowTargetHistory.java | 17 +- .../org/zanata/action/CopyTransAction.java | 49 +-- .../org/zanata/action/CopyVersionManager.java | 31 +- .../org/zanata/action/MergeTransAction.java | 238 ++++++++++++++ .../action/MergeTranslationsManager.java | 114 +++++++ .../org/zanata/action/VersionHomeAction.java | 51 +-- .../async/handle/CopyVersionTaskHandle.java | 6 - .../handle/MergeTranslationsTaskHandle.java | 42 +++ .../main/java/org/zanata/dao/ProjectDAO.java | 34 +- .../main/java/org/zanata/dao/TextFlowDAO.java | 79 +++++ .../service/MergeTranslationsService.java | 54 ++++ .../zanata/service/impl/CopyTransWork.java | 34 +- .../service/impl/CopyVersionServiceImpl.java | 6 + .../impl/MergeTranslationsServiceImpl.java | 278 ++++++++++++++++ .../service/impl/MergeTranslationsWork.java | 160 ++++++++++ .../main/java/org/zanata/ui/CopyAction.java | 56 ++++ .../main/java/org/zanata/ui/ProgressBar.java | 38 +-- .../org/zanata/util/MessageGenerator.java | 117 +++++++ .../db/changelogs/db.changelog-3.7.xml | 14 + .../src/main/resources/messages.properties | 24 +- .../layout/version/merge_trans_modal.xhtml | 171 ++++++++++ zanata-war/src/main/webapp/WEB-INF/pages.xml | 11 + .../src/main/webapp/iteration/view.xhtml | 232 ++++++-------- .../webapp/resources/zanata/checkbox.xhtml | 10 +- .../webapp/resources/zanata/progressBar.xhtml | 32 +- .../zanata/version-copy-action-loader.xhtml | 70 ++++ .../java/org/zanata/dao/TextFlowDAOTest.java | 64 ++++ .../MergeTranslationsServiceImplTest.java | 298 ++++++++++++++++++ .../model/MergeTranslationsData.dbunit.xml | 158 ++++++++++ 37 files changed, 2307 insertions(+), 264 deletions(-) create mode 100644 docs/images/version-merge-trans-cancel.png create mode 100644 docs/images/version-merge-trans-dialog.png create mode 100644 docs/images/version-merge-trans-progress.png create mode 100644 docs/images/version-more-action-menu.png create mode 100644 docs/user-guide/merge-translations.md create mode 100644 zanata-war/src/main/java/org/zanata/action/MergeTransAction.java create mode 100644 zanata-war/src/main/java/org/zanata/action/MergeTranslationsManager.java create mode 100644 zanata-war/src/main/java/org/zanata/async/handle/MergeTranslationsTaskHandle.java create mode 100644 zanata-war/src/main/java/org/zanata/service/MergeTranslationsService.java create mode 100644 zanata-war/src/main/java/org/zanata/service/impl/MergeTranslationsServiceImpl.java create mode 100644 zanata-war/src/main/java/org/zanata/service/impl/MergeTranslationsWork.java create mode 100644 zanata-war/src/main/java/org/zanata/ui/CopyAction.java create mode 100644 zanata-war/src/main/java/org/zanata/util/MessageGenerator.java create mode 100644 zanata-war/src/main/webapp/WEB-INF/layout/version/merge_trans_modal.xhtml create mode 100644 zanata-war/src/main/webapp/resources/zanata/version-copy-action-loader.xhtml create mode 100644 zanata-war/src/test/java/org/zanata/service/impl/MergeTranslationsServiceImplTest.java create mode 100644 zanata-war/src/test/resources/org/zanata/test/model/MergeTranslationsData.dbunit.xml diff --git a/docs/images/version-merge-trans-cancel.png b/docs/images/version-merge-trans-cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..8eddac8038fe5d84a9c2b581d68bf273878a9c94 GIT binary patch literal 14647 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 m^d8> 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 Vurznx3K-=8#MsiP{QBL;{{arH$QS?s literal 0 HcmV?d00001 diff --git a/docs/images/version-more-action-menu.png b/docs/images/version-more-action-menu.png new file mode 100644 index 0000000000000000000000000000000000000000..538ebd23c5f6489c1e9304978a89c3690cd560ba GIT binary patch literal 39289 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 literal 0 HcmV?d00001 diff --git a/docs/release-notes.md b/docs/release-notes.md index 1f1a0ad33b..9ac276c082 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -21,6 +21,10 @@ property can safely be reverted or removed before subsequent startups. ----------------------- +
New Features
+ +* [1133989](https://bugzilla.redhat.com/show_bug.cgi?id=1133989) - Copy translations from existing version. + ## 3.6
New Editor (Alpha)
diff --git a/docs/user-guide/merge-translations.md b/docs/user-guide/merge-translations.md new file mode 100644 index 0000000000..727b19a0dc --- /dev/null +++ b/docs/user-guide/merge-translations.md @@ -0,0 +1,59 @@ +# Merge translations from another project version + +Merge translations allows project maintainers to copy translations across different project version based on matching context. (See below for context matching rules) + +## Restrictions + +- This feature is only available to project maintainers. +- Only translations that are in translated/approved state will be used. +- Merge translation can only be run if there are no other copy operations in progress for the selected version, such as copy-trans or copy version. + +## Rule of which translations will be copied over + + + + + + + + + + + + + + + + +
**From****To****Copy?**
fuzzy/untranslatedanyNo
different source text/document id/
resId/msgctxt/locale
anyNo
translated/approveduntranslated/fuzzyYes
translated/approvedtranslated/approvedcopy if `From` is newer and
`Keep existing translated/approved translations` is unchecked
+ +## Run Merge translations + +1. Login to Zanata. +1. Select a project version you wish to copy translations to. +1. Expand `More Action` menu on top right corner and click on `Merge Translations`. This action is only available if you are a maintainer of the project. +
+ More action menu in project version page +
+1. In displayed dialog, select the project-version you wish to copy translations from. +
+ Merge translation dialog +
+1. If you do not want to overwrite translated/approved translations, ensure that `Keep existing translated/approved translations` is checked. If this option is not checked, translated/approved translations will be replaced with newer translated/approved translations if they are available. +1. Click `Merge Translations` button to start the process. +1. The progress of the merge process is shown by a progress bar on the version page. +
+ Merge translation in progress +
+ +## Cancel Merge translation +**_Note: This will only stop additional translations being merged. Any translations that have already been merged will remain merged._** + +1. Go to the progress bar section in project version page. +1. Click on the `Cancel` button on top right panel. +
+ Cancel merge translation in progress +
+ + + diff --git a/mkdocs.yml b/mkdocs.yml index 100dc065fd..6a4d99d3f9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -27,6 +27,7 @@ pages: - ['user-guide/keyboard-shortcuts.md', 'User Guide', 'Keyboard Shortcuts'] - ['user-guide/project-types.md', 'User Guide', 'Project Types'] - ['user-guide/client-configuration.md', 'User Guide', 'Client Configuration'] +- ['user-guide/merge-translations.md', 'User Guide', 'Merge translations'] ## Project - ['user-guide/projects/create-project.md', 'User Guide', 'Creating a Project'] - ['user-guide/projects/project-settings.md', 'User Guide', 'Project settings'] diff --git a/zanata-model/src/main/java/org/zanata/model/HTextFlowTarget.java b/zanata-model/src/main/java/org/zanata/model/HTextFlowTarget.java index 3ead98305d..58e865e23e 100644 --- a/zanata-model/src/main/java/org/zanata/model/HTextFlowTarget.java +++ b/zanata-model/src/main/java/org/zanata/model/HTextFlowTarget.java @@ -46,6 +46,7 @@ import javax.validation.constraints.NotNull; import lombok.AccessLevel; +import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -117,10 +118,25 @@ public class HTextFlowTarget extends ModelEntityBase implements HasContents, private List reviewComments; + @Getter + private String revisionComment; + + private boolean revisionCommentSet = false; + // Only for internal use (persistence transient) @Setter(AccessLevel.PRIVATE) private Integer oldVersionNum; + public void setRevisionComment(String revisionComment) { + this.revisionComment = revisionComment; + revisionCommentSet = true; + } + + @Transient + boolean isRevisionCommentSet() { + return revisionCommentSet; + } + // Only for internal use (persistence transient) @Setter(AccessLevel.PRIVATE) private HTextFlowTargetHistory initialState; @@ -411,6 +427,9 @@ private void preUpdate(HTextFlowTarget tft) { // insert history if this has changed from its initial state if (tft.initialState != null && tft.initialState.hasChanged(tft)) { tft.getHistory().put(tft.oldVersionNum, tft.initialState); + if (!tft.isRevisionCommentSet()) { + tft.setRevisionComment(null); + } } } diff --git a/zanata-model/src/main/java/org/zanata/model/HTextFlowTargetHistory.java b/zanata-model/src/main/java/org/zanata/model/HTextFlowTargetHistory.java index 8b1029537c..35e8c28ffb 100644 --- a/zanata-model/src/main/java/org/zanata/model/HTextFlowTargetHistory.java +++ b/zanata-model/src/main/java/org/zanata/model/HTextFlowTargetHistory.java @@ -46,6 +46,8 @@ import org.zanata.common.ContentState; import com.google.common.base.Objects; +import lombok.Getter; +import lombok.Setter; @Entity @Immutable @@ -105,6 +107,10 @@ public static String getQueryNameMatchingHistory(int size) { private HPerson reviewer; + @Getter + @Setter + private String revisionComment; + public HTextFlowTargetHistory() { } @@ -115,9 +121,11 @@ public HTextFlowTargetHistory(HTextFlowTarget target) { this.textFlowRevision = target.getTextFlowRevision(); this.textFlowTarget = target; this.versionNum = target.getVersionNum(); - translator = target.getTranslator(); - reviewer = target.getReviewer(); + this.translator = target.getTranslator(); + this.reviewer = target.getReviewer(); this.setContents(target.getContents()); + this.revisionComment = target.getRevisionComment(); + } @Id @@ -246,12 +254,13 @@ public boolean hasChanged(HTextFlowTarget current) { this.lastModifiedBy) || !Objects.equal(current.getTranslator(), this.translator) || !Objects.equal(current.getReviewer(), this.reviewer) - || !Objects.equal(current.getState(), this.state) + || !Objects.equal(current.getState(), this.state) || !Objects.equal(current.getTextFlowRevision(), this.textFlowRevision) || !Objects.equal(current.getLastChanged(), this.lastChanged) || !Objects.equal(current.getTextFlow().getId(), this.textFlowTarget.getId()) - || !Objects.equal(current.getVersionNum(), this.versionNum); + || !Objects.equal(current.getVersionNum(), this.versionNum) + || !Objects.equal(current.getRevisionComment(), this.revisionComment); } } diff --git a/zanata-war/src/main/java/org/zanata/action/CopyTransAction.java b/zanata-war/src/main/java/org/zanata/action/CopyTransAction.java index 2b97959264..da133d01cf 100644 --- a/zanata-war/src/main/java/org/zanata/action/CopyTransAction.java +++ b/zanata-war/src/main/java/org/zanata/action/CopyTransAction.java @@ -30,6 +30,8 @@ import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.security.Restrict; +import org.jboss.seam.faces.FacesMessages; +import org.jboss.seam.international.StatusMessage; import org.zanata.async.handle.CopyTransTaskHandle; import org.zanata.dao.ProjectIterationDAO; import org.zanata.i18n.Messages; @@ -37,7 +39,7 @@ import org.zanata.model.HProjectIteration; import org.zanata.seam.scope.ConversationScopeMessages; import org.zanata.service.impl.CopyTransOptionFactory; -import org.zanata.ui.ProgressBar; +import org.zanata.ui.CopyAction; import org.zanata.util.DateUtil; import com.google.common.base.Optional; @@ -52,7 +54,7 @@ */ @Name("copyTransAction") @Scope(ScopeType.CONVERSATION) -public class CopyTransAction implements Serializable, ProgressBar { +public class CopyTransAction extends CopyAction implements Serializable { private static final long serialVersionUID = 1L; @In @@ -86,35 +88,40 @@ public void onCreate() { .getExplicitOptions()); } - @Override public boolean isInProgress() { return copyTransManager.isCopyTransRunning(getProjectIteration()); } + @Override + public String getProgressMessage() { + StringBuilder message = new StringBuilder(); + message.append( + msgs.format("jsf.iteration.CopyTrans.processedItems", + getCurrentProgress(), getMaxProgress())) + .append(", ") + .append(msgs.format( + "jsf.iteration.CopyTrans.estimatedTimeRemaining", + getCopyTransEstimatedTimeLeft())); + + return message.toString(); + } + + @Override + public void onComplete() { + FacesMessages.instance().add(StatusMessage.Severity.INFO, + msgs.format("jsf.iteration.CopyTrans.Completed", + getProjectSlug(), getIterationSlug())); + } + @Begin(join = true) public void updateCopyTrans(String action, String value) { copyTransOptionsModel.update(action, value); } @Override - public String getCompletedPercentage() { - CopyTransTaskHandle handle = - copyTransManager - .getCopyTransProcessHandle(getProjectIteration()); - if (handle != null) { - double completedPercent = - (double) handle.getCurrentProgress() / (double) handle - .getMaxProgress() * 100; - if (Double.compare(completedPercent, 100) == 0) { - conversationScopeMessages - .setMessage( - FacesMessage.SEVERITY_INFO, - msgs.get("jsf.iteration.CopyTrans.Completed")); - } - return PERCENT_FORMAT.format(completedPercent); - } else { - return "0"; - } + protected CopyTransTaskHandle getHandle() { + return copyTransManager + .getCopyTransProcessHandle(getProjectIteration()); } public HProjectIteration getProjectIteration() { diff --git a/zanata-war/src/main/java/org/zanata/action/CopyVersionManager.java b/zanata-war/src/main/java/org/zanata/action/CopyVersionManager.java index 6769251552..d8ca47b6e7 100644 --- a/zanata-war/src/main/java/org/zanata/action/CopyVersionManager.java +++ b/zanata-war/src/main/java/org/zanata/action/CopyVersionManager.java @@ -18,8 +18,9 @@ import org.zanata.service.CopyVersionService; /** + * Manages copy version tasks. + * * @author Alex Eng
aeng@redhat.com - * @author Carlos Munoz aeng@redhat.com */ @AutoCreate @Name("copyVersionManager") @@ -35,6 +36,16 @@ public class CopyVersionManager implements Serializable { @In private Identity identity; + /** + * Copy existing version to new version + * + * @param projectSlug + * - existing project identifier + * @param versionSlug + * - existing version identifier + * @param newVersionSlug + * - new version identifier + */ public void startCopyVersion(String projectSlug, String versionSlug, String newVersionSlug) { CopyVersionKey key = CopyVersionKey.getKey(projectSlug, newVersionSlug); @@ -44,6 +55,14 @@ public void startCopyVersion(String projectSlug, String versionSlug, newVersionSlug, handle); } + /** + * Cancel running copy version task + * + * @param projectSlug + * - target project identifier + * @param versionSlug + * - target version identifier + */ public void cancelCopyVersion(String projectSlug, String versionSlug) { if (isCopyVersionRunning(projectSlug, versionSlug)) { CopyVersionTaskHandle handle = @@ -68,11 +87,21 @@ public boolean isCopyVersionRunning(String projectSlug, String versionSlug) { return handle != null && !handle.isDone(); } + /** + * Key used for copy version task + * + * @param projectSlug + * - target project identifier + * @param versionSlug + * - target version identifier + */ @EqualsAndHashCode @Getter @AllArgsConstructor public static final class CopyVersionKey implements Serializable { + // target project identifier private final String projectSlug; + // target version identifier private final String versionSlug; public static CopyVersionKey getKey(String projectSlug, diff --git a/zanata-war/src/main/java/org/zanata/action/MergeTransAction.java b/zanata-war/src/main/java/org/zanata/action/MergeTransAction.java new file mode 100644 index 0000000000..f9734e9564 --- /dev/null +++ b/zanata-war/src/main/java/org/zanata/action/MergeTransAction.java @@ -0,0 +1,238 @@ +package org.zanata.action; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; + +import javax.faces.application.FacesMessage; + +import com.google.common.collect.Lists; +import lombok.Getter; +import lombok.Setter; + +import org.apache.commons.lang.StringUtils; +import org.jboss.seam.ScopeType; +import org.jboss.seam.annotations.In; +import org.jboss.seam.annotations.Name; +import org.jboss.seam.annotations.Scope; +import org.jboss.seam.faces.FacesMessages; +import org.jboss.seam.international.StatusMessage; +import org.jboss.seam.security.management.JpaIdentityStore; +import org.zanata.async.handle.MergeTranslationsTaskHandle; +import org.zanata.common.EntityStatus; +import org.zanata.dao.ProjectDAO; +import org.zanata.dao.ProjectIterationDAO; +import org.zanata.i18n.Messages; +import org.zanata.model.HAccount; +import org.zanata.model.HProject; +import org.zanata.model.HProjectIteration; +import org.zanata.ui.CopyAction; + +/** + * Handles user interaction from merge_trans_modal.xhtml. + * - start merge translation process. + * - cancel merge translation process. + * - gives progress data of merge translation. + * - provides projects and versions for user selection. + * + * see merge_trans_modal.xhtml for all actions. + * + * @author Alex Eng aeng@redhat.com + */ +@Name("mergeTransAction") +@Scope(ScopeType.PAGE) +public class MergeTransAction extends CopyAction implements Serializable { + + @Getter + private String targetProjectSlug; + + @Getter + @Setter + private String targetVersionSlug; + + @Getter + private String sourceProjectSlug; + + @Getter + @Setter + private String sourceVersionSlug; + + @Getter + @Setter + private boolean keepExistingTranslation; + + @In + private ProjectDAO projectDAO; + + @In + private ProjectIterationDAO projectIterationDAO; + + @In + private MergeTranslationsManager mergeTranslationsManager; + + @In + private CopyTransManager copyTransManager; + + @In + private CopyVersionManager copyVersionManager; + + @In + private Messages msgs; + + @In(required = false, value = JpaIdentityStore.AUTHENTICATED_USER) + private HAccount authenticatedAccount; + + private HProjectIteration targetVersion; + + private HProject sourceProject; + + public void setTargetProjectSlug(String targetProjectSlug) { + this.targetProjectSlug = targetProjectSlug; + + /** + * This should only true during first instantiation to make sure + * sourceProject from selection are the same as targetProject on load. + * + * See pages.xml for setter of mergeTransAction.targetProjectSlug. + */ + if(sourceProjectSlug == null) { + setSourceProjectSlug(targetProjectSlug); + } + } + + public void setSourceProjectSlug(String sourceProjectSlug) { + if(!StringUtils.equals(this.sourceProjectSlug, sourceProjectSlug)) { + this.sourceProjectSlug = sourceProjectSlug; + refreshSourceProject(); + this.sourceVersionSlug = null; + if (!getSourceProject().getProjectIterations().isEmpty()) { + this.sourceVersionSlug = + getSourceProject().getProjectIterations().get(0).getSlug(); + } + } + } + + private void refreshSourceProject() { + sourceProject = null; + } + + public HProjectIteration getTargetVersion() { + if (targetVersion == null && StringUtils.isNotEmpty(targetProjectSlug) + && StringUtils.isNotEmpty(targetVersionSlug)) { + targetVersion = + projectIterationDAO.getBySlug(targetProjectSlug, + targetVersionSlug); + } + return targetVersion; + } + + public HProject getSourceProject() { + if(sourceProject == null && StringUtils.isNotEmpty(sourceProjectSlug)) { + sourceProject = projectDAO.getBySlug(sourceProjectSlug); + } + return sourceProject; + } + + public List getSourceVersions() { + List versions = + getSourceProject().getProjectIterations(); + + if(versions.isEmpty()) { + return Collections.emptyList(); + } + + List results = Lists.newArrayList(); + //remove obsolete version and target version if both are the same project + for (HProjectIteration version : versions) { + if (version.getStatus().equals(EntityStatus.OBSOLETE)) { + continue; + } + if(StringUtils.equals(sourceProjectSlug, targetProjectSlug) && + version.getSlug().equals(targetVersionSlug)) { + continue; + } + results.add(version); + } + return results; + } + + /** + * Only display user maintained project to merge translation from in this + * UI. TODO: implement filterable drop down and allow users to select any + * available project. + * + */ + public List getProjects() { + return projectDAO.getProjectsForMaintainer( + authenticatedAccount.getPerson(), null, 0, Integer.MAX_VALUE); + } + + public void startMergeTranslations() { + if (StringUtils.isEmpty(sourceProjectSlug) + || StringUtils.isEmpty(sourceVersionSlug) + || StringUtils.isEmpty(targetProjectSlug) + || StringUtils.isEmpty(targetVersionSlug)) { + FacesMessages.instance().add(StatusMessage.Severity.ERROR, + msgs.get("jsf.iteration.mergeTrans.noSourceAndTarget")); + return; + } + if (isCopyActionsRunning()) { + FacesMessages.instance().add(StatusMessage.Severity.WARN, + msgs.get("jsf.iteration.mergeTrans.hasCopyActionRunning")); + return; + } + mergeTranslationsManager.start(sourceProjectSlug, + sourceVersionSlug, targetProjectSlug, targetVersionSlug, + !keepExistingTranslation); + } + + // Check if copy-trans, copy version or merge-trans is running for the + // target version + public boolean isCopyActionsRunning() { + return mergeTranslationsManager.isRunning( + targetProjectSlug, targetVersionSlug) + || copyVersionManager.isCopyVersionRunning(targetProjectSlug, + targetVersionSlug) || + copyTransManager.isCopyTransRunning(getTargetVersion()); + } + + @Override + public boolean isInProgress() { + return mergeTranslationsManager.isRunning( + targetProjectSlug, targetVersionSlug); + } + + @Override + public String getProgressMessage() { + MergeTranslationsTaskHandle handle = getHandle(); + if(handle != null) { + return msgs.format("jsf.iteration.mergeTrans.progress.message", + handle.getCurrentProgress(), handle.getTotalTranslations()); + } + return ""; + } + + @Override + public void onComplete() { + FacesMessages.instance().add(StatusMessage.Severity.INFO, + msgs.format("jsf.iteration.mergeTrans.completed.message", + sourceProjectSlug, sourceVersionSlug, + targetProjectSlug, targetVersionSlug)); + } + + public void cancel() { + mergeTranslationsManager.cancel(targetProjectSlug, + targetVersionSlug); + FacesMessages.instance().add( + FacesMessage.SEVERITY_INFO, + msgs.format("jsf.iteration.mergeTrans.cancel.message", + sourceProjectSlug, sourceVersionSlug, + targetProjectSlug, targetVersionSlug)); + } + + @Override + protected MergeTranslationsTaskHandle getHandle() { + return mergeTranslationsManager.getProcessHandle( + targetProjectSlug, targetVersionSlug); + } +} diff --git a/zanata-war/src/main/java/org/zanata/action/MergeTranslationsManager.java b/zanata-war/src/main/java/org/zanata/action/MergeTranslationsManager.java new file mode 100644 index 0000000000..6e2e516be6 --- /dev/null +++ b/zanata-war/src/main/java/org/zanata/action/MergeTranslationsManager.java @@ -0,0 +1,114 @@ +package org.zanata.action; + +import java.io.Serializable; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import org.jboss.seam.ScopeType; +import org.jboss.seam.annotations.AutoCreate; +import org.jboss.seam.annotations.In; +import org.jboss.seam.annotations.Name; +import org.jboss.seam.annotations.Scope; +import org.jboss.seam.security.Identity; +import org.zanata.async.AsyncTaskHandleManager; +import org.zanata.async.handle.MergeTranslationsTaskHandle; +import org.zanata.service.MergeTranslationsService; + +/** + * Manages tasks to copy translations from one existing version to another. + * + * @author Alex Eng aeng@redhat.com + */ +@AutoCreate +@Name("mergeTranslationsManager") +@Scope(ScopeType.STATELESS) +@Slf4j +public class MergeTranslationsManager implements Serializable { + @In + private AsyncTaskHandleManager asyncTaskHandleManager; + + @In + private MergeTranslationsService mergeTranslationsServiceImpl; + + @In + private Identity identity; + + /** + * Merge translations from an existing version to another. + * + * @param sourceProjectSlug - source project identifier + * @param sourceVersionSlug - source version identifier + * @param targetProjectSlug - target project identifier + * @param targetVersionSlug - target version identifier + * @param useNewerTranslation - to override translated/approved string + * in target with newer entry in source + */ + public void start(String sourceProjectSlug, String sourceVersionSlug, + String targetProjectSlug, String targetVersionSlug, + boolean useNewerTranslation) { + + Key key = + Key.getKey(targetProjectSlug, + targetVersionSlug); + + MergeTranslationsTaskHandle handle = new MergeTranslationsTaskHandle(); + asyncTaskHandleManager.registerTaskHandle(handle, key); + mergeTranslationsServiceImpl.startMergeTranslations(sourceProjectSlug, + sourceVersionSlug, targetProjectSlug, targetVersionSlug, + useNewerTranslation, handle); + } + + /** + * Cancel running merge translations task + * + * @param projectSlug - target project identifier + * @param versionSlug - target version identifier + */ + public void cancel(String projectSlug, String versionSlug) { + if (isRunning(projectSlug, versionSlug)) { + MergeTranslationsTaskHandle handle = + getProcessHandle(projectSlug, versionSlug); + handle.cancel(true); + handle.setCancelledTime(System.currentTimeMillis()); + handle.setCancelledBy(identity.getCredentials().getUsername()); + + log.info("Merge translations cancelled- {}:{}", projectSlug, + versionSlug); + } + } + + public MergeTranslationsTaskHandle getProcessHandle( + String projectSlug, String versionSlug) { + return (MergeTranslationsTaskHandle) asyncTaskHandleManager + .getHandleByKey( + Key.getKey(projectSlug, versionSlug)); + } + + public boolean isRunning(String projectSlug, String versionSlug) { + MergeTranslationsTaskHandle handle = + getProcessHandle(projectSlug, versionSlug); + return handle != null && !handle.isDone(); + } + + /** + * Key used for copy version task + * + */ + @EqualsAndHashCode + @Getter + @AllArgsConstructor + public static final class Key implements Serializable { + // target project identifier + private final String projectSlug; + // target version identifier + private final String versionSlug; + + public static Key getKey(String projectSlug, + String versionSlug) { + return new Key(projectSlug, versionSlug); + } + } +} diff --git a/zanata-war/src/main/java/org/zanata/action/VersionHomeAction.java b/zanata-war/src/main/java/org/zanata/action/VersionHomeAction.java index b6b5b66d8f..a8488eeb15 100644 --- a/zanata-war/src/main/java/org/zanata/action/VersionHomeAction.java +++ b/zanata-war/src/main/java/org/zanata/action/VersionHomeAction.java @@ -42,6 +42,7 @@ import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.security.Restrict; +import org.jboss.seam.faces.FacesMessages; import org.jboss.seam.util.Hex; import org.zanata.async.handle.CopyVersionTaskHandle; import org.zanata.common.DocumentType; @@ -77,8 +78,8 @@ import org.zanata.service.VersionStateCache; import org.zanata.ui.AbstractListFilter; import org.zanata.ui.AbstractSortAction; +import org.zanata.ui.CopyAction; import org.zanata.ui.InMemoryListFilter; -import org.zanata.ui.ProgressBar; import org.zanata.ui.model.statistic.WordStatistic; import org.zanata.util.DateUtil; import org.zanata.util.ServiceLocator; @@ -107,6 +108,12 @@ public class VersionHomeAction extends AbstractSortAction implements @In private CopyVersionManager copyVersionManager; + @In + private MergeTranslationsManager mergeTranslationsManager; + + @In + private CopyTransManager copyTransManager; + @In private ProjectIterationDAO projectIterationDAO; @@ -297,11 +304,6 @@ public void setProjectSlug(String projectSlug) { copyVersionHandler.setProjectSlug(projectSlug); } - public void onCopyVersionComplete() { - conversationScopeMessages.setMessage(FacesMessage.SEVERITY_INFO, - msgs.format("jsf.copyVersion.Completed", versionSlug)); - } - public void cancelCopyVersion() { copyVersionManager.cancelCopyVersion(projectSlug, versionSlug); conversationScopeMessages.setMessage(FacesMessage.SEVERITY_INFO, @@ -309,7 +311,7 @@ public void cancelCopyVersion() { } @NoArgsConstructor - public static class CopyVersionHandler implements ProgressBar { + public static class CopyVersionHandler extends CopyAction { @Setter private String projectSlug; @@ -324,16 +326,15 @@ public boolean isInProgress() { } @Override - public String getCompletedPercentage() { - CopyVersionTaskHandle handle = getHandle(); - if (handle != null) { - double completedPercent = - (double) handle.getCurrentProgress() / (double) handle - .getMaxProgress() * 100; - return PERCENT_FORMAT.format(completedPercent); - } else { - return "0"; - } + public String getProgressMessage() { + return getMessages().format("jsf.copyVersion.processedDocuments", + getProcessedDocuments(), getTotalDocuments()); + } + + @Override + public void onComplete() { + FacesMessages.instance().add(FacesMessage.SEVERITY_INFO, + getMessages().format("jsf.copyVersion.Completed", versionSlug)); } public int getProcessedDocuments() { @@ -352,12 +353,16 @@ public int getTotalDocuments() { return 0; } + private Messages getMessages() { + return ServiceLocator.instance().getInstance(Messages.class); + } + private CopyVersionManager getCopyVersionManager() { return ServiceLocator.instance().getInstance( CopyVersionManager.class); } - private CopyVersionTaskHandle getHandle() { + protected CopyVersionTaskHandle getHandle() { CopyVersionManager copyVersionManager = ServiceLocator .instance().getInstance(CopyVersionManager.class); @@ -910,6 +915,16 @@ public String decodeDocId(String docId) { return UrlUtil.decodeString(docId); } + // Check if copy-trans, copy version or merge-trans is running for given + // version + public boolean isCopyActionsRunning() { + return mergeTranslationsManager.isRunning( + projectSlug, versionSlug) + || copyVersionManager.isCopyVersionRunning(projectSlug, + versionSlug) || + copyTransManager.isCopyTransRunning(getVersion()); + } + public void uploadTranslationFile(HLocale hLocale) { identity.checkPermission("modify-translation", hLocale, getVersion() .getProject()); diff --git a/zanata-war/src/main/java/org/zanata/async/handle/CopyVersionTaskHandle.java b/zanata-war/src/main/java/org/zanata/async/handle/CopyVersionTaskHandle.java index 5b1b00cb13..16eb3c97b1 100644 --- a/zanata-war/src/main/java/org/zanata/async/handle/CopyVersionTaskHandle.java +++ b/zanata-war/src/main/java/org/zanata/async/handle/CopyVersionTaskHandle.java @@ -41,12 +41,6 @@ public class CopyVersionTaskHandle extends AsyncTaskHandle { private long cancelledTime; @Getter @Setter private String triggeredBy; - @Getter - private boolean prepared; - - public void setPrepared() { - this.prepared = true; - } /** * Increments the processed document count by 1 diff --git a/zanata-war/src/main/java/org/zanata/async/handle/MergeTranslationsTaskHandle.java b/zanata-war/src/main/java/org/zanata/async/handle/MergeTranslationsTaskHandle.java new file mode 100644 index 0000000000..9c09d75d8c --- /dev/null +++ b/zanata-war/src/main/java/org/zanata/async/handle/MergeTranslationsTaskHandle.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015, Red Hat, Inc. and individual contributors as indicated by the + * @author tags. See the copyright.txt file in the distribution for a full + * listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this software; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF + * site: http://www.fsf.org. + */ +package org.zanata.async.handle; + +import lombok.Getter; +import lombok.Setter; + +import org.zanata.async.AsyncTaskHandle; + +/** + * Asynchronous task handle for the merge translations process. + * + * @author Alex Eng aeng@redhat.com + */ +public class MergeTranslationsTaskHandle extends AsyncTaskHandle { + @Getter @Setter + private int totalTranslations; + @Getter @Setter + private String cancelledBy; + @Getter @Setter + private long cancelledTime; + @Getter @Setter + private String triggeredBy; +} diff --git a/zanata-war/src/main/java/org/zanata/dao/ProjectDAO.java b/zanata-war/src/main/java/org/zanata/dao/ProjectDAO.java index 40c5968313..c207be1284 100644 --- a/zanata-war/src/main/java/org/zanata/dao/ProjectDAO.java +++ b/zanata-war/src/main/java/org/zanata/dao/ProjectDAO.java @@ -31,6 +31,8 @@ import org.zanata.model.HProject; import org.zanata.model.HProjectIteration; +import com.google.common.collect.Lists; + @Name("projectDAO") @AutoCreate @Scope(ScopeType.STATELESS) @@ -71,11 +73,11 @@ public List getProjectMaintainerBySlug(String slug) { @SuppressWarnings("unchecked") public List getOffsetListOrderByName(int offset, int count, - boolean filterActive, boolean filterReadOnly, - boolean filterObsolete) { + boolean filterOutActive, boolean filterOutReadOnly, + boolean filterOutObsolete) { String condition = - constructFilterCondition(filterActive, filterReadOnly, - filterObsolete); + constructFilterCondition(filterOutActive, filterOutReadOnly, + filterOutObsolete); Query q = getSession().createQuery( "from HProject p " + condition @@ -85,12 +87,12 @@ public List getProjectMaintainerBySlug(String slug) { return q.list(); } - public int getFilterProjectSize(boolean filterActive, - boolean filterReadOnly, boolean filterObsolete) { + public int getFilterProjectSize(boolean filterOutActive, + boolean filterOutReadOnly, boolean filterOutObsolete) { String query = "select count(*) from HProject p " - + constructFilterCondition(filterActive, - filterReadOnly, filterObsolete); + + constructFilterCondition(filterOutActive, + filterOutReadOnly, filterOutObsolete); Query q = getSession().createQuery(query.toString()); q.setCacheable(true).setComment("ProjectDAO.getFilterProjectSize"); Long totalCount = (Long) q.uniqueResult(); @@ -100,21 +102,21 @@ public int getFilterProjectSize(boolean filterActive, return totalCount.intValue(); } - private String constructFilterCondition(boolean filterActive, - boolean filterReadOnly, boolean filterObsolete) { + private String constructFilterCondition(boolean filterOutActive, + boolean filterOutReadOnly, boolean filterOutObsolete) { StringBuilder condition = new StringBuilder(); - if (filterActive || filterReadOnly || filterObsolete) { + if (filterOutActive || filterOutReadOnly || filterOutObsolete) { condition.append("where "); } - if (filterActive) { + if (filterOutActive) { // TODO bind this as a parameter condition.append("p.status <> '" + EntityStatus.ACTIVE.getInitial() + "' "); } - if (filterReadOnly) { - if (filterActive) { + if (filterOutReadOnly) { + if (filterOutActive) { condition.append("and "); } @@ -123,8 +125,8 @@ private String constructFilterCondition(boolean filterActive, + EntityStatus.READONLY.getInitial() + "' "); } - if (filterObsolete) { - if (filterActive || filterReadOnly) { + if (filterOutObsolete) { + if (filterOutActive || filterOutReadOnly) { condition.append("and "); } diff --git a/zanata-war/src/main/java/org/zanata/dao/TextFlowDAO.java b/zanata-war/src/main/java/org/zanata/dao/TextFlowDAO.java index 16e3a1bba7..5a1228073b 100644 --- a/zanata-war/src/main/java/org/zanata/dao/TextFlowDAO.java +++ b/zanata-war/src/main/java/org/zanata/dao/TextFlowDAO.java @@ -21,6 +21,7 @@ package org.zanata.dao; import java.util.ArrayList; +import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -33,6 +34,7 @@ import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Scope; +import org.zanata.common.ContentState; import org.zanata.common.LocaleId; import org.zanata.model.HDocument; import org.zanata.model.HLocale; @@ -300,4 +302,81 @@ public Map getByDocumentAndResIds(HDocument document, return builder.build(); } + /** + * Return text flows that have matching document id and content, resId + * between the given source and target version + * + * @param sourceVersionId + * @param targetVersionId + * @param offset + * @param maxResults + */ + public List getSourceByMatchedContext(Long sourceVersionId, + Long targetVersionId, int offset, int maxResults) { + String queryString = generateSourceByMatchedContext(false); + + Query query = getSession() + .createQuery(queryString) + .setParameter("sourceVersionId", sourceVersionId) + .setParameter("targetVersionId" , targetVersionId) + .setMaxResults(maxResults) + .setFirstResult(offset) + .setCacheable(true) + .setComment("TextFlowDAO.getTranslationsByMatchedContext"); + + List results = Lists.newArrayList(); + for(Object result: query.list()) { + Object[] castResults = (Object[]) result; + results.add(new HTextFlow[] { (HTextFlow) castResults[0], + (HTextFlow) castResults[1] }); + } + return results; + } + + public int getSourceByMatchedContextCount(Long sourceVersionId, + Long targetVersionId) { + String queryString = generateSourceByMatchedContext(true); + Query query = getSession() + .createQuery(queryString) + .setParameter("sourceVersionId", sourceVersionId) + .setParameter("targetVersionId" , targetVersionId) + .setCacheable(true) + .setComment("TextFlowDAO.getTranslationsByMatchedContextCount"); + Long count = (Long) query.uniqueResult(); + return count == null ? 0 : count.intValue(); + } + + /** + * Generate query string for text flows that have matching document id and + * content between the given source and target version + * + * @param getRecordCount - if true, generate select count(*) hql. + * + */ + private String generateSourceByMatchedContext(boolean getRecordCount) { + StringBuilder queryBuilder = new StringBuilder(); + + if(getRecordCount) { + queryBuilder.append( + "select count(fromTF.id) from HTextFlow fromTF, HTextFlow toTF "); + } else { + queryBuilder.append( + "select fromTF, toTF from HTextFlow fromTF, HTextFlow toTF "); + } + queryBuilder + .append("where fromTF.document.projectIteration.id = :sourceVersionId ") + .append("and toTF.document.projectIteration.id = :targetVersionId ") + .append("and fromTF.obsolete = false ") + .append("and fromTF.document.obsolete = false ") + .append("and toTF.obsolete = false ") + .append("and toTF.document.obsolete = false ") + .append("and fromTF <> toTF ") + .append("and fromTF.contentHash = toTF.contentHash ") + .append("and fromTF.resId = toTF.resId ") + .append("and fromTF.document.docId = toTF.document.docId"); + + return queryBuilder.toString(); + + } + } diff --git a/zanata-war/src/main/java/org/zanata/service/MergeTranslationsService.java b/zanata-war/src/main/java/org/zanata/service/MergeTranslationsService.java new file mode 100644 index 0000000000..a2366286fa --- /dev/null +++ b/zanata-war/src/main/java/org/zanata/service/MergeTranslationsService.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015, Red Hat, Inc. and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.zanata.service; + +import java.util.concurrent.Future; + +import org.zanata.async.handle.MergeTranslationsTaskHandle; +import org.zanata.model.HProjectIteration; + +public interface MergeTranslationsService { + //@formatter:off + /** + * Starts a background merge translations of a version to another. + * + * @param sourceProjectSlug - source project identifier + * @param sourceVersionSlug - source version identifier + * @param targetProjectSlug - target project identifier + * @param targetVersionSlug - target version identifier + * @param useNewerTranslation - to override translated/approved string in target with newer entry in source + * @param handle - task handler for merge translation + */ + //@formatter:on + Future startMergeTranslations(String sourceProjectSlug, + String sourceVersionSlug, String targetProjectSlug, + String targetVersionSlug, boolean useNewerTranslation, + MergeTranslationsTaskHandle handle); + + /** + * Return total count of translations to be processed. + * + * @param sourceVersionId - source HProjectIteration id + * @param targetVersionId - target HProjectIteration id + */ + int getTotalProgressCount(HProjectIteration sourceVersion, + HProjectIteration targetVersion); +} diff --git a/zanata-war/src/main/java/org/zanata/service/impl/CopyTransWork.java b/zanata-war/src/main/java/org/zanata/service/impl/CopyTransWork.java index 96bae801c1..3e227587b6 100644 --- a/zanata-war/src/main/java/org/zanata/service/impl/CopyTransWork.java +++ b/zanata-war/src/main/java/org/zanata/service/impl/CopyTransWork.java @@ -16,6 +16,7 @@ import org.zanata.service.TranslationFinder; import org.zanata.service.ValidationService; import org.zanata.service.VersionStateCache; +import org.zanata.util.MessageGenerator; import org.zanata.webtrans.shared.model.ValidationAction; import com.google.common.base.Optional; import com.google.common.base.Stopwatch; @@ -179,23 +180,6 @@ protected Integer work() throws Exception { return numCopied; } - private String createComment(HTextFlowTarget target) { - String author; - HDocument document = target.getTextFlow().getDocument(); - String projectname = - document.getProjectIteration().getProject().getName(); - String version = document.getProjectIteration().getSlug(); - String documentid = document.getDocId(); - if (target.getLastModifiedBy() != null) { - author = ", author " + target.getLastModifiedBy().getName(); - } else { - author = ""; - } - - return "translation auto-copied from project " + projectname - + ", version " + version + ", document " + documentid + author; - } - private void saveCopyTransMatch(final HTextFlowTarget matchingTarget, final HTextFlow originalTf, final HCopyTransOptions options, final boolean requireTranslationReview) { @@ -264,12 +248,18 @@ public Boolean get() { } hTarget.setContents(matchingTarget.getContents()); hTarget.setState(copyState); - HSimpleComment hcomment = hTarget.getComment(); - if (hcomment == null) { - hcomment = new HSimpleComment(); - hTarget.setComment(hcomment); + if(matchingTarget.getComment() == null) { + hTarget.setComment(null); + } else { + HSimpleComment hComment = hTarget.getComment(); + if (hComment == null) { + hComment = new HSimpleComment(); + hTarget.setComment(hComment); + } + hComment.setComment(matchingTarget.getComment().getComment()); } - hcomment.setComment(createComment(matchingTarget)); + hTarget.setRevisionComment(MessageGenerator + .getCopyTransMessage(matchingTarget)); // TODO Maybe we should think about registering a Hibernate // integrator for these updates diff --git a/zanata-war/src/main/java/org/zanata/service/impl/CopyVersionServiceImpl.java b/zanata-war/src/main/java/org/zanata/service/impl/CopyVersionServiceImpl.java index 7327c836a2..380a7ed764 100644 --- a/zanata-war/src/main/java/org/zanata/service/impl/CopyVersionServiceImpl.java +++ b/zanata-war/src/main/java/org/zanata/service/impl/CopyVersionServiceImpl.java @@ -22,6 +22,7 @@ import org.zanata.model.HLocale; import org.zanata.model.HProjectIteration; import org.zanata.model.HRawDocument; +import org.zanata.model.HSimpleComment; import org.zanata.model.HTextFlow; import org.zanata.model.HTextFlowTarget; import org.zanata.model.HTextFlowTargetHistory; @@ -33,6 +34,7 @@ import org.zanata.service.CopyVersionService; import org.zanata.service.VersionStateCache; import org.zanata.util.JPACopier; +import org.zanata.util.MessageGenerator; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -350,6 +352,10 @@ JPACopier. copyBean(tft, "textFlow", "reviewComments", "history"); copy.setTextFlow(newTf); copy.setTextFlowRevision(newTf.getRevision()); + if(tft.getComment() != null) { + copy.setComment(new HSimpleComment(tft.getComment().getComment())); + } + copy.setRevisionComment(MessageGenerator.getCopyVersionMessage(tft)); // copy review comment copy.setReviewComments(Lists diff --git a/zanata-war/src/main/java/org/zanata/service/impl/MergeTranslationsServiceImpl.java b/zanata-war/src/main/java/org/zanata/service/impl/MergeTranslationsServiceImpl.java new file mode 100644 index 0000000000..ba8929bd17 --- /dev/null +++ b/zanata-war/src/main/java/org/zanata/service/impl/MergeTranslationsServiceImpl.java @@ -0,0 +1,278 @@ +/* + * Copyright 2015, Red Hat, Inc. and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.zanata.service.impl; + +import java.util.List; +import java.util.concurrent.Future; + +import javax.annotation.Nonnull; + +import lombok.extern.slf4j.Slf4j; + +import org.jboss.seam.ScopeType; +import org.jboss.seam.annotations.In; +import org.jboss.seam.annotations.Name; +import org.jboss.seam.annotations.Scope; +import org.zanata.async.Async; +import org.zanata.async.AsyncTaskResult; +import org.zanata.async.ContainsAsyncMethods; +import org.zanata.async.handle.MergeTranslationsTaskHandle; +import org.zanata.dao.ProjectIterationDAO; +import org.zanata.dao.TextFlowDAO; +import org.zanata.model.HLocale; +import org.zanata.model.HProjectIteration; +import org.zanata.model.HTextFlowTarget; +import org.zanata.security.ZanataIdentity; +import org.zanata.service.LocaleService; +import org.zanata.service.MergeTranslationsService; + +import com.google.common.base.Optional; +import com.google.common.base.Stopwatch; +import org.zanata.service.TranslationStateCache; +import org.zanata.service.VersionStateCache; + +/** + * Service provider for merge translations task. + * + * @see org.zanata.action.MergeTranslationsManager + * + * @author Alex Eng aeng@redhat.com + */ +@Name("mergeTranslationsServiceImpl") +@Scope(ScopeType.STATELESS) +@Slf4j +@ContainsAsyncMethods +public class MergeTranslationsServiceImpl implements MergeTranslationsService { + + @In + private ProjectIterationDAO projectIterationDAO; + + @In + private TextFlowDAO textFlowDAO; + + @In + private ZanataIdentity identity; + + @In + private VersionStateCache versionStateCacheImpl; + + @In + private TranslationStateCache translationStateCacheImpl; + + @In + private LocaleService localeServiceImpl; + + private final static int TRANSLATION_BATCH_SIZE = 10; + + @Override + @Async + public Future startMergeTranslations(String sourceProjectSlug, + String sourceVersionSlug, String targetProjectSlug, + String targetVersionSlug, boolean useNewerTranslation, + MergeTranslationsTaskHandle handle) { + + HProjectIteration sourceVersion = + projectIterationDAO.getBySlug(sourceProjectSlug, + sourceVersionSlug); + + if (sourceVersion == null) { + log.error("Cannot find source version of {}:{}", sourceProjectSlug, + sourceVersionSlug); + return AsyncTaskResult.taskResult(); + } + + HProjectIteration targetVersion = + projectIterationDAO.getBySlug(targetProjectSlug, + targetVersionSlug); + + if (targetVersion == null) { + log.error("Cannot find target version of {}:{}", targetProjectSlug, + targetVersionSlug); + return AsyncTaskResult.taskResult(); + } + + if (isVersionsEmpty(sourceVersion, targetVersion)) { + return AsyncTaskResult.taskResult(); + } + + if (getSupportedLocales(targetProjectSlug, targetVersionSlug).isEmpty()) { + log.error("No locales enabled in target version of {} [{}]", + targetProjectSlug, targetVersionSlug); + return AsyncTaskResult.taskResult(); + } + + Optional taskHandleOpt = + Optional.fromNullable(handle); + + if (taskHandleOpt.isPresent()) { + prepareMergeTranslationsHandle(sourceVersion, targetVersion, + taskHandleOpt.get()); + } + + Stopwatch overallStopwatch = Stopwatch.createStarted(); + log.info("merge translations start: from {} to {}", sourceProjectSlug + + ":" + sourceVersionSlug, targetProjectSlug + ":" + + targetVersionSlug); + + int startCount = 0; + int totalCount = getTotalMatchCount(sourceVersion.getId(), + targetVersion.getId()); + + List supportedLocales = getSupportedLocales(targetVersion + .getProject().getSlug(), targetVersion.getSlug()); + + while (startCount < totalCount) { + int processedCount = + mergeTranslationBatch(sourceVersion, targetVersion, + supportedLocales, useNewerTranslation, startCount, + TRANSLATION_BATCH_SIZE); + if (taskHandleOpt.isPresent()) { + taskHandleOpt.get().increaseProgress(processedCount); + } + + startCount += TRANSLATION_BATCH_SIZE; + textFlowDAO.clear(); + } + versionStateCacheImpl.clearVersionStatsCache(targetVersion.getId()); + log.info("merge translation end: from {} to {}, {}", sourceProjectSlug + + ":" + sourceVersionSlug, targetProjectSlug + ":" + + targetVersionSlug, overallStopwatch); + + return AsyncTaskResult.taskResult(); + } + + protected int mergeTranslationBatch(HProjectIteration sourceVersion, + HProjectIteration targetVersion, List supportedLocales, + boolean useNewerTranslation, int offset, int batchSize) { + try { + return new MergeTranslationsWork(sourceVersion.getId(), + targetVersion.getId(), offset, batchSize, + useNewerTranslation, supportedLocales, + textFlowDAO, translationStateCacheImpl) + .workInTransaction(); + } catch (Exception e) { + log.warn("exception during copy text flow target", e); + return 0; + } + } + + /** + * Check if sourceVersion or targetVersion has source document. + * + * @param sourceVersion + * @param targetVersion + */ + private boolean isVersionsEmpty(HProjectIteration sourceVersion, + HProjectIteration targetVersion) { + if (sourceVersion.getDocuments().isEmpty()) { + log.error("No documents in source version {}:{}", sourceVersion + .getProject().getSlug(), sourceVersion.getSlug()); + return true; + } + if (targetVersion.getDocuments().isEmpty()) { + log.error("No documents in target version {}:{}", targetVersion + .getProject().getSlug(), targetVersion.getSlug()); + return true; + } + return false; + } + + private void prepareMergeTranslationsHandle( + @Nonnull HProjectIteration sourceVersion, + @Nonnull HProjectIteration targetVersion, + @Nonnull MergeTranslationsTaskHandle handle) { + handle.setTriggeredBy(identity.getAccountUsername()); + + int total = getTotalProgressCount(sourceVersion, targetVersion); + handle.setMaxProgress(total); + handle.setTotalTranslations(total); + } + + @Override + public int getTotalProgressCount(HProjectIteration sourceVersion, + HProjectIteration targetVersion) { + int matchCount = getTotalMatchCount(sourceVersion.getId(), + targetVersion.getId()); + + List locales = + getSupportedLocales(targetVersion.getProject().getSlug(), + targetVersion.getSlug()); + + return matchCount * locales.size(); + } + + private int getTotalMatchCount(Long sourceVersionId, Long targetVersionId) { + return textFlowDAO.getSourceByMatchedContextCount( + sourceVersionId, targetVersionId); + } + + public List getSupportedLocales(String projectSlug, + String versionSlug) { + return localeServiceImpl.getSupportedLanguageByProjectIteration( + projectSlug, versionSlug); + } + + // @formatter:off + /** + * Rule of which translation should merge + * | from | to | copy? | + * |-----------------------|------------------|-----------| + * |fuzzy/untranslated | any | no | + * |-----------------------|------------------|-----------| + * |different source text/ | | | + * |docId/locale/resId | any | no | + * |-----------------------|------------------|-----------| + * |translated/approved | untranslated | yes | + * |-----------------------|------------------|-----------| + * |translated/approved | fuzzy | yes | + * |-----------------------|------------------|-----------| + * |translated/approved | same as from | copy if from is newer + * and option says to copy + * + * @param sourceTft - matched documentId, source text, + * translated/approved HTextFlowTarget. + * @see org.zanata.dao.TextFlowDAO#getSourceByMatchedContext + * @param targetTft - HTextFlowTarget from target version + */ + // @formatter:on + public static boolean shouldMerge(HTextFlowTarget sourceTft, + HTextFlowTarget targetTft, boolean useNewerTranslation) { + // should NOT merge is source tft is not translated/approved + if (!sourceTft.getState().isTranslated()) { + return false; + } + + // should merge if target is not in translated/approved state + if (!targetTft.getState().isTranslated()) { + return true; + } + + // should NOT merge if both state and contents are the same + if (sourceTft.getState().equals(targetTft.getState()) + && sourceTft.getContents().equals(targetTft.getContents())) { + return false; + } + + // if both in translated state return latest if enabled + return useNewerTranslation && sourceTft.getLastChanged().after( + targetTft.getLastChanged()); + } +} diff --git a/zanata-war/src/main/java/org/zanata/service/impl/MergeTranslationsWork.java b/zanata-war/src/main/java/org/zanata/service/impl/MergeTranslationsWork.java new file mode 100644 index 0000000000..b51939a255 --- /dev/null +++ b/zanata-war/src/main/java/org/zanata/service/impl/MergeTranslationsWork.java @@ -0,0 +1,160 @@ +/* + * Copyright 2015, Red Hat, Inc. and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.zanata.service.impl; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import org.jboss.seam.core.Events; +import org.jboss.seam.util.Work; +import org.zanata.common.ContentState; +import org.zanata.dao.TextFlowDAO; +import org.zanata.events.TextFlowTargetStateEvent; +import org.zanata.model.HLocale; +import org.zanata.model.HSimpleComment; +import org.zanata.model.HTextFlow; +import org.zanata.model.HTextFlowTarget; +import org.zanata.service.TranslationStateCache; +import org.zanata.util.MessageGenerator; + +import com.google.common.base.Stopwatch; + +/** + * Merge translation and persist in transaction. + * + * Merge HTextFlowTargets from HProjectIteration(id=sourceVersionId) to + * HProjectIteration(id=targetVersionId) in batches(batchStart, batchLength) + * + * @see org.zanata.service.impl.MergeTranslationsServiceImpl#startMergeTranslations + * @see org.zanata.service.impl.MergeTranslationsServiceImpl#shouldMerge + * + * returns count of processed translations. + * + * @author Alex Eng aeng@redhat.com + */ +@Slf4j +@AllArgsConstructor +public class MergeTranslationsWork extends Work { + private final Long sourceVersionId; + private final Long targetVersionId; + private final int batchStart; + private final int batchLength; + private final boolean useNewerTranslation; + private final List supportedLocales; + + private final TextFlowDAO textFlowDAO; + + private final TranslationStateCache translationStateCacheImpl; + + private final Stopwatch stopwatch = Stopwatch.createUnstarted(); + + @Override + protected Integer work() throws Exception { + stopwatch.start(); + + List matches = + textFlowDAO.getSourceByMatchedContext( + sourceVersionId, targetVersionId, batchStart, + batchLength); + + for (HTextFlow[] results : matches) { + HTextFlow sourceTf = results[0]; + HTextFlow targetTf = results[1]; + boolean foundChange = false; + + for (HLocale hLocale : supportedLocales) { + HTextFlowTarget sourceTft = + sourceTf.getTargets().get(hLocale.getId()); + // only process translated state + if (sourceTft == null || !sourceTft.getState().isTranslated()) { + continue; + } + + HTextFlowTarget targetTft = + targetTf.getTargets().get(hLocale.getId()); + if (targetTft == null) { + targetTft = new HTextFlowTarget(targetTf, hLocale); + targetTft.setVersionNum(0); + targetTf.getTargets().put(hLocale.getId(), targetTft); + } + + if (MergeTranslationsServiceImpl.shouldMerge(sourceTft, + targetTft, useNewerTranslation)) { + foundChange = true; + mergeTextFlowTarget(sourceTft, targetTft); + } + } + if (foundChange) { + translationStateCacheImpl.clearDocumentStatistics(targetTf + .getDocument().getId()); + textFlowDAO.makePersistent(targetTf); + textFlowDAO.flush(); + } + } + stopwatch.stop(); + log.info("Complete merge translations of {} in {}", matches.size() + * supportedLocales.size(), stopwatch); + return matches.size() * supportedLocales.size(); + } + + private void mergeTextFlowTarget(HTextFlowTarget sourceTft, + HTextFlowTarget targetTft) { + ContentState oldState = targetTft.getState(); + + targetTft.setContents(sourceTft.getContents()); + targetTft.setState(sourceTft.getState()); + targetTft.setLastChanged(sourceTft.getLastChanged()); + targetTft.setLastModifiedBy(sourceTft.getLastModifiedBy()); + targetTft.setTranslator(sourceTft.getTranslator()); + + if (sourceTft.getComment() == null) { + targetTft.setComment(null); + } else { + HSimpleComment hComment = targetTft.getComment(); + if (hComment == null) { + hComment = new HSimpleComment(); + targetTft.setComment(hComment); + } + hComment.setComment(sourceTft.getComment().getComment()); + } + targetTft.setRevisionComment(MessageGenerator + .getMergeTranslationMessage(sourceTft)); + + raiseSuccessEvent(targetTft, oldState); + } + + private void raiseSuccessEvent(HTextFlowTarget targetTft, + ContentState oldState) { + if (Events.exists()) { + TextFlowTargetStateEvent event = + new TextFlowTargetStateEvent(null, targetVersionId, + targetTft.getTextFlow().getDocument().getId(), + targetTft.getTextFlow().getId(), + targetTft.getLocale().getLocaleId(), + targetTft.getId(), targetTft.getState(), oldState); + + Events.instance().raiseTransactionSuccessEvent( + TextFlowTargetStateEvent.EVENT_NAME, event); + } + } +} diff --git a/zanata-war/src/main/java/org/zanata/ui/CopyAction.java b/zanata-war/src/main/java/org/zanata/ui/CopyAction.java new file mode 100644 index 0000000000..c6feda9499 --- /dev/null +++ b/zanata-war/src/main/java/org/zanata/ui/CopyAction.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015, Red Hat, Inc. and individual contributors as indicated by the + * @author tags. See the copyright.txt file in the distribution for a full + * listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this software; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF + * site: http://www.fsf.org. + */ + +package org.zanata.ui; + +import org.zanata.async.AsyncTaskHandle; + +/** + * + * Interface for Seam component - version-copy-action-loader.xhtml + * + * @author Alex Eng aeng@redhat.com + */ +public abstract class CopyAction implements ProgressBar { + + public abstract String getProgressMessage(); + + public abstract void onComplete(); + + protected abstract AsyncTaskHandle getHandle(); + + @Override + public String getCompletedPercentage() { + AsyncTaskHandle handle = getHandle(); + + if (handle != null) { + double completedPercent = + (double) handle.getCurrentProgress() / (double) handle + .getMaxProgress() * 100; + if (Double.compare(completedPercent, 100) == 0) { + onComplete(); + } + return PERCENT_FORMAT.format(completedPercent); + } else { + return "0"; + } + } +} diff --git a/zanata-war/src/main/java/org/zanata/ui/ProgressBar.java b/zanata-war/src/main/java/org/zanata/ui/ProgressBar.java index 402258c39e..a5f7d4877f 100644 --- a/zanata-war/src/main/java/org/zanata/ui/ProgressBar.java +++ b/zanata-war/src/main/java/org/zanata/ui/ProgressBar.java @@ -1,23 +1,22 @@ /* + * Copyright 2015, Red Hat, Inc. and individual contributors as indicated by the + * @author tags. See the copyright.txt file in the distribution for a full + * listing of individual contributors. * - * * Copyright 2014, Red Hat, Inc. and individual contributors as indicated by the - * * @author tags. See the copyright.txt file in the distribution for a full - * * listing of individual contributors. - * * - * * This is free software; you can redistribute it and/or modify it under the - * * terms of the GNU Lesser General Public License as published by the Free - * * Software Foundation; either version 2.1 of the License, or (at your option) - * * any later version. - * * - * * This software is distributed in the hope that it will be useful, but WITHOUT - * * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more - * * details. - * * - * * You should have received a copy of the GNU Lesser General Public License - * * along with this software; if not, write to the Free Software Foundation, - * * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF - * * site: http://www.fsf.org. + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this software; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF + * site: http://www.fsf.org. */ package org.zanata.ui; @@ -25,6 +24,9 @@ import java.text.DecimalFormat; /** + * + * Interface for Seam component - progressBar.xhtml + * * @author Alex Eng aeng@redhat.com */ public interface ProgressBar { diff --git a/zanata-war/src/main/java/org/zanata/util/MessageGenerator.java b/zanata-war/src/main/java/org/zanata/util/MessageGenerator.java new file mode 100644 index 0000000000..85b6d2b075 --- /dev/null +++ b/zanata-war/src/main/java/org/zanata/util/MessageGenerator.java @@ -0,0 +1,117 @@ +/* + * Copyright 2015, Red Hat, Inc. and individual contributors as indicated by the + * @author tags. See the copyright.txt file in the distribution for a full + * listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this software; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF + * site: http://www.fsf.org. + */ + +package org.zanata.util; + +import org.apache.commons.lang.StringUtils; +import org.zanata.model.HDocument; +import org.zanata.model.HTextFlowTarget; + +/** + * Generate messages/comments of Zanata business actions + * + * @author Alex Eng aeng@redhat.com + */ +public class MessageGenerator { + + /** + * Prefix of action type for generated message + */ + public static final String PREFIX_MERGE_TRANS = "Merge translations"; + public static final String PREFIX_COPY_TRANS = "Copy translation"; + public static final String PREFIX_COPY_VERSION = "Copy version"; + + /** + * Create revision comment for translation that is copied by merge + * translation + * + * @see org.zanata.service.MergeTranslationsService + * + * @param tft - HTextFlowTarget to copy from + */ + public static final String getMergeTranslationMessage( + HTextFlowTarget tft) { + HDocument document = tft.getTextFlow().getDocument(); + String author = ""; + if (tft.getLastModifiedBy() != null) { + author = tft.getLastModifiedBy().getName(); + } + + return generateAutoCopiedMessage(PREFIX_MERGE_TRANS, + document.getProjectIteration().getProject().getName(), + document.getProjectIteration() + .getSlug(), document.getDocId(), author); + } + + /** + * Create revision comment for translation that is copied by copy trans + * @see org.zanata.service.CopyTransService + * + * @param tft - HTextFlowTarget to copy from + */ + public static final String getCopyTransMessage(HTextFlowTarget tft) { + HDocument document = tft.getTextFlow().getDocument(); + String author = ""; + if (tft.getLastModifiedBy() != null) { + author = tft.getLastModifiedBy().getName(); + } + return generateAutoCopiedMessage(PREFIX_COPY_TRANS, + document.getProjectIteration() + .getProject().getName(), document.getProjectIteration() + .getSlug(), document.getDocId(), author); + } + + /** + * Create revision comment for translation that is copied by copy version + * @see org.zanata.service.CopyVersionService + * + * @param tft - HTextFlowTarget to copy from + */ + public static final String getCopyVersionMessage(HTextFlowTarget tft) { + HDocument document = tft.getTextFlow().getDocument(); + String author = ""; + if (tft.getLastModifiedBy() != null) { + author = tft.getLastModifiedBy().getName(); + } + return generateAutoCopiedMessage(PREFIX_COPY_VERSION, + document.getProjectIteration().getProject().getName(), + document.getProjectIteration() + .getSlug(), document.getDocId(), author); + } + + private static final String generateAutoCopiedMessage(String prefix, + String projectName, String versionSlug, String docId, String author) { + StringBuilder comment = new StringBuilder(); + + comment.append(prefix + ": translation auto-copied from project ") + .append(projectName) + .append(", version ") + .append(versionSlug) + .append(", document ") + .append(docId); + + if (!StringUtils.isEmpty(author)) { + comment.append(", author ") + .append(author); + } + return comment.toString(); + } +} diff --git a/zanata-war/src/main/resources/db/changelogs/db.changelog-3.7.xml b/zanata-war/src/main/resources/db/changelogs/db.changelog-3.7.xml index 3132fd1809..122e970918 100644 --- a/zanata-war/src/main/resources/db/changelogs/db.changelog-3.7.xml +++ b/zanata-war/src/main/resources/db/changelogs/db.changelog-3.7.xml @@ -32,4 +32,18 @@ + + Add revisionComment to HTextFlowTarget, HTextFlowTargetHistory + + + + + + + + + + + + diff --git a/zanata-war/src/main/resources/messages.properties b/zanata-war/src/main/resources/messages.properties index 2a0a5e5fb9..5ce8ad9d06 100644 --- a/zanata-war/src/main/resources/messages.properties +++ b/zanata-war/src/main/resources/messages.properties @@ -210,7 +210,7 @@ jsf.validation.target=Target jsf.validation.updated=Updated validation {0} to {1}. # as a verb jsf.CopyTrans.run=Copy Translations -jsf.CopyTrans.cancel=Cancel Translation Copy +jsf.CopyTrans.cancel=Stop copying translations # as a noun (the CopyTrans feature) jsf.CopyTrans=Copy Translations jsf.Copytrans.message="Copy Translations" attempts to reuse translations that have been entered in Zanata by matching them with untranslated strings in your project/version. Consequently, "Copy Translations" is best used before translation and review work is initiated on a project. @@ -442,7 +442,7 @@ jsf.iteration.CopyTrans.Help.translated=Translation will be reused and marked as jsf.Start=Start jsf.iteration.CopyTrans.NoDocuments=There are no documents in this project version. jsf.iteration.CopyTrans.Started="Copy Translations" started. -jsf.iteration.CopyTrans.Completed="Copy Translations" completed. +jsf.iteration.CopyTrans.Completed="Copy Translations" for {0} [{1}] is completed. jsf.iteration.CopyTrans.Cancelled="Copy Translations" cancelled. jsf.iteration.CopyTransOpts.tooltip=Help: Set this version's "Copy Translations" settings. jsf.iteration.tooltip.readonly=This version is currently read only @@ -635,9 +635,23 @@ jsf.iteration.NoDocumentInVersion=No documents in this version jsf.iteration.NoLanguagesInVersion=No language in this version jsf.iteration.TranslateOnline=Translate Online jsf.iteration.ViewOnline=View Online - - - +jsf.iteration.mergeTrans.header=Merge translations from an existing version +jsf.iteration.mergeTrans=Merge Translations +jsf.iteration.mergeTrans.description1=All matching translated/approved translations from the source version will be copied to the target version. +jsf.iteration.mergeTrans.description2=If there is an existing translated/approved translation, the newer translation will be used. +jsf.iteration.mergeTrans.toVersion.label=To (Target) +jsf.iteration.mergeTrans.fromVersion.label=From (Source) +jsf.iteration.mergeTrans.keepExistingTranslated=Keep existing translated/approved translations +jsf.iteration.mergeTrans.keepExistingTranslated.Description=Do not replace existing translated/approved translations with newer translations from this source. +jsf.iteration.mergeTrans.completed.message=Merge translations from {0} [{1}] to {2} [{3}] is completed. +jsf.iteration.mergeTrans.progress.message=Processed translation {0} of {1} +jsf.iteration.mergeTrans.cancel.message=Cancelled merge translations from {0} [{1}] to {2} [{3}]. +jsf.iteration.mergeTrans.hasCopyActionRunning=Another copy process is in progress for this version. Please try again after it is completed. +jsf.iteration.mergeTrans.chooseAnotherProject=Choose another project +jsf.iteration.mergeTrans.noSourceAndTarget=No source version or target version. +jsf.iteration.mergeTrans.cancelMerge=Cancel merge translations +jsf.iteration.mergeTrans.noVersionToMerge.heading=No versions available in this project to merge from. +jsf.iteration.mergeTrans.noVersionToMerge.description=Please choose another project. #------ [home] > Projects > [project-id] > [version-id] > Settings ------ jsf.upload.UploadNewDocuments=Upload new documents diff --git a/zanata-war/src/main/webapp/WEB-INF/layout/version/merge_trans_modal.xhtml b/zanata-war/src/main/webapp/WEB-INF/layout/version/merge_trans_modal.xhtml new file mode 100644 index 0000000000..90c98442eb --- /dev/null +++ b/zanata-war/src/main/webapp/WEB-INF/layout/version/merge_trans_modal.xhtml @@ -0,0 +1,171 @@ + diff --git a/zanata-war/src/main/webapp/WEB-INF/pages.xml b/zanata-war/src/main/webapp/WEB-INF/pages.xml index 7218189329..9149309a5d 100644 --- a/zanata-war/src/main/webapp/WEB-INF/pages.xml +++ b/zanata-war/src/main/webapp/WEB-INF/pages.xml @@ -407,6 +407,9 @@ + + + @@ -427,6 +430,14 @@ value="#{versionHome.instance.slug}" /> + + + + + + diff --git a/zanata-war/src/main/webapp/iteration/view.xhtml b/zanata-war/src/main/webapp/iteration/view.xhtml index b7a475d218..cc73f285e5 100644 --- a/zanata-war/src/main/webapp/iteration/view.xhtml +++ b/zanata-war/src/main/webapp/iteration/view.xhtml @@ -94,13 +94,6 @@ fileInput.value === ""; submitButton.disabled = noFileSelected; } - - function onCancelCopyVersion() { - if(confirm('#{msgs['jsf.copyVersion.cancel.confirm']}')) { - cancelCopyVersion(); - refreshStatistics(); - } - } @@ -132,14 +125,33 @@ action="#{versionHomeAction.setPageRendered(true)}"/> + render="copyTrans, copy-trans-buttons"/> + render="copyTrans, copy-trans-buttons"/> + render="version-info, version-more-actions, version-settings, copyVersion, messages"/> + + + + + + + + + @@ -224,90 +236,32 @@ - - - - - - #{msgs['jsf.Cancel']} - - - -

#{msgs['jsf.CopyTrans']}

-
- -
- - - - - - - , - - - -
-
- - - - - - #{msgs['jsf.Cancel']} - - - -

#{msgs['jsf.CopyVersion']}

-
- -
- - - - - - - - - - - -
- -
+ + + + +
@@ -331,13 +285,13 @@ href="#{request.contextPath}/iteration/view/#{versionHomeAction.projectSlug}/#{versionHomeAction.versionSlug}/documents" class="js-url-mod" data-content="#documents"> - - #{msgs['jsf.Documents']} - - #{versionHomeAction.getDocuments().size} - - + + #{msgs['jsf.Documents']} + + #{versionHomeAction.getDocuments().size} + + @@ -346,12 +300,12 @@ href="#{request.contextPath}/iteration/view/#{versionHomeAction.projectSlug}/#{versionHomeAction.versionSlug}/groups" class="js-url-mod" data-content="#groups"> - - #{msgs['jsf.Groups']} - - #{versionHomeAction.groups.size} - - + + #{msgs['jsf.Groups']} + + #{versionHomeAction.groups.size} + + @@ -361,36 +315,47 @@ styleClass="dropdown dropdown--tab dropdown--small dropdown--right dropdown--inline js-dropdown" id="version-more-actions"> + rendered="#{identity.loggedIn}">
+ + + + + xmlns:s="http://jboss.org/schema/seam/taglib" + xmlns:h="http://java.sun.com/jsf/html"> + + @@ -38,8 +43,9 @@ class="form__checkbox__input js-form__checkbox__input"/> + diff --git a/zanata-war/src/main/webapp/resources/zanata/progressBar.xhtml b/zanata-war/src/main/webapp/resources/zanata/progressBar.xhtml index b786753e5a..a8bb0ae9fc 100644 --- a/zanata-war/src/main/webapp/resources/zanata/progressBar.xhtml +++ b/zanata-war/src/main/webapp/resources/zanata/progressBar.xhtml @@ -22,15 +22,9 @@ - - - - - @@ -38,36 +32,34 @@ - - - - diff --git a/zanata-war/src/main/webapp/resources/zanata/version-copy-action-loader.xhtml b/zanata-war/src/main/webapp/resources/zanata/version-copy-action-loader.xhtml new file mode 100644 index 0000000000..49e915d652 --- /dev/null +++ b/zanata-war/src/main/webapp/resources/zanata/version-copy-action-loader.xhtml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + + + #{msgs['jsf.Cancel']} + + +

#{cc.attrs.header}

+
+ +
+ + + + + + + + +
+
+
+
+ diff --git a/zanata-war/src/test/java/org/zanata/dao/TextFlowDAOTest.java b/zanata-war/src/test/java/org/zanata/dao/TextFlowDAOTest.java index d4896bba37..0e57e073fb 100644 --- a/zanata-war/src/test/java/org/zanata/dao/TextFlowDAOTest.java +++ b/zanata-war/src/test/java/org/zanata/dao/TextFlowDAOTest.java @@ -3,18 +3,23 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import java.util.Collection; import java.util.List; import java.util.Map; +import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; +import org.assertj.core.api.Assertions; import org.dbunit.operation.DatabaseOperation; import org.hamcrest.Matchers; import org.hibernate.Session; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import org.zanata.ZanataDbunitJpaTest; +import org.zanata.common.ContentState; import org.zanata.model.HLocale; +import org.zanata.model.HProjectIteration; import org.zanata.model.HTextFlow; import org.zanata.model.HTextFlowTarget; import org.zanata.search.FilterConstraints; @@ -213,4 +218,63 @@ public void testGetTextFlowByDocumentIdWithConstraint() { assertThat(result, Matchers.hasSize(1)); } + @Test + public void testGetTranslationsByMatchedContext() { + + List testOperations = Lists.newArrayList(); + + testOperations.add(new DataSetOperation( + "org/zanata/test/model/ClearAllTables.dbunit.xml", + DatabaseOperation.CLEAN_INSERT)); + testOperations.add(new DataSetOperation( + "org/zanata/test/model/AccountData.dbunit.xml", + DatabaseOperation.CLEAN_INSERT)); + testOperations.add(new DataSetOperation( + "org/zanata/test/model/LocalesData.dbunit.xml", + DatabaseOperation.CLEAN_INSERT)); + testOperations.add(new DataSetOperation( + "org/zanata/test/model/MergeTranslationsData.dbunit.xml", + DatabaseOperation.CLEAN_INSERT)); + + for(DataSetOperation operation: testOperations) { + operation.prepare(this); + } + + executeOperations(testOperations); + + String projectSlug = "sample-project"; + + String fromVersionSlug = "1.0"; + String toVersionSlug = "2.0"; + + ProjectIterationDAO projectIterationDAO = new + ProjectIterationDAO((Session) getEm().getDelegate()); + + HProjectIteration fromVersion = + projectIterationDAO.getBySlug(projectSlug, fromVersionSlug); + Assertions.assertThat(fromVersion).isNotNull(); + + HProjectIteration toVersion = + projectIterationDAO.getBySlug(projectSlug, toVersionSlug); + Assertions.assertThat(toVersion).isNotNull(); + + List results = + dao.getSourceByMatchedContext( + fromVersion.getId(), toVersion.getId(), 0, 100); + + Assertions.assertThat(results).isNotEmpty(); + + for(HTextFlow[] result: results) { + Assertions.assertThat(result[0].getContentHash()).isEqualTo( + result[1].getContentHash()); + Assertions + .assertThat(result[0].getDocument().getDocId()) + .isEqualTo(result[1].getDocument().getDocId()); + Assertions + .assertThat(result[0].getResId()) + .isEqualTo(result[1].getResId()); + Assertions.assertThat(result[0]).isNotEqualTo(result[1]); + } + } + } diff --git a/zanata-war/src/test/java/org/zanata/service/impl/MergeTranslationsServiceImplTest.java b/zanata-war/src/test/java/org/zanata/service/impl/MergeTranslationsServiceImplTest.java new file mode 100644 index 0000000000..a7f5312833 --- /dev/null +++ b/zanata-war/src/test/java/org/zanata/service/impl/MergeTranslationsServiceImplTest.java @@ -0,0 +1,298 @@ +/* + * Copyright 2015, Red Hat, Inc. and individual contributors as indicated by the + * @author tags. See the copyright.txt file in the distribution for a full + * listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this software; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF + * site: http://www.fsf.org. + */ +package org.zanata.service.impl; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.longThat; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.concurrent.Future; + +import org.dbunit.operation.DatabaseOperation; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.zanata.ZanataDbunitJpaTest; +import org.zanata.cache.InfinispanTestCacheContainer; +import org.zanata.common.ContentState; +import org.zanata.dao.ProjectIterationDAO; +import org.zanata.dao.TextFlowDAO; +import org.zanata.dao.TextFlowTargetDAO; +import org.zanata.model.HLocale; +import org.zanata.model.HProjectIteration; +import org.zanata.model.HTextFlow; +import org.zanata.model.HTextFlowTarget; +import org.zanata.rest.editor.service.LocalesService; +import org.zanata.seam.SeamAutowire; +import org.zanata.security.ZanataIdentity; +import org.zanata.service.LocaleService; +import org.zanata.util.MessageGenerator; + +import com.google.common.collect.Lists; + +@Test(groups = { "business-tests" }) +public class MergeTranslationsServiceImplTest extends ZanataDbunitJpaTest { + private SeamAutowire seam = SeamAutowire.instance(); + + @Mock + private ZanataIdentity identity; + + private ProjectIterationDAO projectIterationDAO; + + private TextFlowTargetDAO textFlowTargetDAO; + + private TextFlowDAO textFlowDAO; + + private MergeTranslationsServiceImpl service; + + private final String projectSlug = "sample-project"; + + @Override + protected void prepareDBUnitOperations() { + beforeTestOperations.add(new DataSetOperation( + "org/zanata/test/model/ClearAllTables.dbunit.xml", + DatabaseOperation.CLEAN_INSERT)); + beforeTestOperations.add(new DataSetOperation( + "org/zanata/test/model/AccountData.dbunit.xml", + DatabaseOperation.CLEAN_INSERT)); + beforeTestOperations.add(new DataSetOperation( + "org/zanata/test/model/LocalesData.dbunit.xml", + DatabaseOperation.CLEAN_INSERT)); + beforeTestOperations.add(new DataSetOperation( + "org/zanata/test/model/MergeTranslationsData.dbunit.xml", + DatabaseOperation.CLEAN_INSERT)); + } + + @BeforeMethod + protected void beforeMethod() throws Exception { + MockitoAnnotations.initMocks(this); + seam.reset(); + projectIterationDAO = new ProjectIterationDAO(getSession()); + textFlowTargetDAO = new TextFlowTargetDAO(getSession()); + textFlowDAO = new TextFlowDAO(getSession()); + + service = seam + .use("projectIterationDAO" , projectIterationDAO) + .use("textFlowTargetDAO", textFlowTargetDAO) + .use("textFlowDAO" , textFlowDAO) + .use("entityManager" , getEm()) + .use("session" , getSession()) + .use("identity" , identity) + .use("cacheContainer", new InfinispanTestCacheContainer()) + .useImpl(LocaleServiceImpl.class) + .useImpl(VersionStateCacheImpl.class) + .useImpl(TranslationStateCacheImpl.class) + .ignoreNonResolvable() + .autowire(MergeTranslationsServiceImpl.class); + } + + @Test + public void testMergeVersionNotExist() { + String sourceVersionSlug = "1.0"; + String targetVersionSlug = "non-exist-version"; + + MergeTranslationsServiceImpl spyService = spy(service); + + spyService.startMergeTranslations(projectSlug, + sourceVersionSlug, projectSlug, targetVersionSlug, true, null); + verifyZeroInteractions(identity); + verify(spyService, never()).mergeTranslationBatch(Matchers.any( + HProjectIteration.class), + Matchers.any(HProjectIteration.class), Matchers.anyList(), + Matchers.anyBoolean(), Matchers.anyInt(), Matchers.anyInt()); + } + + @Test + public void testMergeEmptyDoc() { + String sourceVersionSlug = "1.0"; + String targetVersionSlug = "3.0"; + MergeTranslationsServiceImpl spyService = spy(service); + + spyService.startMergeTranslations(projectSlug, + sourceVersionSlug, projectSlug, targetVersionSlug, true, null); + verifyZeroInteractions(identity); + verify(spyService, never()).mergeTranslationBatch(Matchers.any( + HProjectIteration.class), + Matchers.any(HProjectIteration.class), Matchers.anyList(), + Matchers.anyBoolean(), Matchers.anyInt(), Matchers.anyInt()); + } + + @Test + public void testMergeTranslations1() { + String sourceVersionSlug = "1.0"; + String targetVersionSlug = "2.0"; + boolean useNewerTranslation = false; + + HProjectIteration expectedSourceVersion = + projectIterationDAO.getBySlug(projectSlug, sourceVersionSlug); + assertThat(expectedSourceVersion).isNotNull(); + + HProjectIteration expectedTargetVersion = + projectIterationDAO.getBySlug(projectSlug, targetVersionSlug); + assertThat(expectedTargetVersion).isNotNull(); + + List preMergeData = + textFlowDAO.getSourceByMatchedContext( + expectedSourceVersion.getId(), expectedTargetVersion.getId(), 0, + 100); + + List locales = + service.getSupportedLocales(projectSlug, targetVersionSlug); + + List expectedMergeData = Lists.newArrayList(); + for (HTextFlow[] data : preMergeData) { + for(HLocale locale: locales) { + HTextFlowTarget sourceTft = + data[0].getTargets().get(locale.getId()); + HTextFlowTarget targetTft = + data[1].getTargets().get(locale.getId()); + + if(targetTft == null) { + targetTft = textFlowTargetDAO + .getOrCreateTarget(data[1], locale); + } + if (MergeTranslationsServiceImpl.shouldMerge(sourceTft, + targetTft, useNewerTranslation)) { + expectedMergeData.add(new HTextFlowTarget[] { sourceTft, + targetTft }); + } + } + } + + MergeTranslationsServiceImpl spyService = spy(service); + spyService.startMergeTranslations(projectSlug, sourceVersionSlug, + projectSlug, targetVersionSlug, useNewerTranslation, null); + + //entity in preMergeData should be updated after merge process + + verify(spyService).mergeTranslationBatch(Matchers.eq( + expectedSourceVersion), Matchers.eq(expectedTargetVersion), + Matchers.anyList(), Matchers.eq(useNewerTranslation), + Matchers.anyInt(), Matchers.anyInt()); + + // check all results has same contents and states + // check generated comments in [1] + // check non translated/approved is not being used + // check use latest translated if enabled + for(HTextFlowTarget[] data: expectedMergeData) { + assertThat(data[0].getState()).isIn(ContentState.TRANSLATED_STATES); + assertThat(data[1].getState()).isIn(ContentState.TRANSLATED_STATES); + assertThat(data[0].getContents()).isEqualTo(data[1].getContents()); + assertThat(data[0].getState()).isEqualTo(data[1].getState()); + assertThat(data[1].getRevisionComment()).contains( + MessageGenerator.PREFIX_MERGE_TRANS); + } + } + + @Test + public void testMergeTranslationWorkIsNotTranslated1() { + Date now = new Date(); + HTextFlowTarget target1 = generateTarget(ContentState.NeedReview, now, "string1"); + HTextFlowTarget target2 = generateTarget(ContentState.Translated, now, "string2"); + + testShouldMergeCondition(target1, target2, false, false); + } + + @Test + public void testMergeTranslationWorkIsNotTranslated2() { + Date now = new Date(); + HTextFlowTarget target1 = generateTarget(ContentState.Translated, now, "string1"); + HTextFlowTarget target2 = generateTarget(ContentState.NeedReview, now, "string2"); + + testShouldMergeCondition(target1, target2, false, true); + } + + // target tft is has same modify date as source tft + @Test + public void testMergeTranslationWorkCheckDate1() { + Date now = new Date(); + HTextFlowTarget target1 = generateTarget(ContentState.Translated, now, "string1"); + HTextFlowTarget target2 = generateTarget(ContentState.Translated, now, "string2"); + + testShouldMergeCondition(target1, target2, false, false); + } + + // target tft is newer than source tft + @Test + public void testMergeTranslationWorkCheckDate2() { + Calendar c = new GregorianCalendar(); + HTextFlowTarget target1 = + generateTarget(ContentState.Translated, c.getTime(), "string1"); + c.add(Calendar.DATE, 30); + HTextFlowTarget target2 = + generateTarget(ContentState.Translated, c.getTime(), "string2"); + + testShouldMergeCondition(target1, target2, true, false); + } + + // target tft is older than source tft + @Test + public void testMergeTranslationWorkCheckDate3() { + Calendar c = new GregorianCalendar(); + HTextFlowTarget target2 = + generateTarget(ContentState.Translated, c.getTime(), "string1"); + c.add(Calendar.DATE, 30); + HTextFlowTarget target1 = + generateTarget(ContentState.Translated, c.getTime(), "string2"); + + testShouldMergeCondition(target1, target2, true, true); + } + + // target tft is older than source tft, but same content + @Test + public void testMergeTranslationWorkSameStateAndContent() { + String content = "content0"; + Calendar c = new GregorianCalendar(); + HTextFlowTarget target2 = + generateTarget(ContentState.Translated, c.getTime(), content); + c.add(Calendar.DATE, 30); + HTextFlowTarget target1 = + generateTarget(ContentState.Translated, c.getTime(), content); + testShouldMergeCondition(target1, target2, false, false); + } + + private HTextFlowTarget generateTarget(ContentState state, + Date lastChanged, String content) { + HTextFlowTarget target = new HTextFlowTarget(); + target.setState(state); + target.setLastChanged(lastChanged); + target.setContents(content); + return target; + } + + private void testShouldMergeCondition(HTextFlowTarget target1, + HTextFlowTarget target2, boolean useNewerTranslation, + boolean expectedResult) { + boolean result = + service.shouldMerge(target1, target2, useNewerTranslation); + assertThat(result).isEqualTo(expectedResult); + } +} diff --git a/zanata-war/src/test/resources/org/zanata/test/model/MergeTranslationsData.dbunit.xml b/zanata-war/src/test/resources/org/zanata/test/model/MergeTranslationsData.dbunit.xml new file mode 100644 index 0000000000..1d27db35c9 --- /dev/null +++ b/zanata-war/src/test/resources/org/zanata/test/model/MergeTranslationsData.dbunit.xml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +