-
-* Zanata now uses Infinispan as its cache provider, and the cache needs to be configured in Jboss' `standalone.xml` file. Please see the [Infinispan](user-guide/system-admin/configuration/infinispan) section for more information.
+
Deployment
-* This release adds a one-time migration of some data, which can cause a timeout during server startup. This applies to
-all plain text and libreoffice formats, so is only a concern for servers that are upgrading from an earlier version and
-already have several hundred such documents. To avoid the timeout, add or change the following property in
-`standalone.xml`. A value of 1000 seconds is sufficient in our tests. Since the migration is performed only once, the
-property can safely be reverted or removed before subsequent startups.
+* Deployment for this release may require a longer timeout due to underlying database schema changes and data migration. This is dependent on database size and system performance, and the system administrator should consider increasing the JBoss timeout value in standalone.xml. This example sets a timeout of two hours, which should be more than enough:
...
-
+
+ ...
+
+
+* The Zanata administrator will also need to reindex HProject table via the Administration menu. See [Manage search](user-guide/admin/manage-search) for more information.
+
+
+
Infrastructure Changes
+
+* Zanata now uses Infinispan as its cache provider, and the cache needs to be configured in Jboss' `standalone.xml` file. Please see the [Infinispan](user-guide/system-admin/configuration/infinispan) section for more information.
* [1207423](https://bugzilla.redhat.com/show_bug.cgi?id=1207423) - zanata-assets(javascipts and css style) now are packaged as jar and is part of zanata-server dependency.
[Release](http://repository-zanata.forge.cloudbees.com/release/org/zanata/zanata-assets/) and [snapshot](http://repository-zanata.forge.cloudbees.com/snapshot/org/zanata/zanata-assets/)
@@ -37,7 +40,22 @@ Example usage in html file: `
+
*Note: The activation URL will expire after 24 hours.*
\ No newline at end of file
diff --git a/docs/user-guide/account/account-resend-activation.md b/docs/user-guide/account/account-resend-activation.md
index 5feac71063..1d472e3c52 100644
--- a/docs/user-guide/account/account-resend-activation.md
+++ b/docs/user-guide/account/account-resend-activation.md
@@ -6,5 +6,6 @@ If you didn't receive an email after signing up, or entered wrong email address
1. You will be redirected to `account inactive` page.
1. To resend activation email to the same email address, click on `Resend activation email` button.
1. To update email address and send activation email, enter the new email address in the input field and click `Update email`.
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/user-guide/account/account-settings.md b/docs/user-guide/account/account-settings.md
index 54aa2610ff..0d7d9db496 100644
--- a/docs/user-guide/account/account-settings.md
+++ b/docs/user-guide/account/account-settings.md
@@ -1,27 +1,27 @@
-# Account Settings
-
To access your user settings:
1. Login to Zanata
1. Click on `User menu` on top right corner, click on `Settings` on the drop down menu.
1. Alternatively, you can click on the `Settings` tab on your dashboard page.
-## Account Settings
+## Settings
### Update email
1. Enter the new email address in input field and click `Update email`.
-
-
+
1. A confirmation email will be sent to the new email address. Click on the link or the URL to confirm email address change.
### Change password
1. Fill in the `Old Password` and `New Password` fields.
+
-
-
1. Click `Update password` to change your password.
### Merge with other account
@@ -38,13 +38,14 @@ Use this to merge additional Zanata accounts you may have created when signing u
## Languages
This page shows all the language teams you have joined in Zanata.
-
-
+
To join more language teams,
1. Click on `Join a language team`.
- 1. See [Joining language team](user-guide/languages/language-team#joining-a-language-team).
+ 1. See [Joining language team](/user-guide/languages/language-team#joining-a-language-team).
## Client
diff --git a/docs/user-guide/account/account-sign-up.md b/docs/user-guide/account/account-sign-up.md
index a23538085b..91ce5ff04e 100644
--- a/docs/user-guide/account/account-sign-up.md
+++ b/docs/user-guide/account/account-sign-up.md
@@ -16,8 +16,9 @@ To create an account using OpenID:
1. Complete the `Name`, `Username`, and `Email` fields. Ensure your email address is entered correctly.
1. Click `Save`. This sends a validation email to the address you entered in the previous step.
1. Open the validation email and click the validation link.
-
-
+
You can now sign in to Zanata using your OpenID credentials.
diff --git a/docs/user-guide/account/user-dashboard.md b/docs/user-guide/account/user-dashboard.md
index 2dc296db56..6235a9a58f 100644
--- a/docs/user-guide/account/user-dashboard.md
+++ b/docs/user-guide/account/user-dashboard.md
@@ -7,8 +7,9 @@ The dashboard aims to help you quickly see and navigate to content that is relev
## Opening the Dashboard
If you sign in from the Zanata homepage, the first screen you see will be the user dashboard. To get to the dashboard from another page, expand the user menu on the right and click `Dashboard`.
-
-
+
## Dashboard Information
@@ -19,9 +20,10 @@ Dashboard has
- translation statistics
- an activity stream
- list of maintained projects
-- [Profile settings](user-guide/account/account-settings)
-
-
+- [Profile settings](/user-guide/account/account-settings)
+
### User Information
@@ -44,5 +46,6 @@ If you have more recent activity than is shown in the activity stream, you can s
### Projects
In the Project tab, a list of your maintained projects is shown. The down arrow to the left of the version can be clicked to show all the versions for the project. The project name, version name link directly to the project page, version page respectively.
-
-
+
diff --git a/docs/user-guide/account/user-profile.md b/docs/user-guide/account/user-profile.md
index 6d9232aa03..9cceb153d0 100644
--- a/docs/user-guide/account/user-profile.md
+++ b/docs/user-guide/account/user-profile.md
@@ -1,8 +1,10 @@
You can view a user's profile page by typing the username in the top search field and click on the search result.
-
-
+
On the user profile page, you can view this user's recent contribution statistics according to the selected date range option.
By default it will show this week's contribution categorized by language and project.
-
-
+
diff --git a/docs/user-guide/admin/admin-overview.md b/docs/user-guide/admin/admin-overview.md
index d1e1536b99..c49fd4fd51 100644
--- a/docs/user-guide/admin/admin-overview.md
+++ b/docs/user-guide/admin/admin-overview.md
@@ -2,14 +2,13 @@ Zanata admin has full access to all functionality in Zanata.
To access to admin menu, login to Zanata with admin role user, select `Administrator`.
## Admin sections
-
@@ -17,7 +16,7 @@ To access to admin menu, login to Zanata with admin role user, select `Administr
Configuration and settings for Zanata server.
-See [server configuration](user-guide/admin/server-config) for more information.
+See [server configuration](/user-guide/admin/server-config) for more information.
### Manage users
@@ -34,11 +33,11 @@ See [server configuration](user-guide/admin/server-config) for more information.
Reindexing Zanata database used for searching.
-See [Manage search](user-guide/admin/manage-search) for more information.
+See [Manage search](/user-guide/admin/manage-search) for more information.
### Role assignment rules
-See [Role assignment rules](user-guide/admin/role-assignment-rules) for more information.
+See [Role assignment rules](/user-guide/admin/role-assignment-rules) for more information.
### Translation memory
@@ -48,16 +47,16 @@ See [Role assignment rules](user-guide/admin/role-assignment-rules) for more inf
All background asynchronous tasks running in Zanata.
-See [Process manager](user-guide/admin/process-manager) for more information.
+See [Process manager](/user-guide/admin/process-manager) for more information.
### Overall statistic
Overall general statistics of Zanata.
-See [Statistic](user-guide/admin/statistic) for more information.
+See [Statistic](/user-guide/admin/statistic) for more information.
### Server monitoring
Server performance and statistics.
-See [Monitoring](user-guide/admin/monitoring) for more information.
\ No newline at end of file
+See [Monitoring](/user-guide/admin/monitoring) for more information.
\ No newline at end of file
diff --git a/docs/user-guide/admin/contact-admin.md b/docs/user-guide/admin/contact-admin.md
index 3a90eb87e1..e438616117 100644
--- a/docs/user-guide/admin/contact-admin.md
+++ b/docs/user-guide/admin/contact-admin.md
@@ -1,11 +1,11 @@
1. Login to Zanata.
1. At the bottom of the page, click on `Contact admin`.
1. In the dialog, fill in the message in the input file and click `Send`.
\ No newline at end of file
diff --git a/docs/user-guide/admin/manage-search.md b/docs/user-guide/admin/manage-search.md
index 52f9a70deb..7d0ea5e60f 100644
--- a/docs/user-guide/admin/manage-search.md
+++ b/docs/user-guide/admin/manage-search.md
@@ -1,6 +1,4 @@
-
+![Manage search](/images/admin-manage-search.png)
Zanata uses lucene index for fast searching and perform various tasks such as translation memory search.
diff --git a/docs/user-guide/admin/monitoring.md b/docs/user-guide/admin/monitoring.md
index 4a7868d6eb..e452273b15 100644
--- a/docs/user-guide/admin/monitoring.md
+++ b/docs/user-guide/admin/monitoring.md
@@ -1,6 +1,4 @@
-
+![Monitoring](/images/admin-monitoring.png)
* Zanata uses [Java Melody](https://code.google.com/p/javamelody/) for performance and statistic monitoring.
diff --git a/docs/user-guide/admin/process-manager.md b/docs/user-guide/admin/process-manager.md
index f915482a36..064eb75dd7 100644
--- a/docs/user-guide/admin/process-manager.md
+++ b/docs/user-guide/admin/process-manager.md
@@ -1,7 +1,6 @@
All asynchronous tasks that are running in the background are shown in this page.
-
diff --git a/docs/user-guide/admin/role-assignment-rules.md b/docs/user-guide/admin/role-assignment-rules.md
index 86d5db7413..5b98d68950 100644
--- a/docs/user-guide/admin/role-assignment-rules.md
+++ b/docs/user-guide/admin/role-assignment-rules.md
@@ -1,16 +1,14 @@
'Role assignment rules' are Zanata's way of dynamically assigning security roles and even Project access to users.
To configure Role Assignment Rules, admin users must go to the Administration section and click 'Role assignment rules' button.
-
### Overview
-
@@ -33,12 +31,12 @@ To configure Role Assignment Rules, admin users must go to the Administration se
1. Navigate to 'Role assignment rules' page.
1. Click on `More Action` menu on the top right panel.
1. Select `New Rule`.
1. Fill in the fields and click `Save`.
@@ -49,7 +47,7 @@ To configure Role Assignment Rules, admin users must go to the Administration se
1. Locate the rule you wish to delete, click on `Options` menu in front of the row.
1. Click `Delete` to remove the rule.
@@ -63,7 +61,7 @@ To restrict project access,
1. Click on `Permission` tab.
1. Check `Restrict access to certain user roles` and select roles that you wish to allow access to your project.
1. Any role restrictions will now be seen on the project's page and only users belonging to that role will be able to work on the project.
\ No newline at end of file
diff --git a/docs/user-guide/admin/server-config.md b/docs/user-guide/admin/server-config.md
index 14f5d08981..2a0c7c49f4 100644
--- a/docs/user-guide/admin/server-config.md
+++ b/docs/user-guide/admin/server-config.md
@@ -1,9 +1,8 @@
## General
General configuration for Zanata server.
-
@@ -22,9 +21,8 @@ General configuration for Zanata server.
## Email log
This enables or disables the sending of Zanata diagnostics log information via email.
-
@@ -39,9 +37,8 @@ This enables or disables the sending of Zanata diagnostics log information via e
## Piwik
[Piwik](http://piwik.org/) is a web analytics tools application. It tracks online visits to one or more websites and displays reports on these visits for analysis.
-
@@ -53,9 +50,8 @@ This enables or disables the sending of Zanata diagnostics log information via e
## Client
Admin can control the limit of client communication towards server via Client or REST API.
-
diff --git a/docs/user-guide/admin/statistic.md b/docs/user-guide/admin/statistic.md
index 604d5064a0..dc70c68b05 100644
--- a/docs/user-guide/admin/statistic.md
+++ b/docs/user-guide/admin/statistic.md
@@ -1,6 +1,4 @@
-
+![Statistic](/images/admin-statistics.png)
This page shows the number of
diff --git a/docs/user-guide/documents/download-translated-documents.md b/docs/user-guide/documents/download-translated-documents.md
index 50693fa5b7..1453605d36 100644
--- a/docs/user-guide/documents/download-translated-documents.md
+++ b/docs/user-guide/documents/download-translated-documents.md
@@ -11,13 +11,13 @@ Zanata allows anyone to download translations from a project version.
1. On the right panel, click on the drop down menu, select `Download All(zip)`.
1. Click on `Download` button to start downloading.
@@ -26,7 +26,7 @@ Zanata allows anyone to download translations from a project version.
1. Click on the drop down menu located in front of a document.
1. Select `Download translated [supported file format]`.
### From client
diff --git a/docs/user-guide/documents/raw-documents.md b/docs/user-guide/documents/raw-documents.md
index d6b9887162..ab6f52f045 100644
--- a/docs/user-guide/documents/raw-documents.md
+++ b/docs/user-guide/documents/raw-documents.md
@@ -36,6 +36,6 @@ _**Workaround:** Only upload translation documents that are completely translate
## Tips for Translating 'Raw' Documents
### Inline Tags
-Some parts of raw documents are not intended for direct translation. These are converted to xml-style inline tags such as "<g1><g2></g2></g1>" in the place of the image in the example document. It is recommended that these tags be included in translations with no modifications. The [XML/HTML tags validator](user-guide/projects/validations#htmlxml-tags) can help detect accidental changes to inline tags.
+Some parts of raw documents are not intended for direct translation. These are converted to xml-style inline tags such as "<g1><g2></g2></g1>" in the place of the image in the example document. It is recommended that these tags be included in translations with no modifications. The [XML/HTML tags validator](/user-guide/projects/validations#htmlxml-tags) can help detect accidental changes to inline tags.
-If unsure, you can also download a preview document to ensure that there are no errors or layout problems associated with treatment of tags - see [Downloading Translations through Web Interface](user-guide/documents/download-translated-documents/#from-website)
\ No newline at end of file
+If unsure, you can also download a preview document to ensure that there are no errors or layout problems associated with treatment of tags - see [Downloading Translations through Web Interface](/user-guide/documents/download-translated-documents/#from-website)
\ No newline at end of file
diff --git a/docs/user-guide/documents/upload-documents.md b/docs/user-guide/documents/upload-documents.md
index 17e5949169..3755190471 100644
--- a/docs/user-guide/documents/upload-documents.md
+++ b/docs/user-guide/documents/upload-documents.md
@@ -2,7 +2,7 @@
Anyone with an account on Zanata can create a translation project for their documents.
-You will need to [Create a project](user-guide/projects/create-project) and [Create a version](user-guide/versions/create-version) to upload strings.
+You will need to [Create a project](/user-guide/projects/create-project) and [Create a version](/user-guide/versions/create-version) to upload strings.
------------
@@ -13,11 +13,11 @@ You will need to [Create a project](user-guide/projects/create-project) and [Cre
#### Uploading new source documents
1. Go to `Documents` tab, click on down arrow on top of Document panel.
-
+![Upload source document](/images/upload-new-source-doc.png)
1. Select `Upload new source document`.
1. Browse or Drag your documents into the dialog and click `Upload Documents`.
1. You can access the same dialog in by go to `Settings` tab -> `Documents` section. Click `+` sign on top left panel.
-
+![Upload source document dialog](/images/upload-source-doc-dialog.png)
#### Updating existing source documents
@@ -26,7 +26,7 @@ You will need to [Create a project](user-guide/projects/create-project) and [Cre
1. Select your file and choose the source language for this file.
1. Click `Upload` to proceed.
-
+![Upload source document](/images/upload-source-doc.png)
*Alternatively*
@@ -35,7 +35,7 @@ You will need to [Create a project](user-guide/projects/create-project) and [Cre
1. Select your file and choose the source language for this file.
1. Click `Upload` to proceed.
-
+![Upload source document from settings](/images/upload-source-doc-from-settings.png)
#### Uploading translation documents
@@ -43,10 +43,10 @@ You will need to [Create a project](user-guide/projects/create-project) and [Cre
1. Click on `Languages` tab in version page, select the language of translation document you wish to upload.
1. On right panel, click on the down arrow(Translation option) on left side of document and select `Upload translation`.
-
-
+![Upload translation document](/images/upload-translation-doc.png)
+
-
+![Upload translation document dialog](/images/upload-translation-doc-dialog.png)
1. On the dialog, select your translation file.
- `Merge` option - If unchecked, uploaded translations overrides current translation, otherwise, it will merge with current translation in system.
diff --git a/docs/user-guide/editor/documents-view.md b/docs/user-guide/editor/documents-view.md
index ae0c5fe8dd..63e0904220 100644
--- a/docs/user-guide/editor/documents-view.md
+++ b/docs/user-guide/editor/documents-view.md
@@ -1,20 +1,17 @@
-
+![Documents view](/images/editor-doc-list.png)
1. **Breadcrumbs** - Links to Project -> Version
2. **Statistic** - Statistic and remaining hours for the selected project version in selected language.
-3. [**Views**](user-guide/editor/overview#views-in-webtrans)
+3. [**Views**](/user-guide/editor/overview#views-in-webtrans)
4. **Search** - Allow translators to by document name.
5. **Change statistic view** - Change statistic measurement in `Words`(total words in the all documents) or `Messages` (total translation entry in all documents).
6. **Settings** - From top to bottom: Notification, Chat room, Settings, Validations.
7. **Paging** - Page navigation for document list.
## Settings
-
@@ -26,9 +23,8 @@
## Validations
-
@@ -42,4 +38,4 @@
**Note:** validation icons will not change until the next time validation is run on the document, even if the translations are updated. Make sure to re-run validation after making any changes so that the results are accurate.
-See [validations](user-guide/projects/validations) for more details.
\ No newline at end of file
+See [validations](/user-guide/projects/validations) for more details.
\ No newline at end of file
diff --git a/docs/user-guide/editor/editor-view.md b/docs/user-guide/editor/editor-view.md
index bdd56fadae..54af3c6ed4 100644
--- a/docs/user-guide/editor/editor-view.md
+++ b/docs/user-guide/editor/editor-view.md
@@ -1,11 +1,9 @@
-
+![Editor view](/images/editor-overview.png)
### 1. Breadcrumbs
-Links to Project -> Version -> [Documents view](user-guide/editor/documents-view) -> Document name. Each link will redirect to the respective page.
+Links to Project -> Version -> [Documents view](/user-guide/editor/documents-view) -> Document name. Each link will redirect to the respective page.
### 2. Statistic
@@ -14,7 +12,7 @@ Statistic and remaining hours of the current document translation document.
### 3. Views
-See [Views](user-guide/editor/overview#views-in-webtrans) for more information.
+See [Views](/user-guide/editor/overview#views-in-webtrans) for more information.
### 4. Search and filter
@@ -77,16 +75,14 @@ If a glossary has been uploaded for your language, each word in the currently se
If you're not part of that language team, the editor will open as readonly mode.
You will be restricted to only viewing source and translations in the editor.
-
## Settings
-
@@ -102,14 +98,13 @@ You will be restricted to only viewing source and translations in the editor.
## Validations
-
Translators can enable or disable validation check on their translations except for those which is enforced by project maintainers.
-See [Project settings](user-guide/projects/project-settings#validations) for more information.
+See [Project settings](/user-guide/projects/project-settings#validations) for more information.
-See [validations](user-guide/projects/validations) all available check.
+See [validations](/user-guide/projects/validations) all available check.
\ No newline at end of file
diff --git a/docs/user-guide/editor/overview.md b/docs/user-guide/editor/overview.md
index 952c2b655a..54edc48ee2 100644
--- a/docs/user-guide/editor/overview.md
+++ b/docs/user-guide/editor/overview.md
@@ -1,18 +1,17 @@
## Navigating to editor
-See [Translator guide](user-guide/translator-guide) for more information.
+See [Translator guide](/user-guide/translator-guide) for more information.
## Views in webtrans
-
From left to right
-1. [Editor view (default view)](user-guide/editor/editor-view)
-2. [Documents view](user-guide/editor/documents-view)
-3. [Project search and replace](user-guide/editor/project-search-replace-view)
-4. [Keyboard shortcut](user-guide/editor/keyboard-shortcuts)
+1. [Editor view (default view)](/user-guide/editor/editor-view)
+2. [Documents view](/user-guide/editor/documents-view)
+3. [Project search and replace](/user-guide/editor/project-search-replace-view)
+4. [Keyboard shortcut](/user-guide/editor/keyboard-shortcuts)
diff --git a/docs/user-guide/editor/translation-history.md b/docs/user-guide/editor/translation-history.md
index e9d52517b1..ea35116408 100644
--- a/docs/user-guide/editor/translation-history.md
+++ b/docs/user-guide/editor/translation-history.md
@@ -2,17 +2,15 @@ User will be able to see translation history of each messages. It will display f
### How to show translation history
In editor click on the "timer" like icon below will bring up the initial view:
-
### Comparison of two entries
If selecting exactly two entries, user will be able to compare them in a diff view:
-
diff --git a/docs/user-guide/glossary/delete-glossaries.md b/docs/user-guide/glossary/delete-glossaries.md
index 2a3591902d..4356168538 100644
--- a/docs/user-guide/glossary/delete-glossaries.md
+++ b/docs/user-guide/glossary/delete-glossaries.md
@@ -1,12 +1,12 @@
### Prerequisite
-Requires **Glossary-admin** role. See [glossary roles and permission](user-guide/glossary/glossary-roles-permissions) for permission setup.
+Requires **Glossary-admin** role. See [glossary roles and permission](/user-guide/glossary/glossary-roles-permissions) for permission setup.
### Delete via Web UI
1. Login into Zanata
1. Click `Glossary` on menu.
1. Click on `Glossary options` on the glossary language you wish to delete and click `Delete glossary`.
1. Click `OK` to confirm delete all glossary entries in selected locale.
diff --git a/docs/user-guide/glossary/edit-glossaries.md b/docs/user-guide/glossary/edit-glossaries.md
index a7e9d7d4c9..2891490788 100644
--- a/docs/user-guide/glossary/edit-glossaries.md
+++ b/docs/user-guide/glossary/edit-glossaries.md
@@ -1,17 +1,17 @@
### Prerequisite
-See [glossary roles and permission](user-guide/glossary/glossary-roles-permissions) for permission setup.
+See [glossary roles and permission](/user-guide/glossary/glossary-roles-permissions) for permission setup.
1. Go to editor in Zanata and select a string. Instructions for opening the
- translation editor can be found at [Preparing for Translation](user-guide/translator-guide#start-translate-a-project-version).
+ translation editor can be found at [Preparing for Translation](/user-guide/translator-guide#start-translate-a-project-version).
1. In Glossary panel (bottom right corner), click on icon on the glossary entry you wish to edit.
-
+
1. In the popup window, user can:
diff --git a/docs/user-guide/glossary/glossary-roles-permissions.md b/docs/user-guide/glossary/glossary-roles-permissions.md
index e8396f04f1..e72233105a 100644
--- a/docs/user-guide/glossary/glossary-roles-permissions.md
+++ b/docs/user-guide/glossary/glossary-roles-permissions.md
@@ -6,16 +6,16 @@
There are 2 roles that manage glossaries in Zanata, **glossarist** and **glossary-admin**.
-User can request those roles by [contacting Zanata admin](user-guide/admin/contact-admin).
+User can request those roles by [contacting Zanata admin](/user-guide/admin/contact-admin).
Below are the summary of these 2 roles with their permission.
diff --git a/docs/user-guide/glossary/upload-glossaries.md b/docs/user-guide/glossary/upload-glossaries.md
index f563be59fd..936ce3ec0e 100644
--- a/docs/user-guide/glossary/upload-glossaries.md
+++ b/docs/user-guide/glossary/upload-glossaries.md
@@ -1,6 +1,6 @@
### Prerequisite
-See [glossary roles and permission](user-guide/glossary/glossary-roles-permissions) for permission setup.
+See [glossary roles and permission](/user-guide/glossary/glossary-roles-permissions) for permission setup.
### Supported file format
#### PO
@@ -21,8 +21,8 @@ See [glossary roles and permission](user-guide/glossary/glossary-roles-permissio
1. part-of-speech column: The part-of-speech is an informational field that indicates the sense in which the terms in the row should be used. Sample parts-of-speech include adjective, adverb, noun, and verb.
1. description column: The description should provide any notes for the translator, including the meaning of the terms in the row.
### Upload via Web UI
@@ -31,18 +31,18 @@ See [glossary roles and permission](user-guide/glossary/glossary-roles-permissio
1. Click `Glossary` menu.
1. Click on `More Actions` on top right corner of the table, follow by `Upload glossary` from the list.
1. A window will popup, click on `choose file` to select your glossary file.
1. For PO file format, you will need to select **Source and Target languages** of the selected po file.
1. Click `Upload` button to start uploading selected glossary file.
diff --git a/docs/user-guide/groups/create-group.md b/docs/user-guide/groups/create-group.md
index 6d9b98acf8..d3b3302270 100644
--- a/docs/user-guide/groups/create-group.md
+++ b/docs/user-guide/groups/create-group.md
@@ -4,13 +4,12 @@ Currently, only admin can create a group:
1. Click on `Groups` section on the top menu.
1. Click on 'New Group' button on right panel.
The following screenshot shows the group creation page.
-
diff --git a/docs/user-guide/groups/group-settings.md b/docs/user-guide/groups/group-settings.md
index a8658b725b..d398bf8bfd 100644
--- a/docs/user-guide/groups/group-settings.md
+++ b/docs/user-guide/groups/group-settings.md
@@ -1,17 +1,16 @@
Once a group has been created, the maintainer can add further details and group behaviour via the Settings tab.
-See the [Group Creation Help](user-guide/groups/create-group) for details on creating groups.
+See the [Group Creation Help](/user-guide/groups/create-group) for details on creating groups.
------------
## General Settings
-The General tab contains fields that manage appearance your group which already covered in the [Group Creation Help](user-guide/groups/create-group).
-
+The General tab contains fields that manage appearance your group which already covered in the [Group Creation Help](/user-guide/groups/create-group).
@@ -24,9 +23,8 @@ This button is used to set a group to read-only, which prevents the group being
------------
## Languages Settings
-
@@ -45,9 +43,8 @@ To remove a language from the list of available locales, first move the cursor o
------------
## Projects Settings
-
@@ -62,14 +59,14 @@ To add a version to your group, select the desired project version from the drop
### Remove a project version
To remove a project version from the list, first move the cursor over the project version, then click the "X" that appears.
-
+
+![Groups projects remove](/images/group-projects-remove.png)
------------
## Maintainers Settings
-
diff --git a/docs/user-guide/groups/group-view.md b/docs/user-guide/groups/group-view.md
index 15c3b0ceb9..ffc637ab57 100644
--- a/docs/user-guide/groups/group-view.md
+++ b/docs/user-guide/groups/group-view.md
@@ -1,7 +1,6 @@
When a group is selected, you will be taken to an overall view of the group. This will show a list of the available versions as well as an indication of their translation progress.
-
diff --git a/docs/user-guide/languages/add-team-member.md b/docs/user-guide/languages/add-team-member.md
index bd222c3430..827874d10e 100644
--- a/docs/user-guide/languages/add-team-member.md
+++ b/docs/user-guide/languages/add-team-member.md
@@ -6,7 +6,7 @@ While signed-in as language coordinator
1. Click the language team.
1. Click on `+` sign on the top left panel.
1. Enter username and click `Search`.
diff --git a/docs/user-guide/languages/language-team.md b/docs/user-guide/languages/language-team.md
index be7e9dedf7..cd5a979287 100644
--- a/docs/user-guide/languages/language-team.md
+++ b/docs/user-guide/languages/language-team.md
@@ -12,12 +12,12 @@ While signed-in, translators can join a Language Team by completing the followin
1. Click the language team you want to join.
1. Click on `More Action` menu on top right panel, select `Request to Join Team`.
1. In the dialog, select roles you wish to be added as, fill in some information for language team coordinator and click `Send`.
@@ -30,7 +30,8 @@ While signed-in, translators can leave a Language Team by completing the followi
1. Click `Language` tab.
1. Click the language team you want to leave.
1. Click on `More Action` menu on top right panel, select `Leave Language Team`.
-
+
+![Request to leave](/images/language-leave-team.png)
## Contact Language Coordinator
@@ -40,10 +41,10 @@ Any user in Zanata can contact language coordinator by:
1. Click on a language team.
1. Click on `More Action` menu on top right panel, select `Contact Coordinators`.
1. In the dialog, fill in the message in the input file and click `Send`.
\ No newline at end of file
diff --git a/docs/user-guide/languages/modify-role.md b/docs/user-guide/languages/modify-role.md
index d53cf7eddc..9694272379 100644
--- a/docs/user-guide/languages/modify-role.md
+++ b/docs/user-guide/languages/modify-role.md
@@ -3,8 +3,7 @@ To modify role for existing user
1. Click `Languages` tab.
1. Click the language team.
1. In members tab, locate the user in the list, and check on the relevant role you wish to add.
-
\ No newline at end of file
diff --git a/docs/user-guide/project-maintainer-guide.md b/docs/user-guide/project-maintainer-guide.md
index d88a7a2b39..e981f9881a 100644
--- a/docs/user-guide/project-maintainer-guide.md
+++ b/docs/user-guide/project-maintainer-guide.md
@@ -1,14 +1,14 @@
Any user who has joined Zanata can upload their project to be translated.
-See [signup account](user-guide/account/account-sign-up) and [activate account](user-guide/account/account-activate) for more information.
+See [signup account](/user-guide/account/account-sign-up) and [activate account](/user-guide/account/account-activate) for more information.
## New project
-1. Create a [project](user-guide/projects/create-project) and [version](user-guide/versions/create-version).
+1. Create a [project](/user-guide/projects/create-project) and [version](/user-guide/versions/create-version).
1. [Install](http://zanata-client.readthedocs.org/en/latest/installation) and [configure](http://zanata-client.readthedocs.org/en/latest/configuration) Zanata client.
-1. [Upload your project documents](user-guide/documents/upload-documents).
-1. [Download translated documents](user-guide/documents/download-translated-documents) when translations is completed.
+1. [Upload your project documents](/user-guide/documents/upload-documents).
+1. [Download translated documents](/user-guide/documents/download-translated-documents) when translations is completed.
## Migrating to Zanata
-See [migrating to Zanata guide](user-guide/projects/import-projects) for migrating guide.
\ No newline at end of file
+See [migrating to Zanata guide](/user-guide/projects/import-projects) for migrating guide.
\ No newline at end of file
diff --git a/docs/user-guide/projects/create-project.md b/docs/user-guide/projects/create-project.md
index f580b3b542..d0c1164f90 100644
--- a/docs/user-guide/projects/create-project.md
+++ b/docs/user-guide/projects/create-project.md
@@ -1,7 +1,7 @@
Anyone with an account can upload source strings to Zanata. The first step is to create a project:
1. **Create a project.**
- 1. [Create a version](user-guide/versions/create-version) under the project.
+ 1. [Create a version](/user-guide/versions/create-version) under the project.
1. Upload documents to the version:
- Using the website
- Using the [command-line client push](http://zanata-client.readthedocs.org/en/latest/commands/push/) command
@@ -9,17 +9,15 @@ Anyone with an account can upload source strings to Zanata. The first step is to
## Project creation through the website
To start creating a project on the Zanata website, click the `New Project` button on the `Projects` page.
-
The following screenshot shows the project creation page. In the screenshot, all fields have been filled in to illustrate the process. This example would create a project with a URL ending in '/zanata-server', that will be displayed in the project list as 'Zanata Server'. Details for each of the fields are shown below.
-
@@ -41,12 +39,12 @@ A short description to provide a little more information for translators to iden
Project Type defines the type of files that your project uses to store source and translation strings. This setting ensures that files for your project will be downloaded in the correct format.
-There is a brief description for each project type next to each project type option. If the description is insufficient, more information on each project type is available at the [Project Types](user-guide/projects/project-types).
+There is a brief description for each project type next to each project type option. If the description is insufficient, more information on each project type is available at the [Project Types](/user-guide/projects/project-types).
## Project Settings
Once the project has been created, the maintainer can customize the project appearance and behaviour as required.
-See the [Project Settings Help](user-guide/projects/project-settings) for details on project settings.
+See the [Project Settings Help](/user-guide/projects/project-settings) for details on project settings.
## Project creation from command line
diff --git a/docs/user-guide/projects/gettext-example.md b/docs/user-guide/projects/gettext-example.md
index 2e5f758531..ae682a75a9 100644
--- a/docs/user-guide/projects/gettext-example.md
+++ b/docs/user-guide/projects/gettext-example.md
@@ -1,4 +1,4 @@
-This document presents an example of a Zanata project with type `gettext`. It assumes that you have already created a project and a version (See [Project Creation](user-guide/projects/create-project) ).
+This document presents an example of a Zanata project with type `gettext`. It assumes that you have already created a project and a version (See [Project Creation](/user-guide/projects/create-project) ).
Gettext projects consist of a single source file (.pot), and several translation files (.po) named after their corresponding locale. Below is a typical directory structure for a Gettext project:
@@ -32,4 +32,4 @@ Notice the presence of the `zanata.xml` file already there. This file will be ge
This example is also using the locale mapping feature. The `map-from` attributes on the `locale` elements are telling the client that although it will find the files using locales `es_ES` and `zh_TW`, those translated documents should be stored in the server under locales `es-ES` and `zh-Hant`.
-See [Upload documents](user-guide/documents/upload-documents) and [Download translation](user-guide/documents/download-translated-documents) for more information.
+See [Upload documents](/user-guide/documents/upload-documents) and [Download translation](/user-guide/documents/download-translated-documents) for more information.
diff --git a/docs/user-guide/projects/import-projects.md b/docs/user-guide/projects/import-projects.md
index 02f26848a3..e0aca3fa70 100644
--- a/docs/user-guide/projects/import-projects.md
+++ b/docs/user-guide/projects/import-projects.md
@@ -1,11 +1,11 @@
This document will provide help when importing or migrating projects into Zanata from other translation platforms.
You need a user account on the Zanata instance you will migrate to.
-See [signup account](user-guide/account/account-sign-up) and [activate account](user-guide/account/account-activate) for more information.
+See [signup account](/user-guide/account/account-sign-up) and [activate account](/user-guide/account/account-activate) for more information.
## Create a project in Zanata
-The first thing to do is to create a project and version to host your content in Zanata. You can do this using the command line clients (See [Initialising a Project](http://zanata-client.readthedocs.org/en/latest/commands/init/) ), or Zanata's web interface (See [Project Creation](user-guide/projects/create-project) ).
+The first thing to do is to create a project and version to host your content in Zanata. You can do this using the command line clients (See [Initialising a Project](http://zanata-client.readthedocs.org/en/latest/commands/init/) ), or Zanata's web interface (See [Project Creation](/user-guide/projects/create-project) ).
Please note that the `init` command might not be available on older platforms (like Fedora 19). Alternatively, you can use the Zanata Ivy client. For more information see [Installing the Client](http://zanata-client.readthedocs.org/en/latest/installation/) and [Configuring the Client](http://zanata-client.readthedocs.org/en/latest/configuration/).
@@ -23,11 +23,11 @@ Extract the project's content from the other platform. Below are some examples o
Depending on your project type, you might need to modify your Zanata configuration or your project's directory structure. Here are some examples on how to set up a project locally to be pushed to Zanata.
-+ [Gettext project](user-guide/projects/gettext-example)
++ [Gettext project](/user-guide/projects/gettext-example)
Gettext projects consist of a single source file (.pot), and several translation files (.po) named after their corresponding locale.
-+ [Podir project](user-guide/projects/podir-example)
++ [Podir project](/user-guide/projects/podir-example)
Podir projects consist of multiple source files (.pot) in a common source directory, and several translation files (.po) located in their corresponding locale's directory.
diff --git a/docs/user-guide/projects/podir-example.md b/docs/user-guide/projects/podir-example.md
index ab187ced18..5cf0461ea4 100644
--- a/docs/user-guide/projects/podir-example.md
+++ b/docs/user-guide/projects/podir-example.md
@@ -1,4 +1,4 @@
-This document presents an example of a Zanata project with type `podir`. It assumes that you have already created a project and a version (See [Project Creation](user-guide/projects/create-project) ).
+This document presents an example of a Zanata project with type `podir`. It assumes that you have already created a project and a version (See [Project Creation](/user-guide/projects/create-project) ).
Podir projects consist of multiple source files (.pot) in a common source directory, and several translation files (.po) located in their corresponding locale's directory. Below is a typical directory structure for a Podir project:
@@ -44,5 +44,5 @@ Notice the presence of the `zanta.xml` file already there. This file will be gen
-See [Upload documents](user-guide/documents/upload-documents) and [Download translation](user-guide/documents/download-translated-documents) for more information.
+See [Upload documents](/user-guide/documents/upload-documents) and [Download translation](/user-guide/documents/download-translated-documents) for more information.
diff --git a/docs/user-guide/projects/project-settings.md b/docs/user-guide/projects/project-settings.md
index 1044c03ad1..b6ca454f48 100644
--- a/docs/user-guide/projects/project-settings.md
+++ b/docs/user-guide/projects/project-settings.md
@@ -1,18 +1,17 @@
Once a project has been created, the maintainer can add further details and project behaviour via the Settings tab.
-See the [Project Creation Help](user-guide/projects/create-project) for details on creating projects.
+See the [Project Creation Help](/user-guide/projects/create-project) for details on creating projects.
-The Settings tab contains fields that manage appearance and workflow of your project. Some of these are already covered in the [Project Creation Help](user-guide/projects/create-project).
+The Settings tab contains fields that manage appearance and workflow of your project. Some of these are already covered in the [Project Creation Help](/user-guide/projects/create-project).
------------
## General Settings
-
@@ -21,7 +20,7 @@ The Settings tab contains fields that manage appearance and workflow of your pro
Project Type defines the type of files that your project uses to store source and translation strings. This setting ensures that files for your project will be downloaded in the correct format.
-There is a brief description for each project type next to each project type option. If the description is insufficient, more information on each project type is available at [Project Types](user-guide/projects/project-types).
+There is a brief description for each project type next to each project type option. If the description is insufficient, more information on each project type is available at [Project Types](/user-guide/projects/project-types).
### Home Page
@@ -48,9 +47,8 @@ This can be toggled using the same button, as desired.
------------
## Languages Settings
-
@@ -65,9 +63,8 @@ By default, your project will be available for translation to all of the enabled
To search for available languages, enter text into the field under "Add a language". Available languages matching the entered text will display in a dropdown.
To add add a language to your project, select the desired language from the dropdown.
-
@@ -75,18 +72,16 @@ To add add a language to your project, select the desired language from the drop
### Remove a Language
To remove a language from the list of available locales, first move the cursor over the language, then click the "X" that appears.
-
------------
## Translation Settings
-
@@ -100,20 +95,19 @@ Validations not enabled here can be toggled by translators to suit their individ
* Warning - display warning to translator when validation failed. Translator can save the translation as `Translated`.
* Error - display error to translator when validation failed. Translator cannot save translation as `Translated` until error has been fixed.
-See [validation](user-guide/projects/validations) all available check.
+See [validation](/user-guide/projects/validations) all available check.
### Copy Translations settings
Copy Translations attempts to reuse translations that have been entered in Zanata by matching them with untranslated strings in your project-version. These settings change the way Copy Translations behaves when a new version is created.
-Refer to [the Copy Translations reference](user-guide/translation-reuse/copy-trans/) for more information.
+Refer to [the Copy Translations reference](/user-guide/translation-reuse/copy-trans/) for more information.
------------
## Permissions Settings
-
@@ -134,9 +128,8 @@ The access restriction feature is intended for use with special roles that can b
------------
## Webhooks
-
@@ -149,15 +142,14 @@ When an event occurs, Zanata will make a HTTP POST to the URI configured in the
### Adding a Webhook URI
-
+![Project Webhooks Settings tab](/images/project-webhooks-settings-2.png)
Enter a valid URI into the provided text input. Hit the 'enter' key or click on 'Add webhook' button to add the URI.
------------
## About
-
diff --git a/docs/user-guide/projects/project-view.md b/docs/user-guide/projects/project-view.md
index da4740faf6..f64f0ec471 100644
--- a/docs/user-guide/projects/project-view.md
+++ b/docs/user-guide/projects/project-view.md
@@ -1,7 +1,6 @@
When a project is selected, you will be taken to an overall view of the project. This will show a list of the available versions as well as an indication of their translation progress.
-
@@ -16,4 +15,4 @@ The different views for a project offer more information about it.
- **About** shows more information about the project.
- **Settings** _(This is only available to Project Maintainers)_ allows the user to change project settings.
-To [view the information about a specific version](user-guide/versions/version-view), you can click on the version's name from the list.
\ No newline at end of file
+To [view the information about a specific version](/user-guide/versions/version-view), you can click on the version's name from the list.
\ No newline at end of file
diff --git a/docs/user-guide/review/add-reviewer.md b/docs/user-guide/review/add-reviewer.md
index 5f9935a60a..591b0d36ac 100644
--- a/docs/user-guide/review/add-reviewer.md
+++ b/docs/user-guide/review/add-reviewer.md
@@ -2,7 +2,7 @@ Translation review can only be performed for a language by users who are reviewe
## Requesting Review Privileges
-You can request to be a reviewer using the [Contact Coordinators](user-guide/languages/language-team#contact-language-coordinator).
+You can request to be a reviewer using the [Contact Coordinators](/user-guide/languages/language-team#contact-language-coordinator).
This lets you send a message to the coordinators for the language team, in which you can write a message asking to become a reviewer.
@@ -10,10 +10,10 @@ This lets you send a message to the coordinators for the language team, in which
### New team member
-See [Add new member](user-guide/languages/add-team-member) for more information.
+See [Add new member](/user-guide/languages/add-team-member) for more information.
### Existing team member
To add reviewer role for existing user
-* See [Modify role](user-guide/languages/modify-role) and check `Reviewer` on the user.
\ No newline at end of file
+* See [Modify role](/user-guide/languages/modify-role) and check `Reviewer` on the user.
\ No newline at end of file
diff --git a/docs/user-guide/review/review-enable.md b/docs/user-guide/review/review-enable.md
index 273d6533bd..d3ddee2326 100644
--- a/docs/user-guide/review/review-enable.md
+++ b/docs/user-guide/review/review-enable.md
@@ -1,3 +1,3 @@
Translation review can be enable/disable by project maintainer in settings page.
-See [Version settings|Review](user-guide/versions/version-settings#require-translation-review) for more information.
\ No newline at end of file
+See [Version settings|Review](/user-guide/versions/version-settings#require-translation-review) for more information.
\ No newline at end of file
diff --git a/docs/user-guide/review/translation-review.md b/docs/user-guide/review/translation-review.md
index 8ccca40556..daba058f45 100644
--- a/docs/user-guide/review/translation-review.md
+++ b/docs/user-guide/review/translation-review.md
@@ -1,16 +1,15 @@
Some project-versions require translations to be reviewed before they are considered ready to use.
-Instructions for changing this setting can be found at [Enable review](user-guide/review/review-enable)
+Instructions for changing this setting can be found at [Enable review](/user-guide/review/review-enable)
-Review can only be performed by reviewers. To become a reviewer, see [Adding Reviewers](user-guide/review/add-reviewer)
+Review can only be performed by reviewers. To become a reviewer, see [Adding Reviewers](/user-guide/review/add-reviewer)
The review process is about reading translations in the 'translated' state and determining whether they are technically correct translations of sufficient quality. These instructions show how to use the user interface for accepting or rejecting translations, but does not aim to teach how to decide whether a translation should be accepted.
## Ready for review
Translations are considered ready for review when they have 'translated' state, which is shown as green bars on either side of the translation string.
-
@@ -18,9 +17,8 @@ Translations are considered ready for review when they have 'translated' state,
## Filtering the view
If only some of the translations are in 'translated' state, such as if the document is only partially translated or has already been through an initial review, it may be helpful to filter the view so that only 'translated' strings are shown. This is done by checking `Translated` state in the `Complete` category near the top of the editor.
-
@@ -30,18 +28,16 @@ If only some of the translations are in 'translated' state, such as if the docum
## Accept and Reject buttons
If you have review permission for a document, you will see an extra pair of buttons next to each editor cell to accept or reject the translation. You will have review permission if you are a reviewer for the language, or if you are a maintainer for the project. Maintainers may wish to review strings to make sure they are correctly formatted for the environment, particularly for software translations.
-
## Accepting Translations
If you decide a translation is acceptable and does not need any change, it can be approved simply by pressing the `Accept translation` button next to the editor window. This will change the state to `Approved`.
-
@@ -53,34 +49,30 @@ Approved state is is shown as blue bars on either side of the translation string
If a translation is not yet acceptable, it can be rejected so that translators know that it needs to be changed.
To reject a translation click the `Reject translation` button next to the editor window. This will open a dialog where you can enter the reason for the rejection.
-
You must enter a reason for rejecting the translation - the `Confirm rejection` will not work until a reason has been entered. This is to make sure that translators can make the right changes so that the translation is acceptable, rather than trying to guess why it was rejected.
Rejected state is shown as orange bars on either side of the translation string. You will also notice an indicator on the top right of the text area showing that there is a comment. Clicking the comment indicator will open the history view where the comment is shown.
-
You can also open the history view by clicking the `History` button on the right.
-
When a translation is rejected, the reason for the rejection is shown as a comment in history view, with the `Rejected` state shown above it.
There is also a space where additional comments can be added. This may be useful for discussing a rejected translation, but keep in mind that at the time of writing, reviewers do not yet receive any notification when there is a new comment on a rejected translation.
-
diff --git a/docs/user-guide/system-admin/configuration/email.md b/docs/user-guide/system-admin/configuration/email.md
index 296d19f33f..c50fec5b57 100644
--- a/docs/user-guide/system-admin/configuration/email.md
+++ b/docs/user-guide/system-admin/configuration/email.md
@@ -1,5 +1,8 @@
-As of Zanata 3.6? (https://github.com/zanata/zanata-server/pull/633) email configuration is taken directly from JBoss/WildFly's standalone.xml configuration, using the mail session configured with the JNDI name "java:jboss/mail/Default". In the default configuration of JBoss/WildFly, this expects an SMTP server on localhost port 25.
+As of Zanata 3.7 (https://github.com/zanata/zanata-server/pull/633) email configuration is taken directly from JBoss/WildFly's `standalone.xml` configuration, using the mail session configured with the JNDI name `java:jboss/mail/Default`. In the default configuration of JBoss/WildFly, this expects an SMTP server on localhost port 25.
-The JNDI strings starting with "java:global/zanata/smtp/" are now obsolete. If you have previously configured these values, you will need to change the configuration of the mail session "java:jboss/mail/Default" to include your preferred SMTP settings.
+The JNDI strings starting with `java:global/zanata/smtp/` are now obsolete. If you have previously configured these values, you will need to change the configuration of the mail session `java:jboss/mail/Default` to include your preferred SMTP settings.
-JBoss's mail session configuration is described on these pages: https://developer.jboss.org/wiki/JBossAS720EmailSessionConfigurtion-EnglishVersion and http://www.mastertheboss.com/jboss-server/jboss-configuration/jboss-mail-service-configuration
+JBoss's mail session configuration is described on these pages:
+
+* https://developer.jboss.org/wiki/JBossAS720EmailSessionConfigurtion-EnglishVersion
+* http://www.mastertheboss.com/jboss-server/jboss-configuration/jboss-mail-service-configuration
diff --git a/docs/user-guide/system-admin/configuration/installation.md b/docs/user-guide/system-admin/configuration/installation.md
index bd2ea23e61..71840bcc5e 100644
--- a/docs/user-guide/system-admin/configuration/installation.md
+++ b/docs/user-guide/system-admin/configuration/installation.md
@@ -113,10 +113,13 @@ Any other value will be treated as the name of a virus scanner command: the comm
## Running Zanata
-Go to the `/bin` directory and run the `standalone.sh` (Linux, Mac) or `standalone.bat` (Windows) file.
+Go to the `/bin` directory and run
+
+* `standalone.sh` or `start-zanata.sh` for Linux, Mac
+* `standalone.bat` or `start-zanata.bat` for Windows
## Using Zanata
To start using your Zanata server, open a browser and navigate to `http://localhost:8080/zanata`
-You can now upload some source strings and start translating. To get started, see [Adding Source Documents](user-guide/documents/upload-documents).
+You can now upload some source strings and start translating. To get started, see [Adding Source Documents](/user-guide/documents/upload-documents).
diff --git a/docs/user-guide/translation-reuse/copy-trans.md b/docs/user-guide/translation-reuse/copy-trans.md
index 07d76bd91b..a16e9c68ac 100644
--- a/docs/user-guide/translation-reuse/copy-trans.md
+++ b/docs/user-guide/translation-reuse/copy-trans.md
@@ -29,18 +29,16 @@ This process is repeated for each text flow in the uploaded document, or, if Cop
#### Permissive Options
If `Continue` is selected for all conditions, only the required "On content mismatch" rule is checked. This means that if a string has matching content it bypasses all other conditions and is reused in a "Translated" state (or "Approved" if your project requires review and the translation being copied is already in "Approved" state). When there are multiple matches, the latest translation is used.
-
#### Strict Options
If `Don't Copy` is selected for all conditions, a string must have matching content, a matching ID, be from the same project, and be from a document with the same name and path, otherwise it will not be reused. If all of the conditions are passed, the content is reused in a "Translated" state (or "Approved" if your project requires review and the translation being copied is already in "Approved" state).
-
@@ -51,9 +49,8 @@ For this example, consider that you have a new version of your project in Zanata
1. To ensure that Copy Trans uses strings from the other version in your project only, set "On project mismatch" to `Don't Copy`.
1. To ensure that the changed document paths do not affect the reuse process, set "On document mismatch" to `Continue`.
1. Because most string IDs have not changed, set "On Context mismatch" to `Continue as Fuzzy`.
-
@@ -64,14 +61,13 @@ You can run Copy Trans manually against a project version.
1. On the project version page, click on `More actions` menu on the top right.
1. Select `Copy Translations`.
1. Select the appropriate action for each condition, and then click `Copy Translations` button.
-
diff --git a/docs/user-guide/translation-reuse/copy-version.md b/docs/user-guide/translation-reuse/copy-version.md
index db2c10654d..86ffc6517e 100644
--- a/docs/user-guide/translation-reuse/copy-version.md
+++ b/docs/user-guide/translation-reuse/copy-version.md
@@ -2,20 +2,19 @@ Copy version is a service that allow project maintainers to create new project v
This saves project maintainer from uploading the same source and translation documents every time a new version is required.
-When [creating new version](user-guide/versions/create-version), project maintainer can select if it will be copying from an existing version in the same project.
+When [creating new version](/user-guide/versions/create-version), project maintainer can select if it will be copying from an existing version in the same project.
## Run copy version
### From create new version page
-1. [Create a new version](user-guide/versions/create-version).
+1. [Create a new version](/user-guide/versions/create-version).
1. Select option `Copy from previous version`.
1. Select the version you wish to copy from.
In the screen shot below, this will create a new version **0.3** and copying all information and history from version **0.2**.
-
@@ -26,15 +25,15 @@ A progress bar on the version page will displays the progress of the operation.
1. Go to `Version` tab in your project page.
1. Locate the version you wish to branch off, click on the menu in front of the version and select `Copy to new version`.
1. The same menu can access by going to the version page, and click on `More action` menu on the top right. Select `Copy to new version`.
-1. This will redirect you to [Create a new version](user-guide/versions/create-version) with pre-selected version.
+1. This will redirect you to [Create a new version](/user-guide/versions/create-version) with pre-selected version.
1. Fill in new version id and click `Create Version` to start.
A progress bar on the version page will displays the progress of the operation.
\ No newline at end of file
diff --git a/docs/user-guide/translation-reuse/merge-trans.md b/docs/user-guide/translation-reuse/merge-trans.md
index 45228d0714..be107f656d 100644
--- a/docs/user-guide/translation-reuse/merge-trans.md
+++ b/docs/user-guide/translation-reuse/merge-trans.md
@@ -9,12 +9,12 @@ If there is an existing translate/approved translations, the newer translation w
1. Navigate to the project version page you wish to merge translations to.
1. Click on `More action` menu on the top right. Select `Merge translations`.
1. In popup windows, select your project and version that you want to copy translation from.
1. `Keep existing translated/approved translations` - select if you do not want to merge translation to replace existing translated/approved translations with newer translations from this source.
diff --git a/docs/user-guide/translator-guide.md b/docs/user-guide/translator-guide.md
index 2fb6d03adc..9f5c7e9bce 100644
--- a/docs/user-guide/translator-guide.md
+++ b/docs/user-guide/translator-guide.md
@@ -4,26 +4,25 @@ Any translator who has joined a language team can participate in the document tr
# Start translate a project version
-1. Login as user. Make sure you have [joined at least 1 language team](user-guide/languages/language-team#join-a-language-team).
+1. Login as user. Make sure you have [joined at least 1 language team](/user-guide/languages/language-team#join-a-language-team).
1. Click on the `Projects` menu option on the top menu.
1. Click on project you wish to translation from the list.
1. In project page, locate the version you wish to translate.
1. Click on `Options` and select `Translate in {your language}`.
# Start translating a specific document in a project version
-1. Login as user. Make sure you have [joined at least 1 language team](user-guide/languages/language-team#join-a-language-team).
+1. Login as user. Make sure you have [joined at least 1 language team](/user-guide/languages/language-team#join-a-language-team).
1. Click on the `Projects` menu option on the top menu.
1. Click on project you wish to translation from the list.
1. In project page, click on the version you wish to translate.
3. In version page, open up the language page (default page) and select a language.
-4. Click on the document name to open up the [editor](user-guide/editor/editor-view).
-
+4. Click on the document name to open up the [editor](/user-guide/editor/editor-view).
\ No newline at end of file
diff --git a/docs/user-guide/versions/create-version.md b/docs/user-guide/versions/create-version.md
index c1d4345922..cb9237e296 100644
--- a/docs/user-guide/versions/create-version.md
+++ b/docs/user-guide/versions/create-version.md
@@ -3,7 +3,7 @@ Documents for a project are grouped into versions, rather than being added direc
For simple projects, it is typical to create a single version named 'master'. Other projects use workflows in which there is a version under active development, and one or more versions that are being maintained in a stable state with only some minor changes. Grouping these related versions under the same project allows for easier reuse of translations between versions.
- 1. [Create a project](user-guide/projects/create-project).
+ 1. [Create a project](/user-guide/projects/create-project).
1. **Create a version under the project.**
1. Upload documents to the version:
- Using the website
@@ -14,11 +14,11 @@ For simple projects, it is typical to create a single version named 'master'. Ot
To add a version to your project, navigate to the project using the menu or user dashboard, go to `Version` tab, click on `More action` and select `New Version`. If your project has no versions yet, there will be a link under the project on the user dashboard to jump straight to version creation.
-![Version creation button on a project homepage](images/version-create.png)
+![Version creation button on a project homepage](/images/version-create.png)
Version creation button on a project homepage.
-![Shortcut version creation link on user dashboard](images/user-dashboard-create-version.png)
+![Shortcut version creation link on user dashboard](/images/user-dashboard-create-version.png)
Shortcut version creation link on user dashboard.
@@ -26,7 +26,7 @@ Shortcut version creation link on user dashboard.
The following screenshot shows the version creation page. The settings in this screenshot will create a version under the 'zanata-server' project with ID 'master', that does not require a review phase for translations and will use the locales and validations from the project. See below for details on each option.
-![Example version creation form with completed name and other settings](images/version-create-new.png)
+![Example version creation form with completed name and other settings](/images/version-create-new.png)
### Version ID
@@ -35,12 +35,12 @@ This is the identifier used to refer to the version on the Zanata website and wh
### Project Type
-Project Type defines the type of files that your project uses to store source and translation strings. For more information see the [Project Types](user-guide/projects/project-types).
+Project Type defines the type of files that your project uses to store source and translation strings. For more information see the [Project Types](/user-guide/projects/project-types).
### Copy from previous version
If selected, this new version is will based off the selected version.
-See [Copy version](user-guide/translation-reuse/copy-version) for more information.
+See [Copy version](/user-guide/translation-reuse/copy-version) for more information.
## Version creation through the command line
diff --git a/docs/user-guide/versions/merge-translations.md b/docs/user-guide/versions/merge-translations.md
index 727b19a0dc..d9a2551475 100644
--- a/docs/user-guide/versions/merge-translations.md
+++ b/docs/user-guide/versions/merge-translations.md
@@ -33,17 +33,17 @@ Merge translations allows project maintainers to copy translations across differ
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.
1. In displayed dialog, select the project-version you wish to copy translations from.
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.
## Cancel Merge translation
@@ -52,7 +52,7 @@ Merge translations allows project maintainers to copy translations across differ
1. Go to the progress bar section in project version page.
1. Click on the `Cancel` button on top right panel.
diff --git a/docs/user-guide/versions/request-to-join-group.md b/docs/user-guide/versions/request-to-join-group.md
index 7588a9fd0c..bead21d520 100644
--- a/docs/user-guide/versions/request-to-join-group.md
+++ b/docs/user-guide/versions/request-to-join-group.md
@@ -3,11 +3,11 @@ Project maintainer can request for their project version to be part of certain G
1. Navigate to the group page you wish to join.
2. On the right panel, click on the drop down menu, and select "Request to add a project version to {{Group name}}".
3. In the dialog, search and click from the drop down for your maintained project, and check the version you wish to join into the group.
4. Fill in `Additional information` for group maintainer and click `Send`.
\ No newline at end of file
diff --git a/docs/user-guide/versions/version-settings.md b/docs/user-guide/versions/version-settings.md
index b49b090871..a19bf777cf 100644
--- a/docs/user-guide/versions/version-settings.md
+++ b/docs/user-guide/versions/version-settings.md
@@ -1,23 +1,22 @@
Once a version has been created, the maintainer can add further details and version behaviour via the Settings tab.
-See the [Version Creation Help](user-guide/versions/create-version) for details on creating version.
+See the [Version Creation Help](/user-guide/versions/create-version) for details on creating version.
------------
## General Settings
-
### Project Type
-Project Type settings by default inherits from project's settings but maintainers are able to select a different project type for the version. See [Project Types](user-guide/projects/project-types) and [Project Settings](user-guide/projects/project-settings/#project-type) for more information.
+Project Type settings by default inherits from project's settings but maintainers are able to select a different project type for the version. See [Project Types](/user-guide/projects/project-types) and [Project Settings](/user-guide/projects/project-settings/#project-type) for more information.
### Make this version read only
@@ -28,9 +27,8 @@ This can be toggled using the same button, as desired.
------------
## Documents Settings
-
@@ -43,9 +41,8 @@ Click `+` sign on top left in Documents tab under Settings. Browse or Drag your
------------
## Languages Settings
-
@@ -58,19 +55,18 @@ By default, your project will be available for translation to a set of locales d
------------
## Translation Settings
-
------------
### Require translation review
-See [Review overview](user-guide/review/overview) for more information.
+See [Review overview](/user-guide/review/overview) for more information.
### Customized list of validations
If your version requires a different set of validations than the parent project, they can be selected here. If customized validations are not specified, the validations specified for your project will be used. An advantage of inheriting validations from the poject is that new validations can be added to the project without having to add them to each different version.
-For more information, see [Project settings](user-guide/projects/project-settings#validations).
+For more information, see [Project settings](/user-guide/projects/project-settings#validations).
diff --git a/docs/user-guide/versions/version-view.md b/docs/user-guide/versions/version-view.md
index ccb118208a..089e027472 100644
--- a/docs/user-guide/versions/version-view.md
+++ b/docs/user-guide/versions/version-view.md
@@ -1,8 +1,6 @@
When a project version is selected, you will be shown specific details about the selected version.
-
diff --git a/etc/scripts/extractAppserver.groovy b/etc/scripts/extractAppserver.groovy
new file mode 100644
index 0000000000..f6d4520966
--- /dev/null
+++ b/etc/scripts/extractAppserver.groovy
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+/**
+ * @author Sean Flanigan sflaniga@redhat.com
+ */
+
+import groovy.util.AntBuilder
+
+// NB: project is a MavenProject
+// http://maven.apache.org/ref/3-LATEST/maven-core/apidocs/org/apache/maven/project/MavenProject.html
+
+String downloadDir = project.properties.get('download.dir') // ~/Downloads
+String cargoExtractDir = project.properties.get('cargo.extract.dir') // target/cargo/installs
+String url = project.properties.get('cargo.installation') // http://example.com/jbosseap6.zip
+
+String filename = url.substring(url.lastIndexOf('/')+1)
+String filePath = "${downloadDir}/${filename}"
+String basename = filename.substring(0, filename.lastIndexOf('.'))
+String extractDir = "${cargoExtractDir}/${basename}"
+
+def ant = new AntBuilder()
+ant.mkdir(dir: downloadDir)
+ant.get(src: url, dest: filePath, skipexisting: "true")
+ant.unzip(src: filePath, dest:"${extractDir}")
+
+def files = new File(extractDir).listFiles()
+if (files.length != 1) {
+ throw new Exception('zip should contain exactly one top-level dir; see ' + extractDir)
+}
+def topLevelDir = files[0].path
+
+project.properties.put('appserver.home', topLevelDir)
diff --git a/frontend/.gitignore b/frontend/.gitignore
new file mode 100644
index 0000000000..ab7bf238c3
--- /dev/null
+++ b/frontend/.gitignore
@@ -0,0 +1,2 @@
+# npm modules
+node_modules/
diff --git a/frontend/README.md b/frontend/README.md
new file mode 100644
index 0000000000..94f223f4bb
--- /dev/null
+++ b/frontend/README.md
@@ -0,0 +1,30 @@
+## This is a module to build zanata frontend javascript projects
+
+At the moment it only contains "user profile page" bundle.
+
+To build it, just run
+
+```mvn install```
+
+It will build and deploy to local maven repository a jar file containing the javascript bundle.
+The jar file can be used directly under any servlet 3 compatible container and the bundle is accessible as static resources.
+See [Servlet 3 static resources](http://www.webjars.org/documentation#servlet3).
+
+The following Maven properties can be overridden on the command line with ```-Dkey=value```:
+
+```
+v0.12.2
+2.7.6
+${download.dir}/zanata-frontend/node-${node.version}-npm-${npm.version}
+${node.install.directory}/node/npm/bin/npm-cli.js
+```
+
+By default it will try to install npm modules from npm registry (default cache TTL is 10 seconds).
+If you activate profile ```-DnpmOffline``` the cache-min option will become 9999999 which means it will try to install npm modules from cache first.
+
+## NPM shrinkwrap
+
+Currently the user profile page module has been "shrinkwrapped" which means its npm module dependencies has been fixed to certain version. If you want to add or upgrade an individual version, you will need to consult [npm shrinkwrap documentation](https://docs.npmjs.com/cli/shrinkwrap#building-shrinkwrapped-packages) for detail instruction.
+
+Since we use maven to copy our source to target/ then run npm from maven, you will need to run above commands under target/ then copy the new npm-shrinkwrap.json file back to src/.
+
diff --git a/frontend/pom.xml b/frontend/pom.xml
new file mode 100644
index 0000000000..f4e9f91e73
--- /dev/null
+++ b/frontend/pom.xml
@@ -0,0 +1,137 @@
+
+
+ 4.0.0
+
+ org.zanata
+ server
+ 3.8.0-SNAPSHOT
+
+ frontend
+ frontend
+ http://maven.apache.org
+
+ UTF-8
+ v0.12.2
+ 2.7.6
+ ${project.build.directory}/web
+ ${project.build.outputDirectory}/META-INF/resources
+ ${download.dir}/zanata-frontend/node-${node.version}-npm-${npm.version}
+ ${node.install.directory}/node/npm/bin/npm-cli.js
+
+ 10
+
+
+ user-profile-page
+
+
+
+
+
+
+
+
+
+
+ false
+ src/main/web
+ ${web.target}
+
+
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+ 0.0.23
+
+ ${node.install.directory}
+
+
+
+
+ install node and npm
+ generate-resources
+
+ install-node-and-npm
+
+
+ ${node.version}
+ ${npm.version}
+
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 1.4.0
+
+
+ ${node.install.directory}/node:${node.install.directory}/node/npm/bin:${env.PATH}
+
+ ${node.install.directory}/node/node
+
+
+
+
+
+ execute npm install for: ${module.user-profile-page}
+ process-resources
+
+ exec
+
+
+ ${web.target}/${module.user-profile-page}
+
+ ${npm.cli.script}
+ install
+ --cache-min
+ ${npm.cache.min}
+
+
+
+
+ execute npm run build for: ${module.user-profile-page}
+ compile
+
+ exec
+
+
+ ${web.target}/${module.user-profile-page}
+
+ ${npm.cli.script}
+ run
+ build
+ --env.bundleDest=${bundle.dest}
+
+
+
+
+
+
+
+ maven-surefire-plugin
+
+ true
+ false
+
+
+
+
+
+
+
+
+ npmOffline
+
+
+ npmOffline
+
+
+
+ 9999999
+
+
+
+
+
diff --git a/frontend/src/main/web/user-profile-page/README.md b/frontend/src/main/web/user-profile-page/README.md
new file mode 100644
index 0000000000..14aed5aec8
--- /dev/null
+++ b/frontend/src/main/web/user-profile-page/README.md
@@ -0,0 +1,17 @@
+# React Profile
+
+## Setup
+
+`npm install`
+
+## Options
+
+### Production Build
+
+`npm run build`
+
+### Development Server
+
+#### (a HTTP server to serve index.html with webpack produced bundle.js)
+
+`npm start`
diff --git a/frontend/src/main/web/user-profile-page/copy.sh b/frontend/src/main/web/user-profile-page/copy.sh
new file mode 100755
index 0000000000..26457fe7c5
--- /dev/null
+++ b/frontend/src/main/web/user-profile-page/copy.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+
+arg1=$1
+arg2=$2
+
+srcDest=${arg1:="$HOME/work/root/server/zanata-war/src/main/webapp/profile/js/"}
+deployedDest=${arg2:="/NotBackedUp/tools/jboss-eap/standalone/deployments/zanata.war/profile/js/"}
+
+echo "will copy bundle.min.js to [$srcDest] and [$deployedDest]"
+
+cp bundle.min.js ${srcDest}
+cp bundle.min.js ${deployedDest}
+
diff --git a/frontend/src/main/web/user-profile-page/index.html b/frontend/src/main/web/user-profile-page/index.html
new file mode 100644
index 0000000000..f7f3643bb7
--- /dev/null
+++ b/frontend/src/main/web/user-profile-page/index.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+ Zanata Profile
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/main/web/user-profile-page/index.js b/frontend/src/main/web/user-profile-page/index.js
new file mode 100644
index 0000000000..ac503b60cc
--- /dev/null
+++ b/frontend/src/main/web/user-profile-page/index.js
@@ -0,0 +1,10 @@
+import React from 'react';
+import RecentContributions from './lib/components/RecentContributions';
+import Configs from './lib/constants/Configs';
+
+var mountNode = document.getElementById('userMatrixRoot'),
+ baseUrl = mountNode.getAttribute('data-base-url');
+
+Configs.baseUrl = baseUrl;
+
+React.render(, mountNode);
diff --git a/frontend/src/main/web/user-profile-page/lib/actions/Actions.js b/frontend/src/main/web/user-profile-page/lib/actions/Actions.js
new file mode 100644
index 0000000000..04b7b6a959
--- /dev/null
+++ b/frontend/src/main/web/user-profile-page/lib/actions/Actions.js
@@ -0,0 +1,44 @@
+import Dispatcher from '../dispatchers/UserMatrixDispatcher';
+import ActionTypes from '../constants/ActionTypes';
+
+
+var Actions = {
+ changeDateRange: function(dateRangeOption) {
+ Dispatcher.handleViewAction(
+ {
+ actionType: ActionTypes.DATE_RANGE_UPDATE,
+ data: dateRangeOption
+ }
+ );
+ },
+
+ changeContentState: function(contentState) {
+ Dispatcher.handleViewAction(
+ {
+ actionType: ActionTypes.CONTENT_STATE_UPDATE,
+ data: contentState
+ }
+ );
+ },
+
+ onDaySelected: function(day) {
+ Dispatcher.handleViewAction(
+ {
+ actionType: ActionTypes.DAY_SELECTED,
+ data: day
+ }
+ );
+ },
+
+ clearSelectedDay: function() {
+ Dispatcher.handleViewAction(
+ {
+ actionType: ActionTypes.DAY_SELECTED,
+ data: null
+ }
+ );
+ }
+
+};
+
+export default Actions;
diff --git a/frontend/src/main/web/user-profile-page/lib/components/CalendarMonthMatrix.jsx b/frontend/src/main/web/user-profile-page/lib/components/CalendarMonthMatrix.jsx
new file mode 100644
index 0000000000..8c23ba193b
--- /dev/null
+++ b/frontend/src/main/web/user-profile-page/lib/components/CalendarMonthMatrix.jsx
@@ -0,0 +1,106 @@
+import React from 'react';
+import moment from 'moment-range';
+import DayMatrix from './DayMatrix';
+import Actions from '../actions/Actions';
+import {ContentStates} from '../constants/Options';
+import {DateRanges} from '../constants/Options';
+
+var CalendarMonthMatrix = React.createClass({
+ propTypes: {
+ matrixData: React.PropTypes.arrayOf(
+ React.PropTypes.shape(
+ {
+ date: React.PropTypes.string.isRequired,
+ wordCount: React.PropTypes.number.isRequired
+ })
+ ).isRequired,
+ selectedDay: React.PropTypes.string,
+ selectedContentState: React.PropTypes.oneOf(ContentStates).isRequired,
+ dateRangeOption: React.PropTypes.oneOf(DateRanges).isRequired
+ },
+
+ getDefaultProps: function() {
+ // this is to make week days locale aware and making sure it align with
+ // below display
+ var now = moment(),
+ weekDays = [],
+ weekDay;
+ for (var i = 0; i < 7; i++) {
+ weekDay = now.weekday(i).format('ddd');
+ weekDays.push(
+ }
+
+ firstDay = moment(matrixData[0]['date']);
+ for (var i = firstDay.weekday() - 1; i >= 0; i--) {
+ // for the first week, we pre-fill missing week days
+ days.push(
T refreshPageUntil(P currentPage,
return done;
}
- /**
- * Wait for certain condition to happen.
- *
- * For example, wait for a translation updated event gets broadcast to editor.
- *
- * @param callable a callable that returns a result
- * @param matcher a matcher that matches to expected result
- * @param result type
- * @deprecated use waitForPageSilence() and then simply check the condition
- */
- @Deprecated
- public void
- waitFor(final Callable callable, final Matcher matcher) {
- waitForAMoment().withMessage(StringDescription.toString(matcher)).until(
- new Predicate() {
- @Override
- public boolean apply(WebDriver input) {
- try {
- T result = callable.call();
- if (!matcher.matches(result)) {
- matcher.describeMismatch(result,
- new Description.NullDescription());
- }
- return matcher.matches(result);
- } catch (Exception e) {
- log.warn("exception", e);
- return false;
- }
- }
- });
- }
-
/**
* Normally a page has no outstanding ajax requests when it has
* finished an operation, but some pages use long polling to
@@ -317,14 +268,6 @@ public boolean apply(WebDriver input) {
});
}
- /**
- * @deprecated use readyElement
- */
- @Deprecated
- public WebElement waitForWebElement(final By elementBy) {
- return readyElement(elementBy);
- }
-
/**
* Expect an element to be interactive, and return it
* @param elementBy WebDriver By locator
@@ -340,15 +283,6 @@ public WebElement readyElement(final By elementBy) {
return targetElement;
}
- /**
- * @deprecated use readyElement
- */
- @Deprecated
- public WebElement waitForWebElement(final WebElement parentElement,
- final By elementBy) {
- return readyElement(parentElement, elementBy);
- }
-
/**
* Wait for a child element to be visible, and return it
* @param parentElement parent element of target
@@ -365,13 +299,6 @@ public WebElement readyElement(final WebElement parentElement,
return targetElement;
}
- /**
- * @deprecated use existingElement
- */
- public WebElement waitForElementExists(final By elementBy) {
- return existingElement(elementBy);
- }
-
/**
* Wait for an element to exist on the page, and return it.
* Generally used for situations where checking on the state of an element,
@@ -391,15 +318,6 @@ public WebElement apply(WebDriver input) {
});
}
- /**
- * @deprecated use existingElement
- */
- @Deprecated
- public WebElement waitForElementExists(final WebElement parentElement,
- final By elementBy) {
- return existingElement(parentElement, elementBy);
- }
-
/**
* Wait for a child element to exist on the page, and return it.
* Generally used for situations where checking on the state of an element,
@@ -420,6 +338,24 @@ public WebElement apply(WebDriver input) {
});
}
+ /**
+ * Convenience function for clicking elements. Removes obstructing
+ * elements, scrolls the item into view and clicks it when it is ready.
+ * @param findby
+ */
+ public void clickElement(By findby) {
+ clickElement(readyElement(findby));
+ }
+
+ public void clickElement(final WebElement element) {
+ removeNotifications();
+ waitForNotificationsGone();
+ scrollIntoView(element);
+ waitForAMoment().withMessage("clickable: " + element.toString()).until(
+ ExpectedConditions.elementToBeClickable(element));
+ element.click();
+ }
+
private void waitForElementReady(final WebElement element) {
waitForAMoment().withMessage("Waiting for element to be ready").until(
new Predicate() {
@@ -435,4 +371,203 @@ private void assertReady(WebElement targetElement) {
assertThat(targetElement.isDisplayed()).as("displayed").isTrue();
assertThat(targetElement.isEnabled()).as("enabled").isTrue();
}
+
+ /**
+ * Remove any visible notifications
+ */
+ public void removeNotifications() {
+ @SuppressWarnings("unchecked")
+ List notifications = (List) getExecutor()
+ .executeScript("return (typeof $ == 'undefined') ? [] : " +
+ "$('a.message__remove').toArray()");
+ if (notifications.isEmpty()) {
+ return;
+ }
+ log.info("Closing {} notifications", notifications.size());
+ for (WebElement notification : notifications) {
+ try {
+ notification.click();
+ } catch (WebDriverException exc) {
+ log.info("Missed a notification X click");
+ }
+ }
+ // Finally, forcibly un-is-active the message container - for speed
+ String script = "return (typeof $ == 'undefined') ? [] : " +
+ "$('ul.message--global').toArray()";
+ @SuppressWarnings("unchecked")
+ List messageBoxes = ((List) getExecutor()
+ .executeScript(script));
+ for (WebElement messageBox : messageBoxes) {
+ getExecutor().executeScript(
+ "arguments[0].setAttribute('class', arguments[1]);",
+ messageBox,
+ messageBox.getAttribute("class").replace("is-active", ""));
+ }
+ }
+
+ /**
+ * Wait for the notifications box to go. Assumes test has dealt with
+ * removing it, or is waiting for it to time out.
+ */
+ public void waitForNotificationsGone() {
+ final String script = "return (typeof $ == 'undefined') ? [] : " +
+ "$('ul.message--global').toArray()";
+ final String message = "Waiting for notifications box to go";
+ waitForAMoment().withMessage(message).until(new Predicate() {
+ @Override
+ public boolean apply(WebDriver input) {
+ @SuppressWarnings("unchecked")
+ List boxes = (List) getExecutor()
+ .executeScript(script);
+ for (WebElement box : boxes) {
+ if (box.isDisplayed()) {
+ log.info(message);
+ return false;
+ }
+ }
+ return true;
+ }
+ });
+ }
+
+ /**
+ * Shift focus to the page, in order to activate some elements that only
+ * exhibit behaviour on losing focus. Some pages with contained objects
+ * cause object behaviour to occur when interacted with, so in this case
+ * interact with the container instead.
+ */
+ public void defocus() {
+ log.info("Click off element focus");
+ List webElements =
+ getDriver().findElements(By.id("container"));
+ webElements.addAll(getDriver().findElements(By.tagName("body")));
+ if (webElements.size() > 0) {
+ webElements.get(0).click();
+ } else {
+ log.warn("Unable to focus page container");
+ }
+ }
+
+ /**
+ * Force the blur 'unfocus' process on a given element
+ */
+ public void defocus(By elementBy) {
+ log.info("Force unfocus");
+ WebElement element = existingElement(elementBy);
+ getExecutor().executeScript("arguments[0].blur()", element);
+ waitForPageSilence();
+ }
+
+ /* The system sometimes moves too fast for the Ajax pages, so provide a
+ * pause
+ */
+ public void slightPause() {
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException ie) {
+ log.warn("Pause was interrupted");
+ }
+ }
+
+ public void scrollIntoView(WebElement targetElement) {
+ getExecutor().executeScript(
+ "arguments[0].scrollIntoView(true);", targetElement);
+ }
+
+ public void scrollToTop() {
+ getExecutor().executeScript("scroll(0, 0);");
+ }
+
+
+ public String getHtmlSource(WebElement webElement) {
+ return (String) getExecutor().executeScript(
+ "return arguments[0].innerHTML;", webElement);
+ }
+
+ // TODO: Deprecated functions, remove after 3.7
+
+ /**
+ * @deprecated Use the overload which includes a message
+ */
+ @Deprecated
+ protected
P refreshPageUntil(P currentPage,
+ Predicate predicate) {
+ return refreshPageUntil(currentPage, predicate, null);
+ }
+
+ /**
+ * @deprecated Use the overload which includes a message
+ */
+ @Deprecated
+ protected
T refreshPageUntil(P currentPage,
+ Function function) {
+ return refreshPageUntil(currentPage, function, null);
+ }
+
+ /**
+ * Wait for certain condition to happen.
+ *
+ * For example, wait for a translation updated event gets broadcast to editor.
+ *
+ * @param callable a callable that returns a result
+ * @param matcher a matcher that matches to expected result
+ * @param result type
+ * @deprecated use waitForPageSilence() and then simply check the condition
+ */
+ @Deprecated
+ public void
+ waitFor(final Callable callable, final Matcher matcher) {
+ waitForAMoment().withMessage(StringDescription.toString(matcher)).until(
+ new Predicate() {
+ @Override
+ public boolean apply(WebDriver input) {
+ try {
+ T result = callable.call();
+ if (!matcher.matches(result)) {
+ matcher.describeMismatch(result,
+ new Description.NullDescription());
+ }
+ return matcher.matches(result);
+ } catch (Exception e) {
+ log.warn("exception", e);
+ return false;
+ }
+ }
+ });
+ }
+
+ /**
+ * @deprecated use readyElement
+ */
+ @Deprecated
+ public WebElement waitForWebElement(final By elementBy) {
+ return readyElement(elementBy);
+ }
+
+ /**
+ * @deprecated use readyElement
+ */
+ @Deprecated
+ public WebElement waitForWebElement(final WebElement parentElement,
+ final By elementBy) {
+ return readyElement(parentElement, elementBy);
+ }
+
+
+ /**
+ * @deprecated use existingElement
+ */
+ public WebElement waitForElementExists(final By elementBy) {
+ return existingElement(elementBy);
+ }
+
+ /**
+ * @deprecated use existingElement
+ */
+ @Deprecated
+ public WebElement waitForElementExists(final WebElement parentElement,
+ final By elementBy) {
+ return existingElement(parentElement, elementBy);
+ }
+
}
diff --git a/functional-test/src/main/java/org/zanata/page/BasePage.java b/functional-test/src/main/java/org/zanata/page/BasePage.java
index de3a5159d5..3991e8c94e 100644
--- a/functional-test/src/main/java/org/zanata/page/BasePage.java
+++ b/functional-test/src/main/java/org/zanata/page/BasePage.java
@@ -85,7 +85,7 @@ public BasePage(final WebDriver driver) {
public DashboardBasePage goToMyDashboard() {
log.info("Click Dashboard menu link");
- readyElement(userAvatar).click();
+ clickElement(userAvatar);
clickLinkAfterAnimation(BY_DASHBOARD_LINK);
return new DashboardBasePage(getDriver());
}
@@ -101,18 +101,9 @@ private void clickNavMenuItem(final WebElement menuItem) {
slightPause();
if (!menuItem.isDisplayed()) {
// screen is too small the menu become dropdown
- readyElement(existingElement(By.id("nav-main")), By.tagName("a")).click();
+ clickElement(readyElement(existingElement(By.id("nav-main")), By.tagName("a")));
}
- waitForAMoment().withMessage("displayed: " + menuItem).until(
- new Predicate() {
- @Override
- public boolean apply(WebDriver input) {
- return menuItem.isDisplayed();
- }
- });
- waitForAMoment().withMessage("clickable: " + menuItem).until(
- ExpectedConditions.elementToBeClickable(menuItem));
- menuItem.click();
+ clickElement(menuItem);
}
public VersionGroupsPage goToGroups() {
@@ -318,7 +309,7 @@ public WebElement apply(WebDriver driver) {
return null;
}
});
- searchItem.click();
+ clickElement(searchItem);
}
public void clickWhenTabEnabled(final WebElement tab) {
@@ -326,11 +317,6 @@ public void clickWhenTabEnabled(final WebElement tab) {
clickElement(tab);
}
- public String getHtmlSource(WebElement webElement) {
- return (String) getExecutor().executeScript(
- "return arguments[0].innerHTML;", webElement);
- }
-
/**
* Check if the page has the home button, expecting a valid base page
* @return boolean is valid
@@ -339,80 +325,4 @@ public boolean isPageValid() {
return (getDriver().findElements(By.id("home"))).size() > 0;
}
- /**
- * Convenience function for clicking elements. Removes obstructing
- * elements, scrolls the item into view and clicks it when it is ready.
- * @param findby
- */
- public void clickElement(By findby) {
- clickElement(readyElement(findby));
- }
-
- public void clickElement(final WebElement element) {
- removeNotifications();
- waitForNotificationsGone();
- scrollIntoView(element);
- waitForAMoment().withMessage("clickable: " + element.toString()).until(
- ExpectedConditions.elementToBeClickable(element));
- element.click();
- }
-
- /**
- * Remove any visible notifications
- */
- public void removeNotifications() {
- @SuppressWarnings("unchecked")
- List notifications = (List) getExecutor()
- .executeScript("return (typeof $ == 'undefined') ? [] : " +
- "$('a.message__remove').toArray()");
- if (notifications.isEmpty()) {
- return;
- }
- log.info("Closing {} notifications", notifications.size());
- for (WebElement notification : notifications) {
- try {
- notification.click();
- } catch (WebDriverException exc) {
- log.info("Missed a notification X click");
- }
- }
- // Finally, forcibly un-is-active the message container - for speed
- String script = "return (typeof $ == 'undefined') ? [] : " +
- "$('ul.message--global').toArray()";
- @SuppressWarnings("unchecked")
- List messageBoxes = ((List) getExecutor()
- .executeScript(script));
- for (WebElement messageBox : messageBoxes) {
- getExecutor().executeScript(
- "arguments[0].setAttribute('class', arguments[1]);",
- messageBox,
- messageBox.getAttribute("class").replace("is-active", ""));
- }
- }
-
- /**
- * Wait for the notifications box to go. Assumes test has dealt with
- * removing it, or is waiting for it to time out.
- */
- public void waitForNotificationsGone() {
- final String script = "return (typeof $ == 'undefined') ? [] : " +
- "$('ul.message--global').toArray()";
- final String message = "Waiting for notifications box to go";
- waitForAMoment().withMessage(message).until(new Predicate() {
- @Override
- public boolean apply(WebDriver input) {
- @SuppressWarnings("unchecked")
- List boxes = (List) getExecutor()
- .executeScript(script);
- for (WebElement box : boxes) {
- if (box.isDisplayed()) {
- log.info(message);
- return false;
- }
- }
- return true;
- }
- });
- }
-
}
diff --git a/functional-test/src/main/java/org/zanata/page/CorePage.java b/functional-test/src/main/java/org/zanata/page/CorePage.java
index 75a5f54252..2e0b53b9bc 100644
--- a/functional-test/src/main/java/org/zanata/page/CorePage.java
+++ b/functional-test/src/main/java/org/zanata/page/CorePage.java
@@ -62,12 +62,12 @@ public String getTitle() {
public HomePage goToHomePage() {
log.info("Click Zanata home icon");
scrollToTop();
- readyElement(homeLink).click();
+ clickElement(homeLink);
return new HomePage(getDriver());
}
protected void clickAndCheckErrors(WebElement button) {
- button.click();
+ clickElement(button);
List errors = getErrors();
if (!errors.isEmpty()) {
throw new RuntimeException(Joiner.on(";").join(errors));
@@ -75,7 +75,7 @@ protected void clickAndCheckErrors(WebElement button) {
}
protected void clickAndExpectErrors(WebElement button) {
- button.click();
+ clickElement(button);
refreshPageUntil(this, new Predicate() {
@Override
public boolean apply(WebDriver input) {
@@ -90,9 +90,10 @@ public List getErrors() {
WebElementUtil.elementsToText(getDriver(),
By.xpath("//span[@class='errors']"));
+ // app-error is a pseudo class we put in just for this
List newError =
WebElementUtil.elementsToText(getDriver(),
- By.className("message--danger"));
+ By.className("app-error"));
List allErrors = Lists.newArrayList();
allErrors.addAll(oldError);
@@ -177,51 +178,4 @@ public void assertNoCriticalErrors() {
}
}
- /**
- * Shift focus to the page, in order to activate some elements that only
- * exhibit behaviour on losing focus. Some pages with contained objects
- * cause object behaviour to occur when interacted with, so in this case
- * interact with the container instead.
- */
- public void defocus() {
- log.info("Click off element focus");
- List webElements =
- getDriver().findElements(By.id("container"));
- webElements.addAll(getDriver().findElements(By.tagName("body")));
- if (webElements.size() > 0) {
- webElements.get(0).click();
- } else {
- log.warn("Unable to focus page container");
- }
- }
-
- /**
- * Force the blur 'unfocus' process on a given element
- */
- public void defocus(By elementBy) {
- log.info("Force unfocus");
- WebElement element = existingElement(elementBy);
- getExecutor().executeScript("arguments[0].blur()", element);
- waitForPageSilence();
- }
-
- /* The system sometimes moves too fast for the Ajax pages, so provide a
- * pause
- */
- public void slightPause() {
- try {
- Thread.sleep(500);
- } catch (InterruptedException ie) {
- log.warn("Pause was interrupted");
- }
- }
-
- public void scrollIntoView(WebElement targetElement) {
- getExecutor().executeScript(
- "arguments[0].scrollIntoView(true);", targetElement);
- }
-
- public void scrollToTop() {
- getExecutor().executeScript("scroll(0, 0);");
- }
}
diff --git a/functional-test/src/main/java/org/zanata/page/account/EditProfilePage.java b/functional-test/src/main/java/org/zanata/page/account/EditProfilePage.java
index 24f0326dcf..2ca8499a1a 100644
--- a/functional-test/src/main/java/org/zanata/page/account/EditProfilePage.java
+++ b/functional-test/src/main/java/org/zanata/page/account/EditProfilePage.java
@@ -68,13 +68,13 @@ public EditProfilePage enterEmail(String email) {
public HomePage clickSave() {
log.info("Click Save");
- readyElement(saveButton).click();
+ clickElement(saveButton);
return new HomePage(getDriver());
}
public EditProfilePage clickSaveAndExpectErrors() {
log.info("Click Save");
- readyElement(saveButton).click();
+ clickElement(saveButton);
return new EditProfilePage(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/account/InactiveAccountPage.java b/functional-test/src/main/java/org/zanata/page/account/InactiveAccountPage.java
index 369354f4dd..09d18257ba 100644
--- a/functional-test/src/main/java/org/zanata/page/account/InactiveAccountPage.java
+++ b/functional-test/src/main/java/org/zanata/page/account/InactiveAccountPage.java
@@ -38,7 +38,7 @@ public InactiveAccountPage(WebDriver driver) {
public HomePage clickResendActivationEmail() {
log.info("Click resend activation email");
- readyElement(By.id("resendEmail")).click();
+ clickElement(By.id("resendEmail"));
return new HomePage(getDriver());
}
@@ -49,7 +49,7 @@ public InactiveAccountPage enterNewEmail(String email) {
}
public HomePage clickUpdateEmail() {
- readyElement(By.id("inactiveAccountForm:emailField:updateEmail")).click();
+ clickElement(By.id("inactiveAccountForm:emailField:updateEmail"));
return new HomePage(getDriver());
}
}
diff --git a/functional-test/src/main/java/org/zanata/page/account/RegisterPage.java b/functional-test/src/main/java/org/zanata/page/account/RegisterPage.java
index 57a9336cb2..32d3a6c85d 100644
--- a/functional-test/src/main/java/org/zanata/page/account/RegisterPage.java
+++ b/functional-test/src/main/java/org/zanata/page/account/RegisterPage.java
@@ -89,13 +89,13 @@ public RegisterPage enterPassword(String password) {
// TODO: Add a "signup success" page
public HomePage register() {
log.info("Click Sign Up");
- readyElement(signUpButton).click();
+ clickElement(signUpButton);
return new HomePage(getDriver());
}
public RegisterPage registerFailure() {
log.info("Click Sign Up");
- readyElement(signUpButton).click();
+ clickElement(signUpButton);
return new RegisterPage(getDriver());
}
@@ -127,13 +127,13 @@ public String getPageTitle() {
public SignInPage goToSignIn() {
log.info("Click Log In");
- readyElement(loginLink).click();
+ clickElement(loginLink);
return new SignInPage(getDriver());
}
public RegisterPage clickPasswordShowToggle() {
log.info("Click Show/Hide");
- readyElement(showHideToggleButton).click();
+ clickElement(showHideToggleButton);
return new RegisterPage(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/account/ResetPasswordPage.java b/functional-test/src/main/java/org/zanata/page/account/ResetPasswordPage.java
index afca467631..ec327fd80b 100644
--- a/functional-test/src/main/java/org/zanata/page/account/ResetPasswordPage.java
+++ b/functional-test/src/main/java/org/zanata/page/account/ResetPasswordPage.java
@@ -59,7 +59,7 @@ public HomePage resetPassword() {
log.info("Click Submit");
defocus(usernameEmailField);
waitForPageSilence();
- readyElement(submitButton).click();
+ clickElement(submitButton);
return new HomePage(getDriver());
}
@@ -67,7 +67,7 @@ public ResetPasswordPage resetFailure() {
log.info("Click Submit");
defocus(usernameEmailField);
waitForPageSilence();
- readyElement(submitButton).click();
+ clickElement(submitButton);
return new ResetPasswordPage(getDriver());
}
}
diff --git a/functional-test/src/main/java/org/zanata/page/account/SignInPage.java b/functional-test/src/main/java/org/zanata/page/account/SignInPage.java
index 812f23a837..54a6e55c51 100644
--- a/functional-test/src/main/java/org/zanata/page/account/SignInPage.java
+++ b/functional-test/src/main/java/org/zanata/page/account/SignInPage.java
@@ -61,37 +61,37 @@ public SignInPage enterPassword(String password) {
public DashboardBasePage clickSignIn() {
log.info("Click Sign In");
- readyElement(signInButton).click();
+ clickElement(signInButton);
return new DashboardBasePage(getDriver());
}
public SignInPage clickSignInExpectError() {
log.info("Click Sign In");
- readyElement(signInButton).click();
+ clickElement(signInButton);
return new SignInPage(getDriver());
}
public InactiveAccountPage clickSignInExpectInactive() {
log.info("Click Sign In");
- readyElement(signInButton).click();
+ clickElement(signInButton);
return new InactiveAccountPage(getDriver());
}
public GoogleAccountPage selectGoogleOpenID() {
log.info("Click 'Google'");
- readyElement(googleButton).click();
+ clickElement(googleButton);
return new GoogleAccountPage(getDriver());
}
public ResetPasswordPage goToResetPassword() {
log.info("Click Forgot Password");
- readyElement(forgotPasswordLink).click();
+ clickElement(forgotPasswordLink);
return new ResetPasswordPage(getDriver());
}
public RegisterPage goToRegister() {
log.info("Click Sign Up");
- readyElement(signUpLink).click();
+ clickElement(signUpLink);
return new RegisterPage(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/administration/AddLanguagePage.java b/functional-test/src/main/java/org/zanata/page/administration/AddLanguagePage.java
index 84b50c2efb..42e90e472e 100644
--- a/functional-test/src/main/java/org/zanata/page/administration/AddLanguagePage.java
+++ b/functional-test/src/main/java/org/zanata/page/administration/AddLanguagePage.java
@@ -90,7 +90,7 @@ public AddLanguagePage expectPluralsWarning() {
public AddLanguagePage enableLanguageByDefault() {
log.info("Click Enable by default");
if (!readyElement(enabledByDefaultCheckbox).isSelected()) {
- readyElement(enabledByDefaultCheckbox).click();
+ clickElement(enabledByDefaultCheckbox);
}
return new AddLanguagePage(getDriver());
}
@@ -98,7 +98,7 @@ public AddLanguagePage enableLanguageByDefault() {
public AddLanguagePage disableLanguageByDefault() {
log.info("Click Disable by default");
if (readyElement(enabledByDefaultCheckbox).isSelected()) {
- readyElement(enabledByDefaultCheckbox).click();
+ clickElement(enabledByDefaultCheckbox);
}
return new AddLanguagePage(getDriver());
diff --git a/functional-test/src/main/java/org/zanata/page/administration/EditHomeContentPage.java b/functional-test/src/main/java/org/zanata/page/administration/EditHomeContentPage.java
index c0967102e7..5a9a1f73cb 100644
--- a/functional-test/src/main/java/org/zanata/page/administration/EditHomeContentPage.java
+++ b/functional-test/src/main/java/org/zanata/page/administration/EditHomeContentPage.java
@@ -49,13 +49,13 @@ public EditHomeContentPage enterText(String text) {
public HomePage update() {
log.info("Click Update");
- readyElement(updateButton).click();
+ clickElement(updateButton);
return new HomePage(getDriver());
}
public HomePage cancelUpdate() {
log.info("Click Cancel");
- readyElement(cancelButton).click();
+ clickElement(cancelButton);
return new HomePage(getDriver());
}
}
diff --git a/functional-test/src/main/java/org/zanata/page/administration/EditRoleAssignmentPage.java b/functional-test/src/main/java/org/zanata/page/administration/EditRoleAssignmentPage.java
index 4537576f1e..a4dc33e8c9 100644
--- a/functional-test/src/main/java/org/zanata/page/administration/EditRoleAssignmentPage.java
+++ b/functional-test/src/main/java/org/zanata/page/administration/EditRoleAssignmentPage.java
@@ -63,13 +63,13 @@ public EditRoleAssignmentPage selectRole(String role) {
public RoleAssignmentsPage saveRoleAssignment() {
log.info("Click Save");
- readyElement(saveButton).click();
+ clickElement(saveButton);
return new RoleAssignmentsPage(getDriver());
}
public RoleAssignmentsPage cancelEditRoleAssignment() {
log.info("Click Cancel");
- readyElement(cancelButton).click();
+ clickElement(cancelButton);
return new RoleAssignmentsPage(getDriver());
}
}
diff --git a/functional-test/src/main/java/org/zanata/page/administration/ManageSearchPage.java b/functional-test/src/main/java/org/zanata/page/administration/ManageSearchPage.java
index 5bd4e8659e..b40a96d13a 100644
--- a/functional-test/src/main/java/org/zanata/page/administration/ManageSearchPage.java
+++ b/functional-test/src/main/java/org/zanata/page/administration/ManageSearchPage.java
@@ -71,7 +71,7 @@ public ManageSearchPage selectAllActionsFor(String clazz) {
public ManageSearchPage clickSelectAll() {
log.info("Click Select All");
- readyElement(selectAllButton).click();
+ clickElement(selectAllButton);
// It seems that if the Select All and Perform buttons are clicked too
// quickly in succession, the operation will fail
try {
@@ -101,7 +101,7 @@ public boolean allActionsSelected() {
public ManageSearchPage performSelectedActions() {
log.info("Click Perform Actions");
- readyElement(performButton).click();
+ clickElement(performButton);
waitForAMoment().until(new Predicate() {
@Override
public boolean apply(WebDriver input) {
@@ -121,7 +121,7 @@ public ManageSearchPage expectActionsToFinish() {
public ManageSearchPage abort() {
log.info("Click Abort");
- readyElement(abortButton).click();
+ clickElement(abortButton);
return new ManageSearchPage(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/administration/ManageUserAccountPage.java b/functional-test/src/main/java/org/zanata/page/administration/ManageUserAccountPage.java
index b88bbd8971..b798cecb6f 100644
--- a/functional-test/src/main/java/org/zanata/page/administration/ManageUserAccountPage.java
+++ b/functional-test/src/main/java/org/zanata/page/administration/ManageUserAccountPage.java
@@ -70,14 +70,14 @@ public ManageUserAccountPage enterConfirmPassword(String confirmPassword) {
public ManageUserAccountPage clickEnabled() {
log.info("Click Enabled");
- readyElement(enabledField).click();
+ clickElement(enabledField);
return new ManageUserAccountPage(getDriver());
}
public ManageUserAccountPage clickRole(String role) {
log.info("Click role {}", role);
- readyElement(By.id("userdetailForm:rolesField:roles:"
- .concat(roleMap.get(role)))).click();
+ clickElement(readyElement(By.id("userdetailForm:rolesField:roles:"
+ .concat(roleMap.get(role)))));
return new ManageUserAccountPage(getDriver());
}
@@ -89,19 +89,19 @@ public boolean isRoleChecked(String role) {
public ManageUserPage saveUser() {
log.info("Click Save");
- readyElement(saveButton).click();
+ clickElement(saveButton);
return new ManageUserPage(getDriver());
}
public ManageUserAccountPage saveUserExpectFailure() {
log.info("Click Save");
- readyElement(saveButton).click();
+ clickElement(saveButton);
return new ManageUserAccountPage(getDriver());
}
public ManageUserPage cancelEditUser() {
log.info("Click Cancel");
- readyElement(cancelButton).click();
+ clickElement(cancelButton);
return new ManageUserPage(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/administration/ManageUserPage.java b/functional-test/src/main/java/org/zanata/page/administration/ManageUserPage.java
index 914f6be47e..fb35f950e5 100644
--- a/functional-test/src/main/java/org/zanata/page/administration/ManageUserPage.java
+++ b/functional-test/src/main/java/org/zanata/page/administration/ManageUserPage.java
@@ -44,7 +44,7 @@ public ManageUserPage(WebDriver driver) {
public ManageUserAccountPage editUserAccount(String username) {
log.info("Click edit on {}", username);
- findRowByUserName(username).click();
+ clickElement(findRowByUserName(username));
return new ManageUserAccountPage(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/administration/RoleAssignmentsPage.java b/functional-test/src/main/java/org/zanata/page/administration/RoleAssignmentsPage.java
index 531efe2d2f..1f8802bfcb 100644
--- a/functional-test/src/main/java/org/zanata/page/administration/RoleAssignmentsPage.java
+++ b/functional-test/src/main/java/org/zanata/page/administration/RoleAssignmentsPage.java
@@ -45,13 +45,13 @@ public RoleAssignmentsPage(WebDriver driver) {
public RoleAssignmentsPage clickMoreActions() {
log.info("Click More Actions dropdown");
- readyElement(moreActions).click();
+ clickElement(moreActions);
return new RoleAssignmentsPage(getDriver());
}
public EditRoleAssignmentPage clickCreateNew() {
log.info("Click Create New");
- readyElement(newRuleButton).click();
+ clickElement(newRuleButton);
return new EditRoleAssignmentPage(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/administration/ServerConfigurationPage.java b/functional-test/src/main/java/org/zanata/page/administration/ServerConfigurationPage.java
index 3063ce8fbf..ce9b5805a3 100644
--- a/functional-test/src/main/java/org/zanata/page/administration/ServerConfigurationPage.java
+++ b/functional-test/src/main/java/org/zanata/page/administration/ServerConfigurationPage.java
@@ -58,6 +58,7 @@ public ServerConfigurationPage(WebDriver driver) {
private void enterTextConfigField(By by, String text) {
scrollIntoView(readyElement(by));
+ waitForNotificationsGone();
new Actions(getDriver()).moveToElement(readyElement(by))
.click()
.sendKeys(Keys.chord(Keys.CONTROL, "a"))
@@ -191,7 +192,7 @@ public String getMaxActiveRequestsPerApiKey() {
public AdministrationPage save() {
log.info("Click Save");
- readyElement(saveButton).click();
+ clickElement(saveButton);
return new AdministrationPage(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryEditPage.java b/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryEditPage.java
index c1896e5549..9a7bc650f5 100644
--- a/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryEditPage.java
+++ b/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryEditPage.java
@@ -55,19 +55,19 @@ public TranslationMemoryEditPage enterMemoryDescription(String description) {
public TranslationMemoryPage saveTM() {
log.info("Click Save");
- readyElement(saveButton).click();
+ clickElement(saveButton);
return new TranslationMemoryPage(getDriver());
}
public TranslationMemoryEditPage clickSaveAndExpectFailure() {
log.info("Click Save");
- readyElement(saveButton).click();
+ clickElement(saveButton);
return new TranslationMemoryEditPage(getDriver());
}
public TranslationMemoryPage cancelTM() {
log.info("Click Cancel");
- readyElement(cancelButton).click();
+ clickElement(cancelButton);
return new TranslationMemoryPage(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryPage.java b/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryPage.java
index 0ff6c10ec2..fb3faff46f 100644
--- a/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryPage.java
+++ b/functional-test/src/main/java/org/zanata/page/administration/TranslationMemoryPage.java
@@ -59,66 +59,67 @@ public TranslationMemoryPage(WebDriver driver) {
public TranslationMemoryEditPage clickCreateNew() {
log.info("Click Create New");
- readyElement(dropDownMenu).click();
+ clickElement(dropDownMenu);
clickLinkAfterAnimation(createTmLink);
return new TranslationMemoryEditPage(getDriver());
}
public TranslationMemoryPage clickOptions(String tmName) {
log.info("Click Options dropdown for {}", tmName);
- readyElement(findRowByTMName(tmName), listDropDownMenu).click();
+ clickElement(readyElement(findRowByTMName(tmName), listDropDownMenu));
return new TranslationMemoryPage(getDriver());
}
public TranslationMemoryPage clickImport(String tmName) {
log.info("Click Import");
- readyElement(findRowByTMName(tmName), listImportButton).click();
+ clickElement(readyElement(findRowByTMName(tmName), listImportButton));
return new TranslationMemoryPage(getDriver());
}
public TranslationMemoryPage enterImportFileName(String importFileName) {
log.info("Enter import TM filename {}", importFileName);
readyElement(filenameInput).sendKeys(importFileName);
+ slightPause();
return new TranslationMemoryPage(getDriver());
}
public TranslationMemoryPage clickUploadButtonAndAcknowledge() {
log.info("Click and accept Upload button");
- readyElement(uploadButton).click();
+ clickElement(uploadButton);
switchToAlert().accept();
return new TranslationMemoryPage(getDriver());
}
public Alert expectFailedUpload() {
log.info("Click Upload");
- readyElement(uploadButton).click();
+ clickElement(uploadButton);
return switchToAlert();
}
public TranslationMemoryPage clickClearTMAndAccept(String tmName) {
log.info("Click and accept Clear {}", tmName);
- readyElement(findRowByTMName(tmName), listClearButton).click();
+ clickElement(readyElement(findRowByTMName(tmName), listClearButton));
switchToAlert().accept();
return new TranslationMemoryPage(getDriver());
}
public TranslationMemoryPage clickClearTMAndCancel(String tmName) {
log.info("Click and Cancel Clear {}", tmName);
- readyElement(findRowByTMName(tmName), listClearButton).click();
+ clickElement(readyElement(findRowByTMName(tmName), listClearButton));
switchToAlert().dismiss();
return new TranslationMemoryPage(getDriver());
}
public TranslationMemoryPage clickDeleteTmAndAccept(String tmName) {
log.info("Click and accept Delete {}", tmName);
- readyElement(findRowByTMName(tmName), listDeleteButton).click();
+ clickElement(readyElement(findRowByTMName(tmName), listDeleteButton));
switchToAlert().accept();
return new TranslationMemoryPage(getDriver());
}
public TranslationMemoryPage clickDeleteTmAndCancel(String tmName) {
log.info("Click and cancel Delete {}", tmName);
- readyElement(findRowByTMName(tmName), listDeleteButton).click();
+ clickElement(readyElement(findRowByTMName(tmName), listDeleteButton));
switchToAlert().dismiss();
return new TranslationMemoryPage(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/dashboard/DashboardActivityTab.java b/functional-test/src/main/java/org/zanata/page/dashboard/DashboardActivityTab.java
index cf2567b657..7f203f28f4 100644
--- a/functional-test/src/main/java/org/zanata/page/dashboard/DashboardActivityTab.java
+++ b/functional-test/src/main/java/org/zanata/page/dashboard/DashboardActivityTab.java
@@ -53,7 +53,7 @@ public List getMyActivityList() {
public boolean clickMoreActivity() {
log.info("Click More Activity button");
final int activityListOrigSize = getMyActivityList().size();
- readyElement(moreActivityButton).click();
+ clickElement(moreActivityButton);
return waitForAMoment().until(new Function() {
@Override
public Boolean apply(WebDriver input) {
diff --git a/functional-test/src/main/java/org/zanata/page/dashboard/DashboardProjectsTab.java b/functional-test/src/main/java/org/zanata/page/dashboard/DashboardProjectsTab.java
index a6ebdcedde..828879da56 100644
--- a/functional-test/src/main/java/org/zanata/page/dashboard/DashboardProjectsTab.java
+++ b/functional-test/src/main/java/org/zanata/page/dashboard/DashboardProjectsTab.java
@@ -50,7 +50,7 @@ public List getMaintainedProjectList() {
public CreateProjectPage clickOnCreateProjectLink() {
log.info("Click Create Project");
- readyElement(createProjectLink).click();
+ clickElement(createProjectLink);
return new CreateProjectPage(getDriver());
}
}
diff --git a/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardAccountTab.java b/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardAccountTab.java
index 553e1de928..0ef57be903 100644
--- a/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardAccountTab.java
+++ b/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardAccountTab.java
@@ -61,7 +61,7 @@ public DashboardAccountTab typeNewAccountEmailAddress(String emailAddress) {
public DashboardAccountTab clickUpdateEmailButton() {
log.info("Click Update Email");
- readyElement(updateEmailButton).click();
+ clickElement(updateEmailButton);
return new DashboardAccountTab(getDriver());
}
@@ -81,7 +81,7 @@ public DashboardAccountTab typeNewPassword(String newPassword) {
public DashboardAccountTab clickUpdatePasswordButton() {
log.info("Click Update Password");
- readyElement(changePasswordButton).click();
+ clickElement(changePasswordButton);
return new DashboardAccountTab(getDriver());
}
}
diff --git a/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardClientTab.java b/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardClientTab.java
index ce07b48678..6374651375 100644
--- a/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardClientTab.java
+++ b/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardClientTab.java
@@ -43,7 +43,7 @@ public DashboardClientTab(WebDriver driver) {
public DashboardClientTab pressApiKeyGenerateButton() {
log.info("Press Generate API Key");
- readyElement(generateApiKeyButton).click();
+ clickElement(generateApiKeyButton);
getDriver().switchTo().alert().accept();
return new DashboardClientTab(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardProfileTab.java b/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardProfileTab.java
index ae932ddb0f..8ad5493d60 100644
--- a/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardProfileTab.java
+++ b/functional-test/src/main/java/org/zanata/page/dashboard/dashboardsettings/DashboardProfileTab.java
@@ -55,7 +55,7 @@ public DashboardProfileTab enterName(String name) {
public DashboardProfileTab clickUpdateProfileButton() {
log.info("Click Update");
- readyElement(updateProfileButton).click();
+ clickElement(updateProfileButton);
return new DashboardProfileTab(getDriver());
}
}
diff --git a/functional-test/src/main/java/org/zanata/page/googleaccount/GoogleAccountPage.java b/functional-test/src/main/java/org/zanata/page/googleaccount/GoogleAccountPage.java
index a6dbb5ebfc..92f9297fc6 100644
--- a/functional-test/src/main/java/org/zanata/page/googleaccount/GoogleAccountPage.java
+++ b/functional-test/src/main/java/org/zanata/page/googleaccount/GoogleAccountPage.java
@@ -57,13 +57,13 @@ public GoogleAccountPage enterGooglePassword(String password) {
public GooglePermissionsPage clickSignIn() {
log.info("Click account Sign In");
- readyElement(signInButton).click();
+ clickElement(signInButton);
return new GooglePermissionsPage(getDriver());
}
public GoogleManagePermissionsPage clickPermissionsSignIn() {
log.info("Click account management Sign In");
- readyElement(signInButton).click();
+ clickElement(signInButton);
return new GoogleManagePermissionsPage(getDriver());
}
@@ -79,7 +79,7 @@ public boolean hasRememberedAuthentication() {
public GoogleAccountPage removeSavedAuthentication() {
log.info("Click Sign in with different account");
- readyElement(signInDifferent).click();
+ clickElement(signInDifferent);
return new GoogleAccountPage(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/googleaccount/GooglePermissionsPage.java b/functional-test/src/main/java/org/zanata/page/googleaccount/GooglePermissionsPage.java
index 27be75bc3b..9fe8f52d4c 100644
--- a/functional-test/src/main/java/org/zanata/page/googleaccount/GooglePermissionsPage.java
+++ b/functional-test/src/main/java/org/zanata/page/googleaccount/GooglePermissionsPage.java
@@ -50,7 +50,7 @@ public Boolean apply(WebDriver driver) {
.isEnabled();
}
});
- readyElement(approveAccessButton).click();
+ clickElement(approveAccessButton);
return new EditProfilePage(getDriver());
}
}
diff --git a/functional-test/src/main/java/org/zanata/page/groups/CreateVersionGroupPage.java b/functional-test/src/main/java/org/zanata/page/groups/CreateVersionGroupPage.java
index ee1442401e..d61ee3f75c 100644
--- a/functional-test/src/main/java/org/zanata/page/groups/CreateVersionGroupPage.java
+++ b/functional-test/src/main/java/org/zanata/page/groups/CreateVersionGroupPage.java
@@ -82,7 +82,7 @@ public VersionGroupsPage saveGroup() {
public CreateVersionGroupPage saveGroupFailure() {
log.info("Click Save");
- readyElement(saveButton).click();
+ clickElement(saveButton);
return new CreateVersionGroupPage(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/groups/VersionGroupPage.java b/functional-test/src/main/java/org/zanata/page/groups/VersionGroupPage.java
index d6cbb33e1e..29d28d2161 100644
--- a/functional-test/src/main/java/org/zanata/page/groups/VersionGroupPage.java
+++ b/functional-test/src/main/java/org/zanata/page/groups/VersionGroupPage.java
@@ -106,7 +106,7 @@ public List apply(WebDriver driver) {
public VersionGroupPage addToGroup(int rowIndex) {
WebElementUtil.getListItems(getDriver(), newVersionList)
.get(rowIndex).click();
- readyElement(projectAddButton).click();
+ clickElement(projectAddButton);
return new VersionGroupPage(getDriver());
}
@@ -190,7 +190,7 @@ public VersionGroupPage clickSettingsTab() {
public VersionGroupPage clickLanguagesSettingsTab() {
clickSettingsTab();
- readyElement(settingsLanguagesTab).click();
+ clickElement(settingsLanguagesTab);
return new VersionGroupPage(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/groups/VersionGroupsPage.java b/functional-test/src/main/java/org/zanata/page/groups/VersionGroupsPage.java
index ebfec8ad83..4dc888dd9f 100644
--- a/functional-test/src/main/java/org/zanata/page/groups/VersionGroupsPage.java
+++ b/functional-test/src/main/java/org/zanata/page/groups/VersionGroupsPage.java
@@ -58,13 +58,13 @@ public List getGroupNames() {
public CreateVersionGroupPage createNewGroup() {
log.info("Click New Group button");
- readyElement(createGroupButton).click();
+ clickElement(createGroupButton);
return new CreateVersionGroupPage(getDriver());
}
public VersionGroupPage goToGroup(String groupName) {
log.info("Click group {}", groupName);
- readyElement(groupTable).findElement(By.linkText(groupName)).click();
+ clickElement(readyElement(groupTable).findElement(By.linkText(groupName)));
return new VersionGroupPage(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/languages/ContactTeamPage.java b/functional-test/src/main/java/org/zanata/page/languages/ContactTeamPage.java
index 218ead7fbc..9ef65b4a05 100644
--- a/functional-test/src/main/java/org/zanata/page/languages/ContactTeamPage.java
+++ b/functional-test/src/main/java/org/zanata/page/languages/ContactTeamPage.java
@@ -46,7 +46,7 @@ public ContactTeamPage enterMessage(String message) {
public LanguagesPage clickSend() {
log.info("Click the Send Message button");
- readyElement(sendButton).click();
+ clickElement(sendButton);
return new LanguagesPage(getDriver());
}
}
diff --git a/functional-test/src/main/java/org/zanata/page/languages/LanguagePage.java b/functional-test/src/main/java/org/zanata/page/languages/LanguagePage.java
index d67be9af07..f3774b71d9 100644
--- a/functional-test/src/main/java/org/zanata/page/languages/LanguagePage.java
+++ b/functional-test/src/main/java/org/zanata/page/languages/LanguagePage.java
@@ -69,23 +69,23 @@ public LanguagePage(WebDriver driver) {
public LanguagePage clickMoreActions() {
log.info("Click More Actions");
- readyElement(moreActions).click();
+ clickElement(moreActions);
return new LanguagePage(getDriver());
}
public ContactTeamPage clickContactCoordinatorsButton() {
log.info("Click Contact Coordinators button");
- readyElement(contactCoordinatorsButton).click();
+ clickElement(contactCoordinatorsButton);
return new ContactTeamPage(getDriver());
}
public LanguagePage gotoSettingsTab() {
- readyElement(settingsTab).click();
+ clickElement(settingsTab);
return new LanguagePage(getDriver());
}
public LanguagePage gotoMembersTab() {
- readyElement(membersTab).click();
+ clickElement(membersTab);
return new LanguagePage(getDriver());
}
@@ -100,7 +100,7 @@ public LanguagePage enableLanguageByDefault(boolean enable) {
}
public LanguagePage saveSettings() {
- readyElement(saveButton).click();
+ clickElement(saveButton);
return new LanguagePage(getDriver());
}
@@ -125,7 +125,7 @@ private String getMemberCount() {
public LanguagePage joinLanguageTeam() {
log.info("Click Join");
- readyElement(joinLanguageTeamButton).click();
+ clickElement(joinLanguageTeamButton);
// we need to wait for this join to finish before returning the page
waitForAMoment().until(new Function() {
@Override
@@ -138,7 +138,7 @@ public Boolean apply(WebDriver driver) {
public LanguagePage clickAddTeamMember() {
log.info("Click Add Team Member");
- readyElement(addTeamMemberButton).click();
+ clickElement(addTeamMemberButton);
return this;
}
@@ -163,7 +163,7 @@ private LanguagePage enterUsername(String username) {
private LanguagePage clickSearch() {
log.info("Click Search");
- readyElement(addUserSearchButton).click();
+ clickElement(addUserSearchButton);
return new LanguagePage(getDriver());
}
@@ -233,7 +233,7 @@ private String getListItemUsername(WebElement listItem) {
public LanguagePage clickAddSelectedButton() {
log.info("Click Add Selected");
- readyElement(addSelectedButton).click();
+ clickElement(addSelectedButton);
return new LanguagePage(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/languages/LanguagesPage.java b/functional-test/src/main/java/org/zanata/page/languages/LanguagesPage.java
index a76124d54b..7b05068e2b 100644
--- a/functional-test/src/main/java/org/zanata/page/languages/LanguagesPage.java
+++ b/functional-test/src/main/java/org/zanata/page/languages/LanguagesPage.java
@@ -85,13 +85,13 @@ public List getLanguageLocales() {
public LanguagesPage clickMoreActions() {
log.info("Click More Actions dropdown");
- readyElement(moreActions).click();
+ clickElement(moreActions);
return new LanguagesPage(getDriver());
}
public AddLanguagePage addNewLanguage() {
log.info("Click Add New Language");
- readyElement(addLanguageLink).click();
+ clickElement(addLanguageLink);
return new AddLanguagePage(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/projects/ProjectVersionsPage.java b/functional-test/src/main/java/org/zanata/page/projects/ProjectVersionsPage.java
index 4aaa24c2aa..99a50b3ee7 100644
--- a/functional-test/src/main/java/org/zanata/page/projects/ProjectVersionsPage.java
+++ b/functional-test/src/main/java/org/zanata/page/projects/ProjectVersionsPage.java
@@ -118,7 +118,7 @@ public boolean apply(WebDriver input) {
public ProjectVersionsPage clickSearchIcon() {
log.info("Click Search icon");
- readyElement(existingElement(versions), searchIcon).click();
+ clickElement(readyElement(existingElement(versions), searchIcon));
return new ProjectVersionsPage(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/projects/ProjectsPage.java b/functional-test/src/main/java/org/zanata/page/projects/ProjectsPage.java
index b81eac805b..a2668a5196 100644
--- a/functional-test/src/main/java/org/zanata/page/projects/ProjectsPage.java
+++ b/functional-test/src/main/java/org/zanata/page/projects/ProjectsPage.java
@@ -43,7 +43,6 @@ public class ProjectsPage extends BasePage {
private By projectTable = By.id("main_content:form:projectList");
private By activeCheckBox = By.xpath("//*[@data-original-title='Filter active projects']");
private By readOnlyCheckBox = By.xpath("//*[@data-original-title='Filter read-only projects']");
- private By archivedCheckBox = By.xpath("//*[@data-original-title='Filter archived projects']");
public ProjectsPage(final WebDriver driver) {
super(driver);
@@ -51,7 +50,7 @@ public ProjectsPage(final WebDriver driver) {
public CreateProjectPage clickOnCreateProjectLink() {
log.info("Click Create Project");
- readyElement(createProjectButton).click();
+ clickElement(createProjectButton);
return new CreateProjectPage(getDriver());
}
@@ -126,12 +125,4 @@ public ProjectsPage setReadOnlyFilterEnabled(final boolean enabled) {
return new ProjectsPage(getDriver());
}
- public ProjectsPage setArchivedFilterEnabled(boolean enabled) {
- log.info("Click to set Archived filter enabled to {}", enabled);
- WebElement archivedCheckbox = readyElement(archivedCheckBox);
- if (archivedCheckbox.isSelected() != enabled) {
- archivedCheckbox.click();
- }
- return new ProjectsPage(getDriver());
- }
}
diff --git a/functional-test/src/main/java/org/zanata/page/projects/projectsettings/ProjectGeneralTab.java b/functional-test/src/main/java/org/zanata/page/projects/projectsettings/ProjectGeneralTab.java
index ed9135a2b4..1d788784ce 100644
--- a/functional-test/src/main/java/org/zanata/page/projects/projectsettings/ProjectGeneralTab.java
+++ b/functional-test/src/main/java/org/zanata/page/projects/projectsettings/ProjectGeneralTab.java
@@ -44,8 +44,10 @@ public class ProjectGeneralTab extends ProjectBasePage {
private By projectTypeList = By.id("project-types");
private By homepageField = By.id("settings-general-form:homePageField:homePage");
private By repoField = By.id("settings-general-form:repoField:repo");
- private By archiveButton = By.id("settings-general-form:button-archive-project");
- private By unarchiveButton = By.id("settings-general-form:button-unarchive-project");
+ private By deleteButton = By.id("button-archive-project");
+ private By confirmDeleteButton = By.id("deleteButton");
+ private By confirmDeleteInput = By.id("confirmDeleteInput");
+ private By cancelDeleteButton = By.id("cancelDelete");
private By lockProjectButton = By.id("settings-general-form:button-lock-project");
private By unlockProjectButton = By.id("settings-general-form:button-unlock-project");
private By updateButton = By.id("settings-general-form:button-update-settings");
@@ -157,28 +159,41 @@ private Map getProjectTypes() {
* Only Administrators can use this feature.
* @return button available true/false
*/
- public boolean isArchiveButtonAvailable() {
+ public boolean isDeleteButtonAvailable() {
log.info("Query is Archive button displayed");
- return getDriver().findElements(archiveButton).size() > 0;
+ return getDriver().findElements(deleteButton).size() > 0;
}
/**
- * Press the "Archive this project" button
- * @return new Project General Settings page
+ * Press the "Delete this project" button
+ * @return new Dashboard page
*/
- public ProjectGeneralTab archiveProject() {
- log.info("Click Archive this project");
- clickElement(archiveButton);
+ public ProjectGeneralTab deleteProject() {
+ log.info("Click Delete this project");
+ clickElement(deleteButton);
return new ProjectGeneralTab(getDriver());
}
/**
- * Press the "Unarchive this project" button
- * @return new Project General Settings page
+ * Enter exact project name again to confirm the deletion.
+ * @param projectName project name
+ * @return this page
+ */
+ public ProjectGeneralTab enterProjectNameToConfirmDelete(String projectName) {
+ log.info("Input project name again to confirm");
+ readyElement(confirmDeleteInput).clear();
+ readyElement(confirmDeleteInput).sendKeys(projectName);
+ waitForPageSilence();
+ return this;
+ }
+
+ /**
+ * Confirm project delete
+ * @return new Dashboard page
*/
- public ProjectGeneralTab unarchiveProject() {
- log.info("Click Unarchive this project");
- clickElement(unarchiveButton);
+ public ProjectGeneralTab confirmDeleteProject() {
+ log.info("Click confirm Delete");
+ clickElement(confirmDeleteButton);
return new ProjectGeneralTab(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/projectversion/CreateVersionPage.java b/functional-test/src/main/java/org/zanata/page/projectversion/CreateVersionPage.java
index ce6e2d53aa..bfaa4cae72 100644
--- a/functional-test/src/main/java/org/zanata/page/projectversion/CreateVersionPage.java
+++ b/functional-test/src/main/java/org/zanata/page/projectversion/CreateVersionPage.java
@@ -155,7 +155,7 @@ public VersionLanguagesPage saveVersion() {
public CreateVersionPage saveExpectingError() {
log.info("Click Save");
- readyElement(saveButton).click();
+ clickElement(saveButton);
return new CreateVersionPage(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/projectversion/VersionBasePage.java b/functional-test/src/main/java/org/zanata/page/projectversion/VersionBasePage.java
index 38b9eeb112..aeeac71167 100644
--- a/functional-test/src/main/java/org/zanata/page/projectversion/VersionBasePage.java
+++ b/functional-test/src/main/java/org/zanata/page/projectversion/VersionBasePage.java
@@ -115,7 +115,7 @@ public VersionDocumentsTab gotoSettingsDocumentsTab() {
public VersionTranslationTab gotoSettingsTranslationTab() {
log.info("Click Translation settings sub-tab");
clickWhenTabEnabled(readyElement(settingsTranslationTab));
- readyElement(By.id("settings-translation-review-form"));
+ readyElement(By.id("settings-translation-validation-form"));
return new VersionTranslationTab(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/projectversion/versionsettings/VersionDocumentsTab.java b/functional-test/src/main/java/org/zanata/page/projectversion/versionsettings/VersionDocumentsTab.java
index d8109fc58d..e020d5c3fc 100644
--- a/functional-test/src/main/java/org/zanata/page/projectversion/versionsettings/VersionDocumentsTab.java
+++ b/functional-test/src/main/java/org/zanata/page/projectversion/versionsettings/VersionDocumentsTab.java
@@ -73,7 +73,7 @@ public boolean canSubmitDocument() {
public VersionDocumentsTab cancelUpload() {
log.info("Click Cancel");
- readyElement(cancelUploadButton).click();
+ clickElement(cancelUploadButton);
waitForAMoment().until(new Predicate() {
@Override
public boolean apply(WebDriver input) {
@@ -102,13 +102,13 @@ public VersionDocumentsTab enterFilePath(String filePath) {
public VersionDocumentsTab submitUpload() {
log.info("Click Submit upload");
- readyElement(startUploadButton).click();
+ clickElement(startUploadButton);
return new VersionDocumentsTab(getDriver());
}
public VersionDocumentsTab clickUploadDone() {
log.info("Click upload Done button");
- readyElement(fileUploadDone).click();
+ clickElement(fileUploadDone);
return new VersionDocumentsTab(getDriver());
}
diff --git a/functional-test/src/main/java/org/zanata/page/utility/ContactAdminFormPage.java b/functional-test/src/main/java/org/zanata/page/utility/ContactAdminFormPage.java
index 810e795e23..5ac798ea12 100644
--- a/functional-test/src/main/java/org/zanata/page/utility/ContactAdminFormPage.java
+++ b/functional-test/src/main/java/org/zanata/page/utility/ContactAdminFormPage.java
@@ -62,7 +62,7 @@ public ContactAdminFormPage inputMessage(String message) {
*/
public
+
+ org.codehaus.gmavenplus
+ gmavenplus-plugin
+ 1.1
+
@@ -1501,10 +1521,7 @@
jboss72x
-
- jboss-eap-6.4
-
- ${cargo.extract.dir}/${cargo.basename}/${appserver.dir.name}
+ ${env.EAP6_URL}
@@ -1519,18 +1536,16 @@
wildfly8x
+
8.1.0.Final
- 9.0.0.CR1
+ 9.0.0.CR2wildfly8http://download.jboss.org/wildfly/${wildfly.version}/wildfly-${wildfly.version}.zip
-
- wildfly-${wildfly.version}
-
- ${cargo.extract.dir}/${appserver.dir.name}/${appserver.dir.name}
+
8.1.0.Final2.1.29-01wildfly-${module.wildfly.version}-module-mojarra-${mojarra.module.version}.zip
@@ -1697,6 +1712,21 @@
+
+
+ build frontend module
+
+
+ !excludeFrontend
+
+
+
+ 3.1.0
+
+
+ frontend
+
+
diff --git a/zanata-liquibase/pom.xml b/zanata-liquibase/pom.xml
index 5a23594721..8ce9a804c0 100644
--- a/zanata-liquibase/pom.xml
+++ b/zanata-liquibase/pom.xml
@@ -1,11 +1,9 @@
-
+serverorg.zanata
- 3.7.0-SNAPSHOT
+ 3.8.0-SNAPSHOT4.0.0
diff --git a/zanata-model/pom.xml b/zanata-model/pom.xml
index c98182046f..e97047e817 100644
--- a/zanata-model/pom.xml
+++ b/zanata-model/pom.xml
@@ -4,7 +4,7 @@
org.zanataserver
- 3.7.0-SNAPSHOT
+ 3.8.0-SNAPSHOTzanata-modelZanata model
diff --git a/zanata-model/src/main/java/org/zanata/hibernate/search/CaseInsensitiveWhitespaceAnalyzer.java b/zanata-model/src/main/java/org/zanata/hibernate/search/CaseInsensitiveWhitespaceAnalyzer.java
new file mode 100644
index 0000000000..edd56e3960
--- /dev/null
+++ b/zanata-model/src/main/java/org/zanata/hibernate/search/CaseInsensitiveWhitespaceAnalyzer.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.hibernate.search;
+
+import java.io.Reader;
+
+import org.apache.lucene.analysis.LowerCaseFilter;
+import org.apache.lucene.analysis.ReusableAnalyzerBase;
+import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.analysis.WhitespaceTokenizer;
+import org.apache.lucene.util.Version;
+
+/**
+ * @author Alex Eng aeng@redhat.com
+ */
+public class CaseInsensitiveWhitespaceAnalyzer extends ReusableAnalyzerBase {
+
+ private final Version matchVersion;
+
+ /**
+ * Creates a new {@link CaseInsensitiveWhitespaceAnalyzer}
+ * @param matchVersion Lucene version to match See {@link above}
+ */
+ public CaseInsensitiveWhitespaceAnalyzer(Version matchVersion) {
+ this.matchVersion = matchVersion;
+ }
+
+ @Override
+ protected TokenStreamComponents createComponents(
+ String fieldName, Reader reader) {
+
+ final WhitespaceTokenizer src = new WhitespaceTokenizer(matchVersion, reader);
+ TokenStream tok = new LowerCaseFilter(matchVersion, src);
+
+ return new TokenStreamComponents(src, tok);
+ }
+}
diff --git a/zanata-model/src/main/java/org/zanata/model/HProject.java b/zanata-model/src/main/java/org/zanata/model/HProject.java
index 026af0cea4..7dcfb673de 100644
--- a/zanata-model/src/main/java/org/zanata/model/HProject.java
+++ b/zanata-model/src/main/java/org/zanata/model/HProject.java
@@ -51,6 +51,7 @@
import lombok.Setter;
import lombok.ToString;
+import org.apache.lucene.analysis.WhitespaceAnalyzer;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Cascade;
@@ -58,6 +59,7 @@
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
import org.hibernate.annotations.Where;
+import org.hibernate.search.annotations.Analyzer;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.validator.constraints.NotEmpty;
@@ -66,6 +68,7 @@
import org.zanata.common.EntityStatus;
import org.zanata.common.LocaleId;
import org.zanata.common.ProjectType;
+import org.zanata.hibernate.search.CaseInsensitiveWhitespaceAnalyzer;
import org.zanata.model.type.EntityStatusType;
import org.zanata.model.type.LocaleIdType;
import org.zanata.model.validator.Url;
@@ -102,11 +105,11 @@ public class HProject extends SlugEntityBase implements Serializable,
@Size(max = 80)
@NotEmpty
- @Field()
+ @Field(analyzer = @Analyzer(impl = CaseInsensitiveWhitespaceAnalyzer.class))
private String name;
@Size(max = 100)
- @Field()
+ @Field(analyzer = @Analyzer(impl = CaseInsensitiveWhitespaceAnalyzer.class))
private String description;
@Type(type = "text")
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 3754804b56..ce95bcab76 100644
--- a/zanata-model/src/main/java/org/zanata/model/HTextFlowTarget.java
+++ b/zanata-model/src/main/java/org/zanata/model/HTextFlowTarget.java
@@ -395,9 +395,16 @@ public HTextFlowTargetReviewComment addReviewComment(String comment,
@Override
public String toString() {
- return MoreObjects.toStringHelper(this).add("contents", getContents())
- .add("locale", getLocale()).add("state", getState())
- .add("comment", getComment())
+ MoreObjects.ToStringHelper helper =
+ MoreObjects.toStringHelper(this)
+ .add("contents", getContents())
+ .add("locale", getLocale())
+ .add("state", getState())
+ .add("comment", getComment());
+ if (getTextFlow() == null) {
+ return helper.toString();
+ }
+ return helper
.add("textFlow", getTextFlow().getContents()).toString();
}
diff --git a/zanata-model/src/main/java/org/zanata/model/SlugEntityBase.java b/zanata-model/src/main/java/org/zanata/model/SlugEntityBase.java
index 80e30a47fd..fbfc29ffee 100644
--- a/zanata-model/src/main/java/org/zanata/model/SlugEntityBase.java
+++ b/zanata-model/src/main/java/org/zanata/model/SlugEntityBase.java
@@ -20,9 +20,11 @@
*/
package org.zanata.model;
+import java.util.Date;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.MappedSuperclass;
+import javax.persistence.Transient;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@@ -31,10 +33,12 @@
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
+import lombok.extern.slf4j.Slf4j;
import org.hibernate.annotations.NaturalId;
import org.hibernate.search.annotations.Field;
import org.zanata.model.validator.Slug;
+import com.google.common.annotations.VisibleForTesting;
@MappedSuperclass
@ToString(callSuper = true)
@@ -43,15 +47,54 @@
@Getter
@AllArgsConstructor
@NoArgsConstructor
+@Slf4j
public abstract class SlugEntityBase extends ModelEntityBase {
+ /**
+ * We append this suffix to a deleted slug entity so that its original slug
+ * become available to use.
+ */
+ private static final String DELETED_SLUG_SUFFIX = "_.-";
+
private static final long serialVersionUID = -1911540675412928681L;
- // TODO PERF @NaturalId(mutable=false) for better criteria caching
@NaturalId(mutable = true)
@Size(min = 1, max = 40)
@Slug
@NotNull
@Field
private String slug;
+
+ /**
+ * If the slug entity is set to obsolete (soft delete), we need to recycle
+ * its slug. This will suffix the original slug with deleted slug suffix
+ * plus a timestamp. if original slug is too long, the suffix will be put
+ * into the end and replacing some of the old slug characters. This means we
+ * won't be able to recover what the old slug was.
+ *
+ * @return an artificial slug just so the old slug will become available to
+ * use.
+ */
+ @Transient
+ public String changeToDeletedSlug() {
+ String deletedSlugSuffix = deletedSlugSuffix();
+ String newSlug = slug + deletedSlugSuffix;
+ if (newSlug.length() <= 40) {
+ return newSlug;
+ } else {
+ newSlug =
+ slug.substring(0, 40 - deletedSlugSuffix.length())
+ + deletedSlugSuffix;
+ log.warn(
+ "Entity [{}] old slug [{}] is too long to apply suffix. We will add suffix in place [{}]",
+ this, slug, newSlug);
+ return newSlug;
+ }
+ }
+
+ @VisibleForTesting
+ protected String deletedSlugSuffix() {
+ int timeSuffix = new Date().hashCode();
+ return DELETED_SLUG_SUFFIX + timeSuffix;
+ }
}
diff --git a/zanata-model/src/test/java/org/zanata/model/SlugEntityBaseTest.java b/zanata-model/src/test/java/org/zanata/model/SlugEntityBaseTest.java
index 9da0fa68aa..a2f98da220 100644
--- a/zanata-model/src/test/java/org/zanata/model/SlugEntityBaseTest.java
+++ b/zanata-model/src/test/java/org/zanata/model/SlugEntityBaseTest.java
@@ -21,6 +21,7 @@
package org.zanata.model;
import lombok.NoArgsConstructor;
+import org.assertj.core.api.Assertions;
import org.junit.Test;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -32,11 +33,19 @@
* href="mailto:pahuang@redhat.com">pahuang@redhat.com
*/
public class SlugEntityBaseTest {
+
+ private static final String DELETED_SUFFIX = "_.-1234567890";
+
@NoArgsConstructor
static class SlugClass extends SlugEntityBase {
public SlugClass(String slug) {
super(slug);
}
+
+ @Override
+ protected String deletedSlugSuffix() {
+ return DELETED_SUFFIX;
+ }
}
@Test
@@ -59,4 +68,21 @@ public void lombokToStringAndEqualsTest() {
assertThat(entity.hashCode(), equalTo(other.hashCode()));
}
+
+ @Test
+ public void changeToDeletedSlug() {
+ SlugEntityBase slugEntityBase = new SlugClass("abc");
+ String newSlug = slugEntityBase.changeToDeletedSlug();
+ Assertions.assertThat(newSlug).isEqualTo("abc" + DELETED_SUFFIX);
+ }
+
+ @Test
+ public void canChangeToDeletedSlugWithSuffixInPlaceIfOldSlugIsTooLong() {
+ // 36 characters long
+ SlugEntityBase slugEntityBase = new SlugClass("abcdefghijklmnopqrstuvwxyz1234567890");
+ String newSlug = slugEntityBase.changeToDeletedSlug();
+ Assertions.assertThat(newSlug)
+ .isEqualTo("abcdefghijklmnopqrstuvwxyz1" + DELETED_SUFFIX)
+ .hasSize(40);
+ }
}
diff --git a/zanata-test-war/pom.xml b/zanata-test-war/pom.xml
index e25d6d1349..509af1dc2d 100644
--- a/zanata-test-war/pom.xml
+++ b/zanata-test-war/pom.xml
@@ -4,7 +4,7 @@
org.zanataserver
- 3.7.0-SNAPSHOT
+ 3.8.0-SNAPSHOTzanata-test-warzanata-test-war
diff --git a/zanata-war/pom.xml b/zanata-war/pom.xml
index 84265492d9..c3a8f577da 100644
--- a/zanata-war/pom.xml
+++ b/zanata-war/pom.xml
@@ -4,7 +4,7 @@
org.zanataserver
- 3.7.0-SNAPSHOT
+ 3.8.0-SNAPSHOTzanata-warwar
@@ -50,15 +50,22 @@
src/main/resourcestrue
+
+
+ ${project.build.directory}/generated-resources/deps
+
+ dependencies.properties
+
+ org.codehaus.gmavenplusgmavenplus-plugin
- 1.1
+ defaultaddSourcesaddTestSources
@@ -70,6 +77,19 @@
removeTestStubs
+
+ dependency-versions
+ generate-resources
+
+ execute
+
+
+
+
+
+
+
+
@@ -158,8 +178,8 @@
- com.ning.maven.plugins
- maven-duplicate-finder-plugin
+ org.basepom.maven
+ duplicate-finder-maven-plugin
@@ -167,16 +187,18 @@
gwt-user
-
+
- META-INF/.*
+ META-INF/.*
- com/lowagie/text/pdf/fonts/cmap_info.txt
+ com/lowagie/text/pdf/fonts/cmap_info.txt
- build.properties
+ build.properties
- seam.properties
-
+ seam.properties
+
+ schema/xml.xsd
+
@@ -362,17 +384,22 @@
maven-surefire-plugin-Dconcordion.output.dir=${concordion.output.dir} -XX:MaxPermSize=256m
- none:none
- org.testng:testng
- 1.5C
- false
-
- src/test/resources/AllNonContainerTests.tng.xml
-
+ none:none
+
+ 1
+ true
+
+ **/*Test.groovy
+ **/*Test.java
+ **/*Tests.groovy
+ **/*Tests.java
+ alphabetical
-
org.codehaus.mojo
@@ -992,18 +1019,18 @@
-
+
- org.codehaus.cargo
- cargo-maven2-plugin
+ org.codehaus.gmavenplus
+ gmavenplus-plugin
- cargo-install
+ extract-appserverprepare-package
@@ -1013,6 +1040,7 @@
maven-failsafe-plugin
+ **/*ITCase.groovy**/*ITCase.java1
@@ -1221,6 +1249,13 @@
+
+ com.tngtech.java
+ junit-dataprovider
+ 1.9.3
+ test
+
+
commons-beanutilscommons-beanutils
@@ -1279,6 +1314,11 @@
zanata-assets
+
+ org.zanata
+ frontend
+
+
org.zanatazanata-common-util
@@ -2063,12 +2103,6 @@
mockito-core
-
- org.testng
- testng
-
-
-
org.codehaus.groovygroovy-all
@@ -2142,6 +2176,12 @@
provided
+
+ org.webjars.bower
+ commonmark
+ 0.19.0
+
+
javax.validationvalidation-api
diff --git a/zanata-war/src/etc/dependencyVersions.groovy b/zanata-war/src/etc/dependencyVersions.groovy
new file mode 100644
index 0000000000..e594e51c7f
--- /dev/null
+++ b/zanata-war/src/etc/dependencyVersions.groovy
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+/**
+ * @author Sean Flanigan sflaniga@redhat.com
+ */
+def props = new Properties()
+// NB: project is a MavenProject
+// http://maven.apache.org/ref/3-LATEST/maven-core/apidocs/org/apache/maven/project/MavenProject.html
+project.artifacts.each { a ->
+ def coord = a.groupId + ":" + a.artifactId + ":" + a.type + (a.classifier ? ":" + a.classifier : "")
+ // Make version available to the build:
+ project.properties.put "version." + coord, a.version
+ // This can be expanded to other deps if required:
+ if (a.groupId.startsWith('org.webjars')) {
+ // Make webjar version available at runtime:
+ props.put coord, a.version
+ }
+}
+
+// NB: this has a corresponding resource directory
+// declaration above.
+def genDir = new File(project.build.directory,
+ 'generated-resources/deps')
+genDir.mkdirs()
+new File(genDir, 'dependencies.properties').withWriter { out ->
+ props.store out, 'Zanata dependency versions'
+}
diff --git a/zanata-war/src/main/java/org/zanata/action/ActivityAction.java b/zanata-war/src/main/java/org/zanata/action/ActivityAction.java
index 1d9552f56b..66b430576c 100644
--- a/zanata-war/src/main/java/org/zanata/action/ActivityAction.java
+++ b/zanata-war/src/main/java/org/zanata/action/ActivityAction.java
@@ -21,34 +21,18 @@
package org.zanata.action;
import java.io.Serializable;
-import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
-import org.apache.commons.lang.StringEscapeUtils;
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.annotations.security.Restrict;
import org.jboss.seam.security.management.JpaIdentityStore;
-import org.zanata.common.ActivityType;
-import org.zanata.dao.DocumentDAO;
-import org.zanata.i18n.Messages;
import org.zanata.model.Activity;
import org.zanata.model.HAccount;
-import org.zanata.model.HDocument;
-import org.zanata.model.HProjectIteration;
-import org.zanata.model.HTextFlowTarget;
-import org.zanata.model.type.EntityType;
import org.zanata.service.ActivityService;
-import org.zanata.util.DateUtil;
-import org.zanata.util.ShortString;
-import org.zanata.util.UrlUtil;
-
-import static org.zanata.common.ActivityType.REVIEWED_TRANSLATION;
-import static org.zanata.common.ActivityType.UPDATE_TRANSLATION;
-import static org.zanata.common.ActivityType.UPLOAD_SOURCE_DOCUMENT;
-import static org.zanata.common.ActivityType.UPLOAD_TRANSLATION_DOCUMENT;
/**
* @author Alex Eng aeng@redhat.com
@@ -59,18 +43,9 @@
public class ActivityAction implements Serializable {
private static final long serialVersionUID = 1L;
- @In
- private DocumentDAO documentDAO;
-
- @In
- private UrlUtil urlUtil;
-
@In
private ActivityService activityServiceImpl;
- @In
- private Messages msgs;
-
@In(required = false, value = JpaIdentityStore.AUTHENTICATED_USER)
private HAccount authenticatedAccount;
@@ -80,331 +55,26 @@ public class ActivityAction implements Serializable {
private int activityPageIndex = 0;
public List getActivities() {
- List activities = new ArrayList();
-
if (authenticatedAccount != null) {
int count = (activityPageIndex + 1) * ACTIVITY_COUNT_PER_LOAD;
- activities =
- activityServiceImpl.findLatestActivities(
+ return activityServiceImpl.findLatestActivities(
authenticatedAccount.getPerson().getId(), 0, count);
}
- return activities;
- }
-
- public String getActivityTypeIconClass(Activity activity) {
- return activity.getActivityType() == UPDATE_TRANSLATION ? "i--translate" :
- activity.getActivityType() == REVIEWED_TRANSLATION ? "i--review" :
- activity.getActivityType() == UPLOAD_SOURCE_DOCUMENT ? "i--document" :
- activity.getActivityType() == UPLOAD_TRANSLATION_DOCUMENT ? "i--translate-up" :
- "";
- }
-
- public String getActivityTitle(Activity activity) {
- return activity.getActivityType() == UPDATE_TRANSLATION ?
- msgs.get("jsf.Translation") :
- activity.getActivityType() == REVIEWED_TRANSLATION ?
- msgs.get("jsf.Reviewed") :
- activity.getActivityType() == UPLOAD_SOURCE_DOCUMENT ?
- msgs.get("jsf.UploadedSource") :
- activity.getActivityType() == UPLOAD_TRANSLATION_DOCUMENT ?
- msgs.get("jsf.UploadedTranslations") :
- "";
- }
-
- public String getActivityMessage(Activity activity) {
- switch (activity.getActivityType()) {
- case UPDATE_TRANSLATION:
- return msgs.format("jsf.dashboard.activity.translate.message",
- activity.getWordCount(), getProjectUrl(activity),
- getProjectName(activity), getEditorUrl(activity),
- StringEscapeUtils
- .escapeHtml(getLastTextFlowContent(activity)));
-
- case REVIEWED_TRANSLATION:
- return msgs.format("jsf.dashboard.activity.review.message",
- activity.getWordCount(), getProjectUrl(activity),
- getProjectName(activity), getEditorUrl(activity),
- StringEscapeUtils
- .escapeHtml(getLastTextFlowContent(activity)));
-
- case UPLOAD_SOURCE_DOCUMENT:
- return msgs
- .format("jsf.dashboard.activity.uploadSource.message",
- activity.getWordCount(),
- getProjectUrl(activity),
- getProjectName(activity));
-
- case UPLOAD_TRANSLATION_DOCUMENT:
- return msgs
- .format("jsf.dashboard.activity.uploadTranslation.message",
- activity.getWordCount(),
- getProjectUrl(activity),
- getProjectName(activity));
-
- default:
- return "";
- }
- }
-
- public String getHowLongAgoDescription(Activity activity) {
- return DateUtil.getHowLongAgoDescription(activity.getLastChanged());
- }
-
- public String getProjectName(Activity activity) {
- Object context =
- getEntity(activity.getContextType(), activity.getContextId());
-
- if (isTranslationUpdateActivity(activity.getActivityType())
- || activity.getActivityType() == UPLOAD_SOURCE_DOCUMENT
- || activity.getActivityType() == UPLOAD_TRANSLATION_DOCUMENT) {
- HProjectIteration version = (HProjectIteration) context;
- return version.getProject().getName();
- }
- return "";
- }
-
- public String getProjectUrl(Activity activity) {
- Object context =
- getEntity(activity.getContextType(), activity.getContextId());
-
- if (isTranslationUpdateActivity(activity.getActivityType())
- || activity.getActivityType() == UPLOAD_SOURCE_DOCUMENT
- || activity.getActivityType() == UPLOAD_TRANSLATION_DOCUMENT) {
- HProjectIteration version = (HProjectIteration) context;
- return urlUtil.projectUrl(version.getProject().getSlug());
- }
- return "";
- }
-
- public String getLastTextFlowContent(Activity activity) {
- String content = "";
- Object lastTarget =
- getEntity(activity.getLastTargetType(),
- activity.getLastTargetId());
-
- if (isTranslationUpdateActivity(activity.getActivityType())) {
- HTextFlowTarget tft = (HTextFlowTarget) lastTarget;
- content = tft.getTextFlow().getContents().get(0);
- }
-
- return ShortString.shorten(content);
- }
-
- public String getEditorUrl(Activity activity) {
- String url = "";
- Object context =
- getEntity(activity.getContextType(), activity.getContextId());
- Object lastTarget =
- getEntity(activity.getLastTargetType(),
- activity.getLastTargetId());
-
- if (isTranslationUpdateActivity(activity.getActivityType())) {
- HProjectIteration version = (HProjectIteration) context;
- HTextFlowTarget tft = (HTextFlowTarget) lastTarget;
-
- url =
- urlUtil.editorTransUnitUrl(version.getProject().getSlug(),
- version.getSlug(), tft.getLocaleId(), tft
- .getTextFlow().getLocale(), tft
- .getTextFlow().getDocument().getDocId(),
- tft.getTextFlow().getId());
- } else if (activity.getActivityType() == UPLOAD_SOURCE_DOCUMENT) {
- // not supported for upload source action
- } else if (activity.getActivityType() == UPLOAD_TRANSLATION_DOCUMENT) {
- HProjectIteration version = (HProjectIteration) context;
- HDocument document = (HDocument) lastTarget;
- HTextFlowTarget tft =
- documentDAO.getLastTranslatedTargetOrNull(document.getId());
-
- if (tft != null) {
- url =
- urlUtil.editorTransUnitUrl(version.getProject()
- .getSlug(), version.getSlug(), tft
- .getLocaleId(), document.getSourceLocaleId(),
- tft.getTextFlow().getDocument().getDocId(), tft
- .getTextFlow().getId());
- }
- }
- return url;
- }
-
- public String getDocumentUrl(Activity activity) {
- String url = "";
- Object context =
- getEntity(activity.getContextType(), activity.getContextId());
- Object lastTarget =
- getEntity(activity.getLastTargetType(),
- activity.getLastTargetId());
-
- if (isTranslationUpdateActivity(activity.getActivityType())) {
- HProjectIteration version = (HProjectIteration) context;
- HTextFlowTarget tft = (HTextFlowTarget) lastTarget;
-
- url =
- urlUtil.editorDocumentUrl(version.getProject().getSlug(),
- version.getSlug(), tft.getLocaleId(), tft
- .getTextFlow().getLocale(), tft
- .getTextFlow().getDocument().getDocId());
- } else if (activity.getActivityType() == UPLOAD_SOURCE_DOCUMENT) {
- HProjectIteration version = (HProjectIteration) context;
- url =
- urlUtil.sourceFilesViewUrl(version.getProject().getSlug(),
- version.getSlug());
- } else if (activity.getActivityType() == UPLOAD_TRANSLATION_DOCUMENT) {
- HProjectIteration version = (HProjectIteration) context;
- HDocument document = (HDocument) lastTarget;
- HTextFlowTarget tft =
- documentDAO.getLastTranslatedTargetOrNull(document.getId());
-
- if (tft != null) {
- url =
- urlUtil.editorDocumentUrl(version.getProject()
- .getSlug(), version.getSlug(), tft
- .getLocaleId(), document.getSourceLocaleId(),
- tft.getTextFlow().getDocument().getDocId());
- }
- }
- return url;
- }
-
- public String getDocumentName(Activity activity) {
- Object lastTarget =
- getEntity(activity.getLastTargetType(),
- activity.getLastTargetId());
- String docName = "";
-
- if (isTranslationUpdateActivity(activity.getActivityType())) {
- HTextFlowTarget tft = (HTextFlowTarget) lastTarget;
- docName = tft.getTextFlow().getDocument().getName();
- } else if (activity.getActivityType() == UPLOAD_SOURCE_DOCUMENT
- || activity.getActivityType() == UPLOAD_TRANSLATION_DOCUMENT) {
- HDocument document = (HDocument) lastTarget;
- docName = document.getName();
- }
- return docName;
- }
-
- public String getVersionUrl(Activity activity) {
- Object context =
- getEntity(activity.getContextType(), activity.getContextId());
- String url = "";
-
- if (isTranslationUpdateActivity(activity.getActivityType())
- || activity.getActivityType() == UPLOAD_SOURCE_DOCUMENT
- || activity.getActivityType() == UPLOAD_TRANSLATION_DOCUMENT) {
- HProjectIteration version = (HProjectIteration) context;
- url =
- urlUtil.versionUrl(version.getProject().getSlug(),
- version.getSlug());
- }
-
- return url;
- }
-
- public String getVersionName(Activity activity) {
- Object context =
- getEntity(activity.getContextType(), activity.getContextId());
- String name = "";
-
- if (isTranslationUpdateActivity(activity.getActivityType())
- || activity.getActivityType() == UPLOAD_SOURCE_DOCUMENT
- || activity.getActivityType() == UPLOAD_TRANSLATION_DOCUMENT) {
- HProjectIteration version = (HProjectIteration) context;
- name = version.getSlug();
- }
- return name;
- }
-
- public String getDocumentListUrl(Activity activity) {
- Object context =
- getEntity(activity.getContextType(), activity.getContextId());
- Object lastTarget =
- getEntity(activity.getLastTargetType(),
- activity.getLastTargetId());
- String url = "";
-
- if (isTranslationUpdateActivity(activity.getActivityType())) {
- HProjectIteration version = (HProjectIteration) context;
- HTextFlowTarget tft = (HTextFlowTarget) lastTarget;
-
- url =
- urlUtil.editorDocumentListUrl(version.getProject()
- .getSlug(), version.getSlug(), tft.getLocaleId(),
- tft.getTextFlow().getLocale());
- } else if (activity.getActivityType() == UPLOAD_SOURCE_DOCUMENT) {
- // not supported for upload source action
- } else if (activity.getActivityType() == UPLOAD_TRANSLATION_DOCUMENT) {
- HProjectIteration version = (HProjectIteration) context;
- HDocument document = (HDocument) lastTarget;
- HTextFlowTarget tft =
- documentDAO.getLastTranslatedTargetOrNull(document.getId());
-
- if (tft != null) {
- url =
- urlUtil.editorDocumentListUrl(version.getProject()
- .getSlug(), version.getSlug(), tft
- .getLocaleId(), tft.getTextFlow().getLocale());
- }
- }
- return url;
- }
-
- public String getLanguageName(Activity activity) {
- Object lastTarget =
- getEntity(activity.getLastTargetType(),
- activity.getLastTargetId());
- String name = "";
-
- if (isTranslationUpdateActivity(activity.getActivityType())) {
- HTextFlowTarget tft = (HTextFlowTarget) lastTarget;
- name = tft.getLocaleId().getId();
- } else if (activity.getActivityType() == UPLOAD_SOURCE_DOCUMENT) {
- // not supported for upload source action
- } else if (activity.getActivityType() == UPLOAD_TRANSLATION_DOCUMENT) {
- HDocument document = (HDocument) lastTarget;
- HTextFlowTarget tft =
- documentDAO.getLastTranslatedTargetOrNull(document.getId());
-
- if (tft != null) {
- name = tft.getLocaleId().getId();
- }
- }
-
- return name;
+ return Collections.emptyList();
}
public void loadNextActivity() {
activityPageIndex++;
}
- public String getWordsCountMessage(int wordCount) {
- if (wordCount == 1) {
- return wordCount + " word";
- }
- return wordCount + " words";
- }
-
public boolean hasMoreActivities() {
int loadedActivitiesCount =
(activityPageIndex + 1) * ACTIVITY_COUNT_PER_LOAD;
- int totalActivitiesCount =
- activityServiceImpl
+ int totalActivitiesCount = activityServiceImpl
.getActivityCountByActor(authenticatedAccount
.getPerson().getId());
- if ((loadedActivitiesCount < totalActivitiesCount)
- && (loadedActivitiesCount < MAX_ACTIVITIES_COUNT_PER_PAGE)) {
- return true;
- }
- return false;
- }
-
- private Object getEntity(EntityType contextType, long id) {
- return activityServiceImpl.getEntity(contextType, id);
- }
-
- private boolean isTranslationUpdateActivity(ActivityType activityType) {
- return activityType == UPDATE_TRANSLATION
- || activityType == REVIEWED_TRANSLATION;
+ return ((loadedActivitiesCount < totalActivitiesCount)
+ && (loadedActivitiesCount < MAX_ACTIVITIES_COUNT_PER_PAGE));
}
}
diff --git a/zanata-war/src/main/java/org/zanata/action/LanguageAction.java b/zanata-war/src/main/java/org/zanata/action/LanguageAction.java
index 0ad5f3c66b..1d2f0c8f70 100644
--- a/zanata-war/src/main/java/org/zanata/action/LanguageAction.java
+++ b/zanata-war/src/main/java/org/zanata/action/LanguageAction.java
@@ -24,6 +24,7 @@
import java.util.ArrayList;
import java.util.List;
import javax.faces.event.ValueChangeEvent;
+import javax.persistence.EntityNotFoundException;
import org.apache.commons.lang.StringUtils;
import org.jboss.seam.ScopeType;
@@ -32,6 +33,7 @@
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.annotations.security.Restrict;
+import org.jboss.seam.faces.Redirect;
import org.jboss.seam.security.management.JpaIdentityStore;
import org.zanata.common.LocaleId;
import org.zanata.dao.LocaleDAO;
@@ -102,6 +104,9 @@ public class LanguageAction implements Serializable {
@In
private ResourceUtils resourceUtils;
+ @In
+ private Redirect redirect;
+
@Getter
@Setter
private String language;
@@ -223,6 +228,28 @@ public HLocale getLocale() {
return locale;
}
+ public void validateLanguage() {
+ if(StringUtils.isEmpty(language)) {
+ redirectToLanguageHome();
+ return;
+ }
+
+ boolean isSupported =
+ localeServiceImpl.localeSupported(new LocaleId(language));
+ if(!isSupported) {
+ redirectToLanguageHome();
+ return;
+ }
+ }
+
+ private void redirectToLanguageHome() {
+ facesMessages.addGlobal(msgs.format(
+ "jsf.language.validation.NotSupport", language));
+ redirect.setViewId("/language/home.xhtml");
+ redirect.execute();
+ }
+
+
public List getLocaleMembers() {
return localeMemberDAO.findAllByLocale(new LocaleId(language));
}
diff --git a/zanata-war/src/main/java/org/zanata/action/ProjectAction.java b/zanata-war/src/main/java/org/zanata/action/ProjectAction.java
index 181870b2a7..196b0e6a7b 100644
--- a/zanata-war/src/main/java/org/zanata/action/ProjectAction.java
+++ b/zanata-war/src/main/java/org/zanata/action/ProjectAction.java
@@ -39,7 +39,7 @@ public class ProjectAction implements Serializable {
private boolean showActive = true;
private boolean showReadOnly = true;
- private boolean showObsolete = false;
+ private final boolean showObsolete = false;
private ProjectPagedListDataModel projectPagedListDataModel =
new ProjectPagedListDataModel(!showActive, !showReadOnly,
@@ -59,15 +59,6 @@ public DataModel getProjectPagedListDataModel() {
return projectPagedListDataModel;
}
- public boolean isShowObsolete() {
- return showObsolete;
- }
-
- public void setShowObsolete(boolean showObsolete) {
- projectPagedListDataModel.setFilterObsolete(!showObsolete);
- this.showObsolete = showObsolete;
- }
-
public boolean isShowActive() {
return showActive;
}
diff --git a/zanata-war/src/main/java/org/zanata/action/ProjectHome.java b/zanata-war/src/main/java/org/zanata/action/ProjectHome.java
index 2a88cc0675..ff2d443903 100644
--- a/zanata-war/src/main/java/org/zanata/action/ProjectHome.java
+++ b/zanata-war/src/main/java/org/zanata/action/ProjectHome.java
@@ -51,6 +51,8 @@
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.annotations.security.Restrict;
+import org.jboss.seam.faces.FacesManager;
+import org.jboss.seam.faces.Redirect;
import org.jboss.seam.security.management.JpaIdentityStore;
import org.zanata.common.EntityStatus;
import org.zanata.common.LocaleId;
@@ -67,7 +69,6 @@
import org.zanata.model.HProjectIteration;
import org.zanata.model.WebHook;
import org.zanata.model.validator.SlugValidator;
-import org.zanata.seam.scope.ConversationScopeMessages;
import org.zanata.security.ZanataIdentity;
import org.zanata.service.LocaleService;
import org.zanata.service.SlugEntityService;
@@ -78,6 +79,7 @@
import org.zanata.ui.faces.FacesMessages;
import org.zanata.util.CommonMarkRenderer;
import org.zanata.util.ComparatorUtil;
+import org.zanata.util.ServiceLocator;
import org.zanata.util.UrlUtil;
import org.zanata.webtrans.shared.model.ValidationAction;
import org.zanata.webtrans.shared.model.ValidationId;
@@ -130,9 +132,6 @@ public class ProjectHome extends SlugHome implements
@In("commonMarkRenderer")
private CommonMarkRenderer renderer;
- @In
- private ConversationScopeMessages conversationScopeMessages;
-
@In
private EntityManager entityManager;
@@ -154,6 +153,9 @@ public class ProjectHome extends SlugHome implements
@In
private CopyTransOptionsModel copyTransOptionsModel;
+ @In
+ private UrlUtil urlUtil;
+
// This property is present to keep the filter in place when the region with
// the filter box is refreshed.
@Getter
@@ -559,8 +561,8 @@ public void useDefaultLocales() {
removeAliasesForDisabledLocales();
refreshDisabledLocales();
update();
- conversationScopeMessages.setMessage(FacesMessage.SEVERITY_INFO,
- msgs.get("jsf.project.LanguageUpdateFromGlobal"));
+ facesMessages.addGlobal(FacesMessage.SEVERITY_INFO,
+ msgs.get("jsf.project.LanguageUpdateFromGlobal"));
}
private void removeAliasesForDisabledLocales() {
@@ -626,10 +628,23 @@ protected HProject loadInstance() {
if (projectId == null) {
HProject project = (HProject) session.byNaturalId(HProject.class)
.using("slug", getSlug()).load();
+ validateProjectState(project);
projectId = project.getId();
return project;
} else {
- return (HProject) session.byId(HProject.class).load(projectId);
+ HProject project =
+ (HProject) session.byId(HProject.class).load(projectId);
+ validateProjectState(project);
+ return project;
+ }
+ }
+
+ private void validateProjectState(HProject project) {
+ if (project == null || project.getStatus() == EntityStatus.OBSOLETE) {
+ log.warn(
+ "Project [id={}, slug={}], does not exist or is soft deleted: {}",
+ projectId, slug, project);
+ throw new EntityNotFoundException();
}
}
@@ -639,8 +654,7 @@ public void validateSuppliedId() {
// when id is invalid and conversation will not
// start
- if (ip.getStatus().equals(EntityStatus.OBSOLETE)
- && !checkViewObsolete()) {
+ if (ip.getStatus().equals(EntityStatus.OBSOLETE)) {
throw new EntityNotFoundException();
}
}
@@ -655,9 +669,8 @@ public void updateCopyTrans(String action, String value) {
copyTransOptionsModel.getInstance());
update();
-
- conversationScopeMessages.setMessage(FacesMessage.SEVERITY_INFO,
- msgs.get("jsf.project.CopyTransOpts.updated"));
+ facesMessages.addGlobal(FacesMessage.SEVERITY_INFO,
+ msgs.get("jsf.project.CopyTransOpts.updated"));
}
public void initialize() {
@@ -717,7 +730,20 @@ public String update() {
return null;
}
getInstance().setSlug(getInputSlugValue());
+
+ boolean softDeleted = false;
+ if (getInstance().getStatus() == EntityStatus.OBSOLETE) {
+ softDeleted = true;
+ getInstance().setSlug(getInstance().changeToDeletedSlug());
+ }
+
String result = super.update();
+
+ if (softDeleted) {
+ String url = urlUtil.dashboardUrl();
+ FacesManager.instance().redirectToExternalURL(url);
+ return result;
+ }
if (!slug.equals(getInstance().getSlug())) {
slug = getInstance().getSlug();
return "projectSlugUpdated";
@@ -779,24 +805,26 @@ public List getInstanceMaintainers() {
return list;
}
+ @In
+ private Redirect redirect;
+
@Restrict("#{s:hasPermission(projectHome.instance, 'update')}")
public String removeMaintainer(HPerson person) {
if (getInstanceMaintainers().size() <= 1) {
- conversationScopeMessages
- .setMessage(FacesMessage.SEVERITY_INFO,
- msgs.get("jsf.project.NeedAtLeastOneMaintainer"));
+ facesMessages.addGlobal(FacesMessage.SEVERITY_INFO,
+ msgs.get("jsf.project.NeedAtLeastOneMaintainer"));
} else {
getInstance().getMaintainers().remove(person);
maintainerFilter.reset();
update();
- conversationScopeMessages.setMessage(FacesMessage.SEVERITY_INFO,
- msgs.format("jsf.project.MaintainerRemoved",
- person.getName()));
-
- // force page to do url redirect to project page. See pages.xml
+ facesMessages.addGlobal(FacesMessage.SEVERITY_INFO,
+ msgs.format("jsf.project.MaintainerRemoved",
+ person.getName()));
if (person.equals(authenticatedAccount.getPerson())) {
- return "redirect";
+ redirect.setViewId("/project/project.xhtml");
+ redirect.setParameter("slug", getSlug());
+ redirect.execute();
}
}
return "";
@@ -817,8 +845,8 @@ public void updateRoles(String roleName, boolean isRestricted) {
}
}
update();
- conversationScopeMessages.setMessage(FacesMessage.SEVERITY_INFO,
- msgs.get("jsf.RolesUpdated"));
+ facesMessages.addGlobal(FacesMessage.SEVERITY_INFO,
+ msgs.get("jsf.RolesUpdated"));
}
@Restrict("#{s:hasPermission(projectHome.instance, 'update')}")
@@ -842,10 +870,13 @@ public void updateStatus(char initial) {
}
}
update();
+ facesMessages.addGlobal(FacesMessage.SEVERITY_INFO,
+ msgs.format("jsf.project.status.updated",
+ EntityStatus.valueOf(initial)));
+ }
- conversationScopeMessages.setMessage(FacesMessage.SEVERITY_INFO,
- msgs.format("jsf.project.status.updated",
- EntityStatus.valueOf(initial)));
+ public void deleteSelf() {
+ updateStatus('O');
}
public Map getRoleRestrictions() {
@@ -876,10 +907,7 @@ private List fetchVersions() {
List results = new ArrayList();
for (HProjectIteration iteration : getInstance().getProjectIterations()) {
- if (iteration.getStatus() == EntityStatus.OBSOLETE
- && checkViewObsolete()) {
- results.add(iteration);
- } else if (iteration.getStatus() != EntityStatus.OBSOLETE) {
+ if (iteration.getStatus() != EntityStatus.OBSOLETE) {
results.add(iteration);
}
}
@@ -960,10 +988,9 @@ public void updateValidationOption(String name, String state) {
}
}
update();
-
- conversationScopeMessages.setMessage(FacesMessage.SEVERITY_INFO,
- msgs.format("jsf.validation.updated",
- validationId.getDisplayName(), state));
+ facesMessages.addGlobal(FacesMessage.SEVERITY_INFO,
+ msgs.format("jsf.validation.updated",
+ validationId.getDisplayName(), state));
}
public List getValidationList() {
@@ -1053,19 +1080,6 @@ protected void updatedMessage() {
// Disable the default message from Seam
}
- /**
- * This is for autocomplete components of which ConversationScopeMessages
- * will be null
- *
- * @param conversationScopeMessages
- */
- private String update(ConversationScopeMessages conversationScopeMessages) {
- if (this.conversationScopeMessages == null) {
- this.conversationScopeMessages = conversationScopeMessages;
- }
- return update();
- }
-
private boolean checkViewObsolete() {
return identity != null
&& identity.hasPermission("HProject", "view-obsolete");
@@ -1085,17 +1099,20 @@ protected List getMaintainers() {
public void onSelectItemAction() {
if (StringUtils.isEmpty(getSelectedItem())) {
return;
- }
-
+ }
HPerson maintainer = personDAO.findByUsername(getSelectedItem());
getInstance().addMaintainer(maintainer);
- update(conversationScopeMessages);
+ update();
reset();
- conversationScopeMessages.setMessage(FacesMessage.SEVERITY_INFO,
- msgs.format("jsf.project.MaintainerAdded",
- maintainer.getName()));
+ getFacesMessages().addGlobal(FacesMessage.SEVERITY_INFO,
+ msgs.format("jsf.project.MaintainerAdded",
+ maintainer.getName()));
+ }
+
+ private FacesMessages getFacesMessages() {
+ return ServiceLocator.instance().getInstance(FacesMessages.class);
}
}
diff --git a/zanata-war/src/main/java/org/zanata/action/ProjectHomeAction.java b/zanata-war/src/main/java/org/zanata/action/ProjectHomeAction.java
index 87e5189b71..48b65facb4 100644
--- a/zanata-war/src/main/java/org/zanata/action/ProjectHomeAction.java
+++ b/zanata-war/src/main/java/org/zanata/action/ProjectHomeAction.java
@@ -394,12 +394,8 @@ public List getProjectVersions() {
ProjectDAO projectDAO =
ServiceLocator.instance().getInstance(ProjectDAO.class);
if (projectVersions == null) {
- if (isUserAllowViewObsolete()) {
- projectVersions = projectDAO.getAllIterations(slug);
- } else {
- projectVersions = projectDAO.getActiveIterations(slug);
- projectVersions.addAll(projectDAO.getReadOnlyIterations(slug));
- }
+ projectVersions = projectDAO.getActiveIterations(slug);
+ projectVersions.addAll(projectDAO.getReadOnlyIterations(slug));
Collections.sort(projectVersions,
ComparatorUtil.VERSION_CREATION_DATE_COMPARATOR);
@@ -407,11 +403,6 @@ public List getProjectVersions() {
return projectVersions;
}
- public boolean isUserAllowViewObsolete() {
- return identity != null
- && identity.hasPermission("HProject", "view-obsolete");
- }
-
public boolean isUserAllowedToTranslateOrReview(HProjectIteration version,
HLocale localeId) {
return version != null
diff --git a/zanata-war/src/main/java/org/zanata/action/ProjectSearch.java b/zanata-war/src/main/java/org/zanata/action/ProjectSearch.java
index 346ffc6497..df988fc416 100644
--- a/zanata-war/src/main/java/org/zanata/action/ProjectSearch.java
+++ b/zanata-war/src/main/java/org/zanata/action/ProjectSearch.java
@@ -39,21 +39,19 @@ public class ProjectSearch implements Serializable {
private final static int DEFAULT_PAGE_SIZE = 30;
- @In
- private ZanataIdentity identity;
-
@Getter
private ProjectAutocomplete projectAutocomplete = new ProjectAutocomplete();
private QueryProjectPagedListDataModel queryProjectPagedListDataModel =
new QueryProjectPagedListDataModel(DEFAULT_PAGE_SIZE);
- // Count of result to be return as part of autocomplete
- private final static int INITIAL_RESULT_COUNT = 5;
+ // Count of project to be return as part of autocomplete
+ private final static int INITIAL_RESULT_COUNT = 10;
+
+ // Count of person to be return as part of autocomplete
+ private final static int INITIAL_PERSON_RESULT_COUNT = 20;
public DataModel getProjectPagedListDataModel() {
- queryProjectPagedListDataModel.setIncludeObsolete(identity
- .hasPermission("HProject", "view-obsolete"));
return queryProjectPagedListDataModel;
}
@@ -94,18 +92,17 @@ public List suggest() {
}
try {
String searchQuery = getQuery().trim();
+ boolean includeObsolete = false;
List searchResult =
- projectDAO.searchProjects(
- searchQuery,
- INITIAL_RESULT_COUNT,
- 0,
- ZanataIdentity.instance().hasPermission(
- "HProject", "view-obsolete"));
+ projectDAO.searchProjects(searchQuery,
+ INITIAL_RESULT_COUNT, 0, includeObsolete);
for (HProject project : searchResult) {
result.add(new SearchResult(project, null));
}
- List hAccounts = accountDAO.searchQuery(searchQuery);
+ List hAccounts =
+ accountDAO.searchQuery(searchQuery,
+ INITIAL_PERSON_RESULT_COUNT, 0);
for (HAccount hAccount : hAccounts) {
result.add(new SearchResult(null, hAccount));
}
diff --git a/zanata-war/src/main/java/org/zanata/action/QueryProjectPagedListDataModel.java b/zanata-war/src/main/java/org/zanata/action/QueryProjectPagedListDataModel.java
index 5e9e1ecd8c..2f9b386f32 100644
--- a/zanata-war/src/main/java/org/zanata/action/QueryProjectPagedListDataModel.java
+++ b/zanata-war/src/main/java/org/zanata/action/QueryProjectPagedListDataModel.java
@@ -39,8 +39,7 @@ public class QueryProjectPagedListDataModel extends
PagedListDataModel implements Serializable {
private static final long serialVersionUID = 1L;
- @Setter
- private boolean includeObsolete;
+ private final boolean includeObsolete = false;
@Setter
@Getter
@@ -63,7 +62,7 @@ public DataPage fetchPage(int startRow, int pageSize) {
int projectSize =
projectDAO.getQueryProjectSize(query, includeObsolete);
- return new DataPage(projectSize, startRow, proj);
+ return new DataPage<>(projectSize, startRow, proj);
} catch (ParseException e) {
return null;
diff --git a/zanata-war/src/main/java/org/zanata/action/RegisterAction.java b/zanata-war/src/main/java/org/zanata/action/RegisterAction.java
index 812da7d1fb..fd6d647355 100644
--- a/zanata-war/src/main/java/org/zanata/action/RegisterAction.java
+++ b/zanata-war/src/main/java/org/zanata/action/RegisterAction.java
@@ -91,7 +91,7 @@ public void setUsername(String username) {
@NotEmpty
@Size(min = 3, max = 20)
- @Pattern(regexp = "^[a-z\\d_]{3,20}$",
+ @Pattern(regexp = "^([a-z\\d][a-z\\d_]*){3,20}$",
message = "{validation.username.constraints}")
public String getUsername() {
return username;
diff --git a/zanata-war/src/main/java/org/zanata/action/VersionHome.java b/zanata-war/src/main/java/org/zanata/action/VersionHome.java
index 0be7816ad9..1d437400b0 100644
--- a/zanata-war/src/main/java/org/zanata/action/VersionHome.java
+++ b/zanata-war/src/main/java/org/zanata/action/VersionHome.java
@@ -42,7 +42,7 @@
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.security.Restrict;
-import org.jboss.seam.international.StatusMessage;
+import org.jboss.seam.faces.FacesManager;
import org.zanata.common.DocumentType;
import org.zanata.common.EntityStatus;
import org.zanata.common.LocaleId;
@@ -50,7 +50,6 @@
import org.zanata.dao.ProjectDAO;
import org.zanata.dao.ProjectIterationDAO;
import org.zanata.dao.LocaleDAO;
-import org.zanata.events.ProjectIterationUpdate;
import org.zanata.i18n.Messages;
import org.zanata.model.HLocale;
import org.zanata.model.HProject;
@@ -61,19 +60,16 @@
import org.zanata.service.SlugEntityService;
import org.zanata.service.ValidationService;
import org.zanata.service.impl.LocaleServiceImpl;
-import org.zanata.transformer.Transformer;
import org.zanata.ui.faces.FacesMessages;
import org.zanata.util.ComparatorUtil;
-import org.zanata.util.Event;
+import org.zanata.util.UrlUtil;
import org.zanata.webtrans.shared.model.ValidationAction;
import org.zanata.webtrans.shared.model.ValidationId;
import org.zanata.webtrans.shared.validation.ValidationFactory;
-import javax.annotation.Nullable;
import javax.faces.application.FacesMessage;
import javax.faces.event.ValueChangeEvent;
import javax.persistence.EntityNotFoundException;
-import javax.swing.text.Document;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
@@ -138,8 +134,8 @@ public class VersionHome extends SlugHome implements
@In
private CopyVersionManager copyVersionManager;
- @In("event")
- private Event projectIterationUpdateEvent;
+ @In
+ private UrlUtil urlUtil;
private Map availableValidations = Maps
.newHashMap();
@@ -247,11 +243,25 @@ protected HProjectIteration loadInstance() {
.byNaturalId(HProjectIteration.class)
.using("slug", getSlug())
.using("project", projectDAO.getBySlug(projectSlug)).load();
+ validateIterationState(iteration);
versionId = iteration.getId();
return iteration;
} else {
- return (HProjectIteration) session.load(HProjectIteration.class,
- versionId);
+ HProjectIteration iteration =
+ (HProjectIteration) session.load(HProjectIteration.class,
+ versionId);
+ validateIterationState(iteration);
+ return iteration;
+ }
+ }
+
+ private void validateIterationState(HProjectIteration iteration) {
+ if (iteration == null
+ || iteration.getStatus() == EntityStatus.OBSOLETE) {
+ log.warn(
+ "Project version [id={}, slug={}], does not exist or is soft deleted: {}",
+ versionId, slug, iteration);
+ throw new EntityNotFoundException();
}
}
@@ -345,7 +355,7 @@ public boolean isSlugAvailable(String slug) {
}
public String createVersion() {
- if (!validateSlug(getInstance().getSlug(), "slug"))
+ if (!validateSlug(inputSlugValue, "slug"))
return "invalid-slug";
if (copyFromVersion) {
@@ -358,6 +368,7 @@ public String createVersion() {
public void copyVersion() {
getInstance().setStatus(EntityStatus.READONLY);
+ getInstance().setSlug(inputSlugValue);
// create basic version here
HProject project = getProject();
@@ -457,11 +468,29 @@ public String update() {
return null;
}
getInstance().setSlug(getInputSlugValue());
+
+ boolean softDeleted = false;
+ if (getInstance().getStatus() == EntityStatus.OBSOLETE) {
+ // if we offer delete in REST, we need to move this to hibernate listener
+ String newSlug = getInstance().changeToDeletedSlug();
+ getInstance().setSlug(newSlug);
+
+ softDeleted = true;
+ }
+
String state = super.update();
+
+ if (softDeleted) {
+ String url = urlUtil.projectUrl(projectSlug);
+ FacesManager.instance().redirectToExternalURL(url);
+ return state;
+ }
+
if (!slug.equals(getInstance().getSlug())) {
slug = getInstance().getSlug();
return "versionSlugUpdated";
}
+
return state;
}
@@ -472,11 +501,18 @@ protected void updatedMessage() {
@Restrict("#{s:hasPermission(versionHome.instance, 'update')}")
public void updateStatus(char initial) {
+ String message = msgs.format("jsf.iteration.status.updated",
+ EntityStatus.valueOf(initial));
getInstance().setStatus(EntityStatus.valueOf(initial));
+ if (getInstance().getStatus() == EntityStatus.OBSOLETE) {
+ message = msgs.get("jsf.iteration.deleted");
+ }
update();
- conversationScopeMessages.setMessage(FacesMessage.SEVERITY_INFO,
- msgs.format("jsf.iteration.status.updated",
- EntityStatus.valueOf(initial)));
+ facesMessages.addGlobal(FacesMessage.SEVERITY_INFO, message);
+ }
+
+ public void deleteSelf() {
+ updateStatus('O');
}
public void updateSelectedProjectType(ValueChangeEvent e) {
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 446fa99859..28e28765b9 100644
--- a/zanata-war/src/main/java/org/zanata/action/VersionHomeAction.java
+++ b/zanata-war/src/main/java/org/zanata/action/VersionHomeAction.java
@@ -88,6 +88,8 @@
import org.zanata.util.StatisticsUtil;
import org.zanata.util.UrlUtil;
import org.zanata.webtrans.shared.model.DocumentStatus;
+import org.zanata.webtrans.shared.util.TokenUtil;
+
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
@@ -176,6 +178,9 @@ public class VersionHomeAction extends AbstractSortAction implements
@In("filePersistService")
private FilePersistService filePersistService;
+ @In
+ private UrlUtil urlUtil;
+
private List supportedLocale;
private List documents;
@@ -931,14 +936,6 @@ private void uploadAdapterFile(DocumentType docType) {
translationFileServiceImpl.removeTempFile(tempFile);
}
- public String encodeDocId(String docId) {
- return UrlUtil.encodeString(docId);
- }
-
- 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() {
@@ -962,6 +959,17 @@ public void setDefaultTranslationDocType(String fileName) {
translationFileUpload.setDocumentType(null);
}
+ public String getEditorUrl(String sourceLocale, String docId) {
+ return urlUtil
+ .editorDocumentUrl(projectSlug, versionSlug,
+ selectedLocale.getLocaleId(), new LocaleId(
+ sourceLocale), TokenUtil.encode(docId));
+ }
+
+ public String encodeDocId(String docId) {
+ return urlUtil.encodeString(docId);
+ }
+
public void uploadTranslationFile(HLocale hLocale) {
identity.checkPermission("modify-translation", hLocale, getVersion()
.getProject());
diff --git a/zanata-war/src/main/java/org/zanata/dao/AccountDAO.java b/zanata-war/src/main/java/org/zanata/dao/AccountDAO.java
index ec712128b6..2d06503670 100644
--- a/zanata-war/src/main/java/org/zanata/dao/AccountDAO.java
+++ b/zanata-war/src/main/java/org/zanata/dao/AccountDAO.java
@@ -132,12 +132,14 @@ public HAccount create(String username, String password, boolean enabled) {
@SuppressWarnings("unchecked")
// TODO: use hibernate search
public
- List searchQuery(String searchQuery) {
+ List searchQuery(String searchQuery, int maxResults, int firstResult) {
String userName = "%" + searchQuery + "%";
Query query =
getSession().createQuery(
"from HAccount as a where lower(a.username) like lower(:username)");
query.setParameter("username", userName);
+ query.setMaxResults(maxResults);
+ query.setFirstResult(firstResult);
query.setComment("AccountDAO.searchQuery/username");
return query.list();
}
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 c207be1284..d8e8030eec 100644
--- a/zanata-war/src/main/java/org/zanata/dao/ProjectDAO.java
+++ b/zanata-war/src/main/java/org/zanata/dao/ProjectDAO.java
@@ -7,6 +7,8 @@
import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
+import org.apache.lucene.analysis.StopAnalyzer;
+import org.apache.lucene.analysis.WhitespaceAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.ParseException;
@@ -14,6 +16,7 @@
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.util.Version;
import org.hibernate.Query;
import org.hibernate.Session;
@@ -25,14 +28,13 @@
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.zanata.common.EntityStatus;
+import org.zanata.hibernate.search.CaseInsensitiveWhitespaceAnalyzer;
import org.zanata.hibernate.search.IndexFieldLabels;
import org.zanata.model.HAccount;
import org.zanata.model.HPerson;
import org.zanata.model.HProject;
import org.zanata.model.HProjectIteration;
-import com.google.common.collect.Lists;
-
@Name("projectDAO")
@AutoCreate
@Scope(ScopeType.STATELESS)
@@ -255,51 +257,24 @@ public int getTotalObsoleteProjectCount() {
public List searchProjects(@Nonnull String searchQuery,
int maxResult, int firstResult, boolean includeObsolete)
throws ParseException {
- FullTextQuery query = getTextQuery(searchQuery, includeObsolete);
+ FullTextQuery query = buildSearchQuery(searchQuery, includeObsolete);
return query.setMaxResults(maxResult).setFirstResult(firstResult)
.getResultList();
}
public int getQueryProjectSize(@Nonnull String searchQuery,
boolean includeObsolete) throws ParseException {
- FullTextQuery query = getTextQuery(searchQuery, includeObsolete);
+ FullTextQuery query = buildSearchQuery(searchQuery, includeObsolete);
return query.getResultSize();
}
- private org.apache.lucene.search.Query constructQuery(String field, String searchQuery)
- throws ParseException {
- QueryParser parser =
- new QueryParser(Version.LUCENE_29, field,
- new StandardAnalyzer(Version.LUCENE_29));
- return parser.parse(searchQuery);
- }
-
- /**
- * Lucene index for project name and slug replaces hyphen with
- * space. This method is to replace hyphen with space when performing search
- *
- * @param query
- * @return
- */
- private String parseSlugAndName(String query) {
- return query.replace("-", " ");
- }
-
- private FullTextQuery getTextQuery(@Nonnull String searchQuery,
- boolean includeObsolete) throws ParseException {
- org.apache.lucene.search.Query nameQuery =
- constructQuery("name", parseSlugAndName(searchQuery) + "*");
- org.apache.lucene.search.Query slugQuery =
- constructQuery("slug", parseSlugAndName(searchQuery) + "*");
-
- searchQuery = QueryParser.escape(searchQuery);
- org.apache.lucene.search.Query descQuery =
- constructQuery("description", searchQuery);
+ private FullTextQuery buildSearchQuery(@Nonnull String searchQuery,
+ boolean includeObsolete) throws ParseException {
BooleanQuery booleanQuery = new BooleanQuery();
- booleanQuery.add(slugQuery, BooleanClause.Occur.SHOULD);
- booleanQuery.add(nameQuery, BooleanClause.Occur.SHOULD);
- booleanQuery.add(descQuery, BooleanClause.Occur.SHOULD);
+ booleanQuery.add(buildSearchFieldQuery(searchQuery, "slug"), BooleanClause.Occur.SHOULD);
+ booleanQuery.add(buildSearchFieldQuery(searchQuery, "name"), BooleanClause.Occur.SHOULD);
+ booleanQuery.add(buildSearchFieldQuery(searchQuery, "description"), BooleanClause.Occur.SHOULD);
if (!includeObsolete) {
TermQuery obsoleteStateQuery =
@@ -311,6 +286,32 @@ private FullTextQuery getTextQuery(@Nonnull String searchQuery,
return entityManager.createFullTextQuery(booleanQuery, HProject.class);
}
+ /**
+ * Build BooleanQuery on single lucene field by splitting searchQuery with
+ * white space.
+ *
+ * @param searchQuery
+ * - query string, will replace hypen with space and escape
+ * special char
+ * @param field
+ * - lucene field
+ */
+ private BooleanQuery buildSearchFieldQuery(@Nonnull String searchQuery,
+ @Nonnull String field) throws ParseException {
+ BooleanQuery query = new BooleanQuery();
+
+ //escape special character search
+ searchQuery = QueryParser.escape(searchQuery);
+
+ for(String searchString: searchQuery.split("\\s+")) {
+ QueryParser parser = new QueryParser(Version.LUCENE_29, field,
+ new CaseInsensitiveWhitespaceAnalyzer(Version.LUCENE_29));
+
+ query.add(parser.parse(searchString + "*"), BooleanClause.Occur.MUST);
+ }
+ return query;
+ }
+
public List findAllTranslatedProjects(HAccount account, int maxResults) {
Query q =
getSession()
diff --git a/zanata-war/src/main/java/org/zanata/rest/editor/MediaTypes.java b/zanata-war/src/main/java/org/zanata/rest/editor/MediaTypes.java
index 4af23cc0b9..f62638fa00 100644
--- a/zanata-war/src/main/java/org/zanata/rest/editor/MediaTypes.java
+++ b/zanata-war/src/main/java/org/zanata/rest/editor/MediaTypes.java
@@ -32,6 +32,9 @@ public String toString() {
public static final String APPLICATION_ZANATA_LOCALES_JSON =
APPLICATION_ZANATA_LOCALES + JSON;
+ public static final String APPLICATION_ZANATA_SUGGESTIONS_JSON =
+ APPLICATION_VND_ZANATA + ".suggestions" + JSON;
+
public static final String APPLICATION_ZANATA_PROJECT_VERSION =
APPLICATION_VND_ZANATA + ".version";
public static final String APPLICATION_ZANATA_PROJECT_VERSION_JSON =
diff --git a/zanata-war/src/main/java/org/zanata/rest/editor/dto/suggestion/JsonDateSerializer.java b/zanata-war/src/main/java/org/zanata/rest/editor/dto/suggestion/JsonDateSerializer.java
new file mode 100644
index 0000000000..ed014397ce
--- /dev/null
+++ b/zanata-war/src/main/java/org/zanata/rest/editor/dto/suggestion/JsonDateSerializer.java
@@ -0,0 +1,49 @@
+/*
+ * 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.rest.editor.dto.suggestion;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+
+import java.io.IOException;
+import java.util.Date;
+
+/**
+ * Serializer to output dates in ISO-8601 format.
+ *
+ * This format is used for the JSON API because it is compatible with
+ * JavaScript Date.parse() and is a widely used standard.
+ */
+public class JsonDateSerializer extends JsonSerializer {
+
+ private static final DateTimeFormatter ISO8601Format = ISODateTimeFormat.dateTime();
+
+ @Override
+ public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider provider) throws IOException {
+ String dateString = ISO8601Format.print(new DateTime(date));
+ jsonGenerator.writeString(dateString);
+ }
+
+}
diff --git a/zanata-war/src/main/java/org/zanata/rest/editor/dto/suggestion/Suggestion.java b/zanata-war/src/main/java/org/zanata/rest/editor/dto/suggestion/Suggestion.java
new file mode 100644
index 0000000000..751514d457
--- /dev/null
+++ b/zanata-war/src/main/java/org/zanata/rest/editor/dto/suggestion/Suggestion.java
@@ -0,0 +1,59 @@
+/*
+ * 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.rest.editor.dto.suggestion;
+
+import lombok.Getter;
+import org.codehaus.jackson.annotate.JsonPropertyOrder;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a single suggested translation.
+ *
+ * This could be from translation memory or other sources.
+ *
+ * This representation is designed for use with the pure JavaScript editor.
+ */
+@Getter
+@JsonPropertyOrder({ "relevanceScore", "similarityPercent", "sourceContents", "targetContents", "matchDetails" })
+@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+public class Suggestion implements Serializable {
+
+ private final double relevanceScore;
+ private final double similarityPercent;
+
+ private final List sourceContents;
+ private final List targetContents;
+
+ private final List matchDetails;
+
+ public Suggestion(double relevanceScore, double similarityPercent,
+ List sourceContents, List targetContents) {
+ this.relevanceScore = relevanceScore;
+ this.similarityPercent = similarityPercent;
+ this.sourceContents = sourceContents;
+ this.targetContents = targetContents;
+ this.matchDetails = new ArrayList<>();
+ }
+}
diff --git a/zanata-war/src/main/java/org/zanata/rest/editor/dto/suggestion/SuggestionDetail.java b/zanata-war/src/main/java/org/zanata/rest/editor/dto/suggestion/SuggestionDetail.java
new file mode 100644
index 0000000000..b4a3711f06
--- /dev/null
+++ b/zanata-war/src/main/java/org/zanata/rest/editor/dto/suggestion/SuggestionDetail.java
@@ -0,0 +1,53 @@
+/*
+ * 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.rest.editor.dto.suggestion;
+
+import java.io.Serializable;
+
+/**
+ * Detailed information about a suggestion of a specific type.
+ */
+public interface SuggestionDetail extends Serializable {
+
+ /**
+ * Possible types of suggestions from different resources.
+ *
+ * Different types may present different information, so use
+ * different class representations.
+ */
+ enum SuggestionType {
+
+ /**
+ * A suggestion from a project on this Zanata server.
+ */
+ LOCAL_PROJECT,
+
+ /**
+ * A suggestion from an imported translation memory.
+ */
+ IMPORTED_TM
+ }
+
+ /**
+ * @return the type of suggestion.
+ */
+ SuggestionType getType();
+}
diff --git a/zanata-war/src/main/java/org/zanata/rest/editor/dto/suggestion/TextFlowSuggestionDetail.java b/zanata-war/src/main/java/org/zanata/rest/editor/dto/suggestion/TextFlowSuggestionDetail.java
new file mode 100644
index 0000000000..58799d6e31
--- /dev/null
+++ b/zanata-war/src/main/java/org/zanata/rest/editor/dto/suggestion/TextFlowSuggestionDetail.java
@@ -0,0 +1,91 @@
+/*
+ * 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.rest.editor.dto.suggestion;
+
+import lombok.Getter;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.zanata.common.ContentState;
+import org.zanata.model.*;
+
+import java.util.Date;
+
+/**
+ * Detailed information about a suggestion from a project on this server.
+ */
+@Getter
+@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+public class TextFlowSuggestionDetail implements SuggestionDetail {
+
+ private final SuggestionType type = SuggestionType.LOCAL_PROJECT;
+
+ private final Long textFlowId;
+
+ private final String sourceComment;
+ private final String targetComment;
+
+ private final ContentState contentState;
+
+ private final String projectId;
+ private final String projectName;
+ private final String version;
+ private final String documentName;
+ private final String documentPath;
+
+ private final String resId;
+
+ // TODO use @JsonFormat for date format when jackson is updated to 2+
+ @JsonSerialize(using = JsonDateSerializer.class)
+ private final Date lastModifiedDate;
+ private final String lastModifiedBy;
+
+ /**
+ * Create a detail object based on a given text flow target.
+ *
+ * @param tft for which to create a detail object.
+ */
+ public TextFlowSuggestionDetail(HTextFlowTarget tft) {
+ HTextFlow tf = tft.getTextFlow();
+ final HDocument document = tf.getDocument();
+ final HProjectIteration version = document.getProjectIteration();
+ final HProject project = version.getProject();
+ final HPerson lastModifiedPerson = tft.getLastModifiedBy();
+ final boolean haveLastModifiedUsername = lastModifiedPerson != null && lastModifiedPerson.hasAccount();
+
+ this.textFlowId = tf.getId();
+
+ this.sourceComment = HSimpleComment.toString(tf.getComment());
+ this.targetComment = HSimpleComment.toString(tft.getComment());
+
+ this.contentState = tft.getState();
+
+ this.projectId = project.getSlug();
+ this.projectName = project.getName();
+ this.version = version.getSlug();
+ this.documentName = document.getName();
+ this.documentPath = document.getPath();
+ this.resId = tf.getResId();
+
+ this.lastModifiedDate = tft.getLastChanged();
+ this.lastModifiedBy = haveLastModifiedUsername ?
+ lastModifiedPerson.getAccount().getUsername() : null;
+ }
+
+}
diff --git a/zanata-war/src/main/java/org/zanata/rest/editor/dto/suggestion/TransMemoryUnitSuggestionDetail.java b/zanata-war/src/main/java/org/zanata/rest/editor/dto/suggestion/TransMemoryUnitSuggestionDetail.java
new file mode 100644
index 0000000000..404e3292e8
--- /dev/null
+++ b/zanata-war/src/main/java/org/zanata/rest/editor/dto/suggestion/TransMemoryUnitSuggestionDetail.java
@@ -0,0 +1,60 @@
+/*
+ * 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.rest.editor.dto.suggestion;
+
+import lombok.Getter;
+import org.codehaus.jackson.map.annotate.JsonSerialize;
+import org.zanata.model.tm.TransMemoryUnit;
+
+import java.util.Date;
+
+/**
+ * Detailed information about a suggestion from an imported translation memory.
+ */
+@Getter
+@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
+public class TransMemoryUnitSuggestionDetail implements SuggestionDetail {
+
+ private final SuggestionType type = SuggestionType.IMPORTED_TM;
+
+ /**
+ * The database id that can be used to look up the TransMemoryUnit.
+ */
+ private final Long transMemoryUnitId;
+
+ private final String transMemorySlug;
+ private final String transUnitId;
+
+ @JsonSerialize(using = JsonDateSerializer.class)
+ private final Date lastChanged;
+
+ /**
+ * Create a detail object based on a given trans memory unit.
+ *
+ * @param tmUnit for which to create a detail object
+ */
+ public TransMemoryUnitSuggestionDetail(TransMemoryUnit tmUnit) {
+ this.transMemoryUnitId = tmUnit.getId();
+ this.transMemorySlug = tmUnit.getTranslationMemory().getSlug();
+ this.transUnitId = tmUnit.getTransUnitId();
+ this.lastChanged = tmUnit.getLastChanged();
+ }
+}
diff --git a/zanata-war/src/main/java/org/zanata/rest/editor/service/SuggestionsService.java b/zanata-war/src/main/java/org/zanata/rest/editor/service/SuggestionsService.java
new file mode 100644
index 0000000000..badf6c1da3
--- /dev/null
+++ b/zanata-war/src/main/java/org/zanata/rest/editor/service/SuggestionsService.java
@@ -0,0 +1,136 @@
+/*
+ * 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.rest.editor.service;
+
+import com.google.common.base.Joiner;
+import com.googlecode.totallylazy.Either;
+import com.googlecode.totallylazy.Option;
+import org.jboss.seam.annotations.In;
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Transactional;
+import org.zanata.common.LocaleId;
+import org.zanata.model.HLocale;
+import org.zanata.rest.editor.dto.suggestion.Suggestion;
+import org.zanata.rest.editor.service.resource.SuggestionsResource;
+import org.zanata.service.LocaleService;
+import org.zanata.service.TranslationMemoryService;
+import org.zanata.webtrans.shared.model.TransMemoryQuery;
+import org.zanata.webtrans.shared.rpc.HasSearchType;
+
+import javax.annotation.Nullable;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.GenericEntity;
+import javax.ws.rs.core.Response;
+import java.util.List;
+
+import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
+import static org.zanata.webtrans.shared.rpc.HasSearchType.*;
+
+/**
+ * @see org.zanata.rest.editor.service.resource.SuggestionsResource
+ */
+@Name("editor.suggestionsService")
+@Path(SuggestionsResource.SERVICE_PATH)
+@Transactional
+public class SuggestionsService implements SuggestionsResource {
+
+ public static final String SEARCH_TYPES = Joiner.on(", ").join(SearchType.values());
+
+ @In("translationMemoryServiceImpl")
+ private TranslationMemoryService transMemoryService;
+
+ @In("localeServiceImpl")
+ private LocaleService localeService;
+
+ @Override
+ public Response query(List query, String sourceLocaleString, String transLocaleString, String searchTypeString) {
+
+ Option searchType = getSearchType(searchTypeString);
+ if (searchType.isEmpty()) {
+ return unknownSearchTypeResponse(searchTypeString);
+ }
+
+ Option sourceLocale = getLocale(sourceLocaleString);
+ if (sourceLocale.isEmpty()) {
+ return Response.status(BAD_REQUEST)
+ .entity(String.format("Unrecognized source locale: \"%s\"", sourceLocaleString))
+ .build();
+ }
+
+ Option transLocale = getLocale(transLocaleString);
+ if (transLocale.isEmpty()) {
+ return Response.status(BAD_REQUEST)
+ .entity(String.format("Unrecognized translation locale: \"%s\"", transLocaleString))
+ .build();
+ }
+
+ List suggestions = transMemoryService.searchTransMemoryWithDetails(transLocale.get(),
+ sourceLocale.get(), new TransMemoryQuery(query, searchType.get()));
+
+ // Wrap in generic entity to prevent type erasure, so that an
+ // appropriate MessageBodyReader can be used.
+ // see docs for GenericEntity
+ GenericEntity> entity = new GenericEntity>(suggestions) {};
+
+ return Response.ok(entity).build();
+ }
+
+ /**
+ * Try to get a valid locale for a given string.
+ *
+ * @param localeString used to look up the locale
+ * @return a wrapped LocaleId if the given string matches one, otherwise an empty option.
+ */
+ private Option getLocale(String localeString) {
+ @Nullable HLocale hLocale = localeService.getByLocaleId(localeString);
+ if (hLocale == null) {
+ return Option.none();
+ }
+ return Option.option(hLocale.getLocaleId());
+ }
+
+ /**
+ * Try to get a valid search type constant for a given string.
+ *
+ * @param searchTypeString used to look up the search type. Case insensitive.
+ * @return A wrapped SearchType if the given string matches one, otherwise an empty option.
+ */
+ private Option getSearchType(String searchTypeString) {
+ for (SearchType type : SearchType.values()) {
+ if (type.name().equalsIgnoreCase(searchTypeString)) {
+ return Option.option(type);
+ }
+ }
+ return Option.none();
+ }
+
+ /**
+ * Generate and build an error response that reports the search type being unrecognized.
+ *
+ * @param searchTypeString shown in the error message as the unrecognized string
+ * @return a built Response.
+ */
+ private Response unknownSearchTypeResponse(String searchTypeString) {
+ String error = String.format("Unrecognized search type: \"%s\". Expected one of: %s",
+ searchTypeString, SEARCH_TYPES);
+ return Response.status(BAD_REQUEST).entity(error).build();
+ }
+}
diff --git a/zanata-war/src/main/java/org/zanata/rest/editor/service/resource/SuggestionsResource.java b/zanata-war/src/main/java/org/zanata/rest/editor/service/resource/SuggestionsResource.java
new file mode 100644
index 0000000000..8c7d02306e
--- /dev/null
+++ b/zanata-war/src/main/java/org/zanata/rest/editor/service/resource/SuggestionsResource.java
@@ -0,0 +1,70 @@
+/*
+ * 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.rest.editor.service.resource;
+
+import org.zanata.rest.editor.MediaTypes;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.List;
+
+/**
+ * Endpoint to search for suggestions from translation memory and other sources.
+ */
+@Produces({ MediaType.APPLICATION_JSON })
+@Consumes({ MediaType.APPLICATION_JSON })
+public interface SuggestionsResource {
+
+ public static final String SERVICE_PATH = "/suggestions";
+
+ /**
+ * Retrieves a list of suggestions for a a query in the body of the request.
+ *
+ * POST is used to allow the potentially long query strings to be sent in the
+ * body rather than the query string.
+ *
+ * @param query a JSON array of query strings in the body of the request,
+ * used to look up similar or identical strings based on
+ * sourceLocale, that have been translated to transLocale.
+ * @param sourceLocale locale id in the form lang[-country[-modifier]]
+ * @param transLocale locale id in the form lang[-country[-modifier]]
+ * @param searchType the search type to use, determines how similar source
+ * strings must be to be considered a match. Valid
+ * values are "EXACT", "FUZZY", "RAW", "FUZZY_PLURAL"
+ * and "CONTENT_HASH" as defined in
+ * {@link org.zanata.webtrans.shared.rpc.HasSearchType.SearchType}.
+ * @return The following response status codes will be returned from this
+ * operation:
+ * OK (200) - Response containing a list of suggestions.
+ * BAD REQUEST (400) - If searchType is not a valid search type, or if
+ * sourceLocale or transLocale are malformed or not available
+ * on the server.
+ * INTERNAL SERVER ERROR (500) - If there is an unexpected error in
+ * the server while performing this operation.
+ */
+ @POST
+ @Produces({ MediaTypes.APPLICATION_ZANATA_SUGGESTIONS_JSON, MediaType.APPLICATION_JSON })
+ public Response query(List query,
+ @QueryParam("from") String sourceLocale,
+ @QueryParam("to") String transLocale,
+ @QueryParam("searchType") @DefaultValue("FUZZY_PLURAL") String searchType);
+}
diff --git a/zanata-war/src/main/java/org/zanata/seam/text/SeamTextToCommonMarkParser.java b/zanata-war/src/main/java/org/zanata/seam/text/SeamTextToCommonMarkParser.java
index 7acf505866..8eadb35c88 100644
--- a/zanata-war/src/main/java/org/zanata/seam/text/SeamTextToCommonMarkParser.java
+++ b/zanata-war/src/main/java/org/zanata/seam/text/SeamTextToCommonMarkParser.java
@@ -142,6 +142,7 @@ protected String monospaceCloseTag() {
return "`";
}
+ // NB for strict accuracy, we should probably un-escape HTML entities in the Seam Text while inside a monospace tag
@Override
protected String monospaceOpenTag() {
return "`";
@@ -191,7 +192,7 @@ protected String paragraphOpenTag() {
@Override
protected String preformattedText(String text) {
- return "\n```\n" + text + "\n```\n";
+ return "\n
\n" + text + "\n
\n";
}
@Override
diff --git a/zanata-war/src/main/java/org/zanata/search/LevenshteinTokenUtil.java b/zanata-war/src/main/java/org/zanata/search/LevenshteinTokenUtil.java
index de2278f33d..c0dbc693c6 100644
--- a/zanata-war/src/main/java/org/zanata/search/LevenshteinTokenUtil.java
+++ b/zanata-war/src/main/java/org/zanata/search/LevenshteinTokenUtil.java
@@ -122,6 +122,7 @@ public static double getSimilarity(final String s1, final String s2) {
int levDistance = getLevenshteinDistanceInWords(s1s, s2s);
int maxDistance = Math.max(s1s.length, s2s.length);
+ // FIXME maxDistance can be 0, leading to divide-by-zero
double similarity = (maxDistance - levDistance) / (double) maxDistance;
return similarity;
}
@@ -129,8 +130,8 @@ public static double getSimilarity(final String s1, final String s2) {
/**
* Splits into tokens (lower-case).
*
- * @param s
- * @return
+ * @param s the string to tokenise
+ * @return an array of lowercase tokens (words)
*/
static String[] tokenise(String s) {
String[] tokens = s.toLowerCase().split(SPLIT_REGEX);
@@ -159,56 +160,86 @@ private static int countExtraStringLengths(List strings,
* strings. Returns the mean similarity of s1 against each string in the
* list.
*
- * @param s1
- * @param strings2
- * @return
+ * @param s1 string to compare against each other string
+ * @param strings2 other strings to compare s1 against
+ * @return mean similarity between s1 and each of strings2
*/
public static double getSimilarity(final String s1,
final List strings2) {
double totalSimilarity = 0.0;
- int stringCount = strings2.size();
- for (int i = 0; i < stringCount; i++) {
- String s2 = strings2.get(i);
+ for (String s2 : strings2) {
totalSimilarity += getSimilarity(s1, s2);
}
- double meanSimilarity = totalSimilarity / stringCount;
- return meanSimilarity;
+ return totalSimilarity / strings2.size();
}
+ /**
+ * Calculate the word-based case-insensitive similarity of two lists of
+ * strings (range 0.0 to 1.0).
+ *
+ * - Strings at the same index are compared.
+ * - Stop-words are ignored in comparisons. See #stopwords.
+ * - When both lists are empty, they are considered identical (returns 1.0)
+ * - Empty strings are considered identical to other empty strings.
+ *
+ * If a string is made up only of stop-words, the similarity will always be
+ * 0.0 regardless of the actual similarity of the stop-words.
+ *
+ * TODO review use of stop-words in these comparisons, since results can
+ * often be confusing to end-users.
+ *
+ * @param strings1 a list of strings to compare
+ * @param strings2 the other list of strings to compare
+ * @return average similarity between the strings, between 0.0 and 1.0
+ */
public static double getSimilarity(final List strings1,
final List strings2) {
- // length of the shorter list
- int minListSize;
-
- // count the extra strings first:
- int extraStringLengths; // total of "extra" strings in the longer list
- if (strings1.size() < strings2.size()) {
- minListSize = strings1.size();
- extraStringLengths = countExtraStringLengths(strings2, minListSize);
- } else {
- minListSize = strings2.size();
- extraStringLengths = countExtraStringLengths(strings1, minListSize);
+ // all empty lists are identical
+ if (strings1.isEmpty() && strings2.isEmpty()) {
+ return 1.0;
}
- // total of Levenshtein distance between corresponding strings in the
- // two lists, plus the length of any extra strings if one list is longer
- int totalLevDistance = extraStringLengths;
- // total of max editing distance between all the corresponding strings,
- // plus length of extra strings
- int totalMaxDistance = extraStringLengths;
+ // length of the shorter list
+ final int minListSize = Math.min(strings1.size(), strings2.size());
+ final List longestList = strings1.size() > minListSize ?
+ strings1 : strings2;
- // now count the strings which correspond between both lists
+ // total of "extra" strings in the longer list
+ final int extraStringLengths =
+ countExtraStringLengths(longestList, minListSize);
+
+ // running total of Levenshtein distance between corresponding strings
+ // in the two lists
+ int cumulativeLevDistance = 0;
+
+ // running total of max editing distance between all the corresponding
+ // strings.
+ int cumulativeMaxDistance = 0;
+
+ // count the strings which correspond between both lists
for (int i = 0; i < minListSize; i++) {
- String[] s1 = tokenise(strings1.get(i));
- String[] s2 = tokenise(strings2.get(i));
- int levenshteinDistance = getLevenshteinDistanceInWords(s1, s2);
- totalLevDistance += levenshteinDistance;
- totalMaxDistance += Math.max(s1.length, s2.length);
+ final String string1 = strings1.get(i);
+ final String string2 = strings2.get(i);
+ String[] tokens1 = tokenise(string1);
+ String[] tokens2 = tokenise(string2);
+ final int levenshteinDistance =
+ getLevenshteinDistanceInWords(tokens1, tokens2);
+ cumulativeLevDistance += levenshteinDistance;
+
+ // When a string contains only stop words, tokenise returns an empty
+ // array, so this value can remain at 0.
+ cumulativeMaxDistance += Math.max(tokens1.length, tokens2.length);
}
- double similarity =
- (totalMaxDistance - totalLevDistance)
- / (double) totalMaxDistance;
- return similarity;
+ final int totalLevDistance = cumulativeLevDistance + extraStringLengths;
+ final int totalMaxDistance = cumulativeMaxDistance + extraStringLengths;
+
+ // if there would be a divide-by-zero situation due to all strings being
+ // only stop-words, return 0 instead.
+ if (totalMaxDistance == 0) {
+ return 0.0;
+ }
+
+ return (totalMaxDistance - totalLevDistance) / (double) totalMaxDistance;
}
}
diff --git a/zanata-war/src/main/java/org/zanata/security/SecurityFunctions.java b/zanata-war/src/main/java/org/zanata/security/SecurityFunctions.java
index 1ed7181b42..853af01ee2 100644
--- a/zanata-war/src/main/java/org/zanata/security/SecurityFunctions.java
+++ b/zanata-war/src/main/java/org/zanata/security/SecurityFunctions.java
@@ -304,17 +304,17 @@ public static boolean canViewObsoleteProjectIteration(
* Mark Project and Project Iteration obsolete rules
**************************************************************************/
- // Only admin can archive projects
+ // Project maintainer can archive/delete projects
@GrantsPermission(actions = "mark-obsolete")
public static boolean canArchiveProject(HProject project) {
- return getIdentity().hasRole("admin");
+ return isProjectMaintainer(project);
}
- // Only admin can archive project iterations
+ // Project maintainer can archive/delete project iterations
@GrantsPermission(actions = "mark-obsolete")
public static boolean canArchiveProjectIteration(
HProjectIteration projectIteration) {
- return getIdentity().hasRole("admin");
+ return isProjectMaintainer(projectIteration.getProject());
}
/***************************************************************************
diff --git a/zanata-war/src/main/java/org/zanata/service/LocaleService.java b/zanata-war/src/main/java/org/zanata/service/LocaleService.java
index 2b58016a97..39cc2c3799 100644
--- a/zanata-war/src/main/java/org/zanata/service/LocaleService.java
+++ b/zanata-war/src/main/java/org/zanata/service/LocaleService.java
@@ -25,6 +25,7 @@
import java.util.Set;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import org.zanata.common.LocaleId;
import org.zanata.exception.ZanataServiceException;
@@ -52,6 +53,7 @@ public interface LocaleService {
HLocale getByLocaleId(@Nonnull LocaleId locale);
+ @Nullable
HLocale getByLocaleId(@Nonnull String localeId);
@Nonnull
diff --git a/zanata-war/src/main/java/org/zanata/service/TranslationMemoryService.java b/zanata-war/src/main/java/org/zanata/service/TranslationMemoryService.java
index 145b9e85b9..ac0da01ef4 100644
--- a/zanata-war/src/main/java/org/zanata/service/TranslationMemoryService.java
+++ b/zanata-war/src/main/java/org/zanata/service/TranslationMemoryService.java
@@ -1,25 +1,23 @@
/*
+ * 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.
*
- * * 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.service;
import java.util.List;
@@ -28,6 +26,7 @@
import org.zanata.model.HLocale;
import org.zanata.model.HTextFlow;
import org.zanata.model.HTextFlowTarget;
+import org.zanata.rest.editor.dto.suggestion.Suggestion;
import org.zanata.webtrans.shared.model.TransMemoryDetails;
import org.zanata.webtrans.shared.model.TransMemoryQuery;
import org.zanata.webtrans.shared.model.TransMemoryResultItem;
@@ -54,4 +53,14 @@ Optional searchBestMatchTransMemory(
List searchTransMemory(LocaleId targetLocaleId,
LocaleId sourceLocaleId, TransMemoryQuery transMemoryQuery);
+
+ /**
+ * Run the given query to generate suggestions.
+ *
+ * @param transMemoryQuery the query type and text to search.
+ * @return a list of suggested translations for the query.
+ */
+ List searchTransMemoryWithDetails(
+ LocaleId targetLocaleId, LocaleId sourceLocaleId,
+ TransMemoryQuery transMemoryQuery);
}
diff --git a/zanata-war/src/main/java/org/zanata/service/impl/LocaleServiceImpl.java b/zanata-war/src/main/java/org/zanata/service/impl/LocaleServiceImpl.java
index e4a6137da8..baa3e82748 100644
--- a/zanata-war/src/main/java/org/zanata/service/impl/LocaleServiceImpl.java
+++ b/zanata-war/src/main/java/org/zanata/service/impl/LocaleServiceImpl.java
@@ -29,6 +29,7 @@
import java.util.Set;
import java.util.TreeMap;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.In;
@@ -263,8 +264,14 @@ public HLocale getByLocaleId(@Nonnull LocaleId locale) {
}
@Override
+ @Nullable
public HLocale getByLocaleId(@Nonnull String localeId) {
- return this.getByLocaleId(new LocaleId(localeId));
+ try {
+ return this.getByLocaleId(new LocaleId(localeId));
+ } catch (IllegalArgumentException e) {
+ log.warn("Tried to look up a locale with a malformed id", e);
+ return null;
+ }
}
@Override
diff --git a/zanata-war/src/main/java/org/zanata/service/impl/TranslationMemoryServiceImpl.java b/zanata-war/src/main/java/org/zanata/service/impl/TranslationMemoryServiceImpl.java
index fc15707632..550de23f04 100644
--- a/zanata-war/src/main/java/org/zanata/service/impl/TranslationMemoryServiceImpl.java
+++ b/zanata-war/src/main/java/org/zanata/service/impl/TranslationMemoryServiceImpl.java
@@ -21,14 +21,9 @@
package org.zanata.service.impl;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
+import lombok.Getter;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.Term;
@@ -60,6 +55,10 @@
import org.zanata.model.HTextFlow;
import org.zanata.model.HTextFlowTarget;
import org.zanata.model.tm.TransMemoryUnit;
+import org.zanata.rest.editor.dto.suggestion.Suggestion;
+import org.zanata.rest.editor.dto.suggestion.SuggestionDetail;
+import org.zanata.rest.editor.dto.suggestion.TextFlowSuggestionDetail;
+import org.zanata.rest.editor.dto.suggestion.TransMemoryUnitSuggestionDetail;
import org.zanata.search.LevenshteinTokenUtil;
import org.zanata.search.LevenshteinUtil;
import org.zanata.service.TranslationMemoryService;
@@ -76,8 +75,6 @@
import static com.google.common.collect.Collections2.filter;
import lombok.extern.slf4j.Slf4j;
-import javax.annotation.Nullable;
-
/**
* @author Alex Eng aeng@redhat.com
*/
@@ -219,6 +216,14 @@ public List searchTransMemory(
return results;
}
+ @Override
+ public List searchTransMemoryWithDetails(
+ LocaleId targetLocaleId, LocaleId sourceLocaleId,
+ TransMemoryQuery transMemoryQuery) {
+ return new QueryMatchProcessor(transMemoryQuery, sourceLocaleId, targetLocaleId)
+ .process();
+ }
+
private TransMemoryQuery buildTMQuery(HTextFlow textFlow,
HasSearchType.SearchType searchType, boolean checkContext,
boolean checkDocument, boolean checkProject,
@@ -305,9 +310,9 @@ private void processIndexMatch(TransMemoryQuery transMemoryQuery,
Lists.newArrayList(textFlowTarget.getContents());
TransMemoryResultItem.MatchType matchType =
fromContentState(textFlowTarget.getState());
- addOrIncrementResultItem(transMemoryQuery, matchesMap, match,
- matchType, textFlowContents, targetContents, textFlowTarget
- .getTextFlow().getId(), "");
+ TransMemoryResultItem item = createOrGetResultItem(transMemoryQuery, matchesMap, match, matchType,
+ textFlowContents, targetContents);
+ addTextFlowTargetToResultMatches(textFlowTarget, item);
} else if (entity instanceof TransMemoryUnit) {
TransMemoryUnit transUnit = (TransMemoryUnit) entity;
ArrayList sourceContents =
@@ -316,10 +321,9 @@ private void processIndexMatch(TransMemoryQuery transMemoryQuery,
ArrayList targetContents =
Lists.newArrayList(transUnit.getTransUnitVariants()
.get(targetLocaleId.getId()).getPlainTextSegment());
- addOrIncrementResultItem(transMemoryQuery, matchesMap, match,
- TransMemoryResultItem.MatchType.Imported, sourceContents,
- targetContents, transUnit.getId(), transUnit
- .getTranslationMemory().getSlug());
+ TransMemoryResultItem item = createOrGetResultItem(transMemoryQuery, matchesMap, match,
+ TransMemoryResultItem.MatchType.Imported, sourceContents, targetContents);
+ addTransMemoryUnitToResultMatches(item, transUnit);
}
}
@@ -370,11 +374,16 @@ private static TransMemoryResultItem.MatchType fromContentState(
}
}
- private void addOrIncrementResultItem(TransMemoryQuery transMemoryQuery,
- Map matchesMap, Object[] match,
- TransMemoryResultItem.MatchType matchType,
- ArrayList sourceContents, ArrayList targetContents,
- Long sourceId, String origin) {
+ /**
+ * Look up the result item for the given source and target contents.
+ *
+ * If no item is found, a new one is added to the map and returned.
+ *
+ * @return the item for the given source and target contents, which may be newly created.
+ */
+ private TransMemoryResultItem createOrGetResultItem(TransMemoryQuery transMemoryQuery, Map matchesMap, Object[] match, TransMemoryResultItem.MatchType matchType,
+ ArrayList sourceContents, ArrayList targetContents) {
TMKey key = new TMKey(sourceContents, targetContents);
TransMemoryResultItem item = matchesMap.get(key);
if (item == null) {
@@ -387,9 +396,29 @@ private void addOrIncrementResultItem(TransMemoryQuery transMemoryQuery,
matchType, score, percent);
matchesMap.put(key, item);
}
+ return item;
+ }
+
+ private void addTransMemoryUnitToResultMatches(TransMemoryResultItem item, TransMemoryUnit transMemoryUnit) {
+ item.incMatchCount();
+ item.addOrigin(transMemoryUnit.getTranslationMemory().getSlug());
+ }
+
+ private void addTextFlowTargetToResultMatches(HTextFlowTarget textFlowTarget, TransMemoryResultItem item) {
item.incMatchCount();
- item.addOrigin(origin);
- item.addSourceId(sourceId);
+
+ // TODO change sourceId to include type, then include the id of imported matches
+ item.addSourceId(textFlowTarget.getTextFlow().getId());
+
+ // Workaround: since Imported does not have a details view in the current editor,
+ // I am treating it as the lowest priority, so will be overwritten by
+ // other match types.
+ // A better fix is to have the DTO hold all the match types so the editor
+ // can show them in whatever way is most sensible.
+ ContentState state = textFlowTarget.getState();
+ if (state == ContentState.Approved || item.getMatchType() == TransMemoryResultItem.MatchType.Imported) {
+ item.setMatchType(fromContentState(state));
+ }
}
/**
@@ -782,4 +811,205 @@ public boolean apply(Object[] input) {
return true;
}
}
+
+ /**
+ * Responsible for running a query and collating the results.
+ *
+ * I am using a class to avoid having to pass several arguments through
+ * all the helper methods, since that makes the code very hard to read.
+ */
+ private class QueryMatchProcessor {
+ public static final boolean SORT_BY_DATE = false;
+
+ private final TransMemoryQuery query;
+ private final LocaleId srcLocale;
+ private final LocaleId transLocale;
+ private final Map suggestions;
+ private boolean processed;
+
+ public QueryMatchProcessor(TransMemoryQuery query, LocaleId srcLocale, LocaleId transLocale) {
+ this.query = query;
+ this.srcLocale = srcLocale;
+ this.transLocale = transLocale;
+ suggestions = new HashMap<>();
+ processed = false;
+ }
+
+ /**
+ * Run the query, process and collate the results.
+ *
+ * Results are cached, so subsequent calls will return cached results
+ * without running the query again.
+ *
+ * @return the collated results of the query.
+ */
+ public List process() {
+ if (!processed) {
+ runQueryAndCacheSuggestions();
+ }
+ return new ArrayList<>(suggestions.values());
+ }
+
+ /**
+ * When this has run, suggestions contains all the results of the query.
+ */
+ private void runQueryAndCacheSuggestions() {
+ for (Object[] resultRow : runQuery()) {
+ processResultRow(resultRow);
+ }
+ processed = true;
+ }
+
+ /**
+ * Convert a result row to a match (if possible) then process the match.
+ *
+ * If the row does not contain an appropriate entity, an error is logged
+ * and the row is skipped.
+ *
+ * @param resultRow in the form [Float score, Object entity]
+ */
+ private void processResultRow(Object[] resultRow) {
+ try {
+ final QueryMatch match = fromResultRow(resultRow);
+ processMatch(match);
+ } catch (IllegalArgumentException e) {
+ log.error(
+ "Skipped result row because it does not contain " +
+ "an expected entity type: {}", resultRow, e);
+ }
+ }
+
+ /**
+ * Run the full-text query.
+ * @return collection of [float, entity] where float is the match score
+ * and entity is a HTextFlowTarget or TransMemoryUnit.
+ */
+ private Collection
diff --git a/zanata-war/src/main/webapp/WEB-INF/layout/dashboard/activity.xhtml b/zanata-war/src/main/webapp/WEB-INF/layout/dashboard/activity.xhtml
index 47673fe66c..467930c087 100644
--- a/zanata-war/src/main/webapp/WEB-INF/layout/dashboard/activity.xhtml
+++ b/zanata-war/src/main/webapp/WEB-INF/layout/dashboard/activity.xhtml
@@ -26,65 +26,7 @@
\n \n Last updated {{detail.lastChanged | date: \'mediumDate\'}}\n Approved by {{user}} on {{detail.lastModifiedDate | date: \'mediumDate\'}}\n Translated by {{user}} on {{detail.lastModifiedDate | date: \'mediumDate\'}}\n