diff --git a/build-tools/src/main/resources/zanata-build-tools/versions-rules.xml b/build-tools/src/main/resources/zanata-build-tools/versions-rules.xml new file mode 100644 index 0000000000..c4e47caf9e --- /dev/null +++ b/build-tools/src/main/resources/zanata-build-tools/versions-rules.xml @@ -0,0 +1,8 @@ + + + + .*[-_\.](alpha|Alpha|ALPHA|b|beta|Beta|BETA|cr|CR|rc|RC|M|EA)[-_\.]?[0-9]* + + diff --git a/parent/pom.xml b/parent/pom.xml index b2583cd3ed..a2eec5884d 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -596,7 +596,17 @@ org.codehaus.mojo versions-maven-plugin - 2.2 + 2.5 + + classpath:///zanata-build-tools/versions-rules.xml + + + + org.zanata + build-tools + ${project.version} + + @@ -843,6 +853,19 @@ + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.1 + true + + + https://oss.sonatype.org/ + + sonatype-staging + + + org.apache.maven.plugins diff --git a/server/services/src/main/java/org/zanata/action/ActivateAction.java b/server/services/src/main/java/org/zanata/action/ActivateAction.java index 2bf5a1a9d3..857e3b9417 100644 --- a/server/services/src/main/java/org/zanata/action/ActivateAction.java +++ b/server/services/src/main/java/org/zanata/action/ActivateAction.java @@ -48,21 +48,34 @@ public class ActivateAction implements Serializable { private static final long serialVersionUID = -8079131168179421345L; private static final int LINK_ACTIVE_DAYS = 1; - @Inject + private GroupedConversation conversation; - @Inject private AccountActivationKeyDAO accountActivationKeyDAO; - @Inject private IdentityManager identityManager; - @Inject private UrlUtil urlUtil; - @Inject + private FacesMessages facesMessages; private String activationKey; private HAccountActivationKey key; private String resetPasswordKey; // @Begin(join = true) + @Inject + public ActivateAction(GroupedConversation conversation, + AccountActivationKeyDAO accountActivationKeyDAO, + IdentityManager identityManager, + UrlUtil urlUtil, FacesMessages facesMessages) { + this.conversation = conversation; + this.accountActivationKeyDAO = accountActivationKeyDAO; + this.identityManager = identityManager; + this.urlUtil = urlUtil; + this.facesMessages = facesMessages; + } + + @SuppressWarnings("unused") + public ActivateAction() { + } + public void validateActivationKey() { if (getActivationKey() == null) { throw new KeyNotFoundException("null activation key"); diff --git a/server/services/src/test/java/org/zanata/action/ActivateActionTest.java b/server/services/src/test/java/org/zanata/action/ActivateActionTest.java new file mode 100644 index 0000000000..c370d90e51 --- /dev/null +++ b/server/services/src/test/java/org/zanata/action/ActivateActionTest.java @@ -0,0 +1,133 @@ +/* + * Copyright 2018, 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.action; + +import org.apache.deltaspike.core.api.scope.GroupedConversation; +import org.jglue.cdiunit.InRequestScope; +import org.jglue.cdiunit.InSessionScope; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.zanata.dao.AccountActivationKeyDAO; +import org.zanata.exception.ActivationLinkExpiredException; +import org.zanata.exception.KeyNotFoundException; +import org.zanata.model.HAccount; +import org.zanata.model.HAccountActivationKey; +import org.zanata.seam.security.IdentityManager; +import org.zanata.test.CdiUnitRunner; +import org.zanata.ui.faces.FacesMessages; +import org.zanata.util.UrlUtil; + +import javax.enterprise.inject.Produces; +import javax.inject.Inject; +import java.util.Date; + +import static org.mockito.Mockito.when; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author djansen djansen@redhat.com + */ +@InSessionScope +@InRequestScope +@RunWith(CdiUnitRunner.class) +public class ActivateActionTest { + + @Produces + @Mock + private GroupedConversation conversation; + @Produces + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private AccountActivationKeyDAO accountActivationKeyDAO; + @Produces + @Mock + private IdentityManager identityManager; + @Produces + @Mock + private UrlUtil urlUtil; + @Produces + @Mock + private FacesMessages facesMessages; + @Inject + private ActivateAction action; + + @Before + public void before() { + action = new ActivateAction(conversation, accountActivationKeyDAO, + identityManager, urlUtil, facesMessages); + } + + @Test + public void nullKeyTest() { + action.setActivationKey(null); + try { + action.validateActivationKey(); + Assert.fail("Expected KeyNotFoundException"); + } catch (KeyNotFoundException knfe) { + assertThat(knfe.getMessage()).contains("null activation key"); + } + } + + @Test + public void keyNoLongerExists() { + action.setActivationKey("1234567890"); + when(accountActivationKeyDAO.findById(action.getActivationKey(), false)) + .thenReturn(null); + try { + action.validateActivationKey(); + Assert.fail("Expected KeyNotFoundException"); + } catch (KeyNotFoundException knfe) { + assertThat(knfe.getMessage()).contains("activation key: 1234567890"); + } + } + + @Test + public void expiredKeyTest() { + HAccountActivationKey key = new HAccountActivationKey(); + key.setCreationDate(new Date(0L)); + action.setActivationKey("1234567890"); + when(accountActivationKeyDAO.findById(action.getActivationKey(), false)) + .thenReturn(key); + try { + action.validateActivationKey(); + Assert.fail("Expected ActivationLinkExpiredException"); + } catch (ActivationLinkExpiredException alee) { + assertThat(alee.getMessage()) + .contains("Activation link expired:1234567890"); + } + } + + @Test + public void activateTest() { + HAccountActivationKey key = new HAccountActivationKey(); + key.setCreationDate(new Date()); + HAccount hAccount = new HAccount(); + hAccount.setUsername("Aloy"); + action.setActivationKey("1234567890"); + when(accountActivationKeyDAO.findById(action.getActivationKey(), false)) + .thenReturn(key); + action.validateActivationKey(); + // TODO ZNTA-2365 action.activate(); + } +} diff --git a/server/services/src/test/java/org/zanata/adapter/AdapterUtilsTest.java b/server/services/src/test/java/org/zanata/adapter/AdapterUtilsTest.java new file mode 100644 index 0000000000..6f482b6b31 --- /dev/null +++ b/server/services/src/test/java/org/zanata/adapter/AdapterUtilsTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2018, 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.adapter; + +import org.junit.Assert; +import org.junit.Test; +import org.zanata.exception.FileFormatAdapterException; + +import java.net.URI; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Damian Jansen djansen@redhat.com + */ +public class AdapterUtilsTest { + + @Test + public void testNull() { + try { + AdapterUtils.readStream(URI.create("")); + Assert.fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException iae) { + assertThat(iae.getMessage()).contains("absolute"); + } + } + + @Test + public void testMalformed() { + try { + AdapterUtils.readStream(URI + .create("http://2aff:eaff:eaff:7aff:8aff:5aff:faff:eaff:8080/foo")); + Assert.fail("Expected FileFormatAdapterException"); + } catch (FileFormatAdapterException ffae) { + assertThat(ffae.getMessage()).contains("malformed"); + } + } + + @Test + public void testAccess() { + try { + AdapterUtils.readStream(URI.create("http://tmp")); + Assert.fail("Expected FileFormatAdapterException"); + } catch (FileFormatAdapterException ffae) { + assertThat(ffae.getMessage()).contains("stream"); + } + } +} diff --git a/server/zanata-frontend/pom.xml b/server/zanata-frontend/pom.xml index 833dff6616..ad014c5dc8 100644 --- a/server/zanata-frontend/pom.xml +++ b/server/zanata-frontend/pom.xml @@ -133,6 +133,7 @@ src/dist + messages/*.json editor.*.cache.js editor.*.cache.css editor.*.cache.css.map diff --git a/server/zanata-frontend/src/app/config.js b/server/zanata-frontend/src/app/config.js index 2d734e454d..028ff5a369 100644 --- a/server/zanata-frontend/src/app/config.js +++ b/server/zanata-frontend/src/app/config.js @@ -8,6 +8,12 @@ const rawConfig = window.config const config = mapValues(rawConfig || {}, (value) => isJsonString(value) ? JSON.parse(value) : value) +export const DEFAULT_LOCALE = { + localeId: 'en-US', + name: 'English', + isRTL: false +} + // URL this app is deployed to export const appUrl = config.appUrl || '' @@ -27,3 +33,4 @@ export const isAdmin = config.permission : false export const user = config.user export const allowRegister = config.allowRegister || false +export const appLocale = config.appLocale || DEFAULT_LOCALE['localeId'] diff --git a/server/zanata-frontend/src/app/editor/actions/header-action-types.js b/server/zanata-frontend/src/app/editor/actions/header-action-types.js index bd1b3682eb..66f0ec5ec5 100644 --- a/server/zanata-frontend/src/app/editor/actions/header-action-types.js +++ b/server/zanata-frontend/src/app/editor/actions/header-action-types.js @@ -4,6 +4,7 @@ export const TOGGLE_INFO_PANEL = 'TOGGLE_INFO_PANEL' export const TOGGLE_HEADER = 'TOGGLE_HEADER' export const TOGGLE_KEY_SHORTCUTS = 'TOGGLE_KEY_SHORTCUTS' export const UI_LOCALES_FETCHED = 'UI_LOCALES_FETCHED' +export const APP_LOCALE_FETCHED = 'APP_LOCALE_FETCHED' export const CHANGE_UI_LOCALE = 'CHANGE_UI_LOCALE' export const DOCUMENT_SELECTED = 'DOCUMENT_SELECTED' export const LOCALE_SELECTED = 'LOCALE_SELECTED' @@ -12,3 +13,6 @@ export const HEADER_DATA_FETCHED = 'HEADER_DATA_FETCHED' export const USER_PERMISSION_REQUEST = 'USER_PERMISSION_REQUEST' export const USER_PERMISSION_SUCCESS = 'USER_PERMISSION_SUCCESS' export const USER_PERMISSION_FAILURE = 'USER_PERMISSION_FAILURE' +export const LOCALE_MESSAGES_REQUEST = 'LOCALE_MESSAGES_REQUEST' +export const LOCALE_MESSAGES_SUCCESS = 'LOCALE_MESSAGES_SUCCESS' +export const LOCALE_MESSAGES_FAILURE = 'LOCALE_MESSAGES_FAILURE' diff --git a/server/zanata-frontend/src/app/editor/actions/header-actions.js b/server/zanata-frontend/src/app/editor/actions/header-actions.js index 880d01e52a..30f82504f4 100644 --- a/server/zanata-frontend/src/app/editor/actions/header-actions.js +++ b/server/zanata-frontend/src/app/editor/actions/header-actions.js @@ -2,6 +2,7 @@ import { fetchStatistics, fetchLocales, + fetchI18nLocale, fetchMyInfo, fetchProjectInfo, fetchDocuments, @@ -15,6 +16,7 @@ import { TOGGLE_HEADER, TOGGLE_KEY_SHORTCUTS, UI_LOCALES_FETCHED, + APP_LOCALE_FETCHED, CHANGE_UI_LOCALE, DOCUMENT_SELECTED, LOCALE_SELECTED, @@ -40,6 +42,7 @@ const unwrapResponse = (_dispatch, _errorMsg, response) => { } export const uiLocaleFetched = createAction(UI_LOCALES_FETCHED) +export const appLocaleFetched = createAction(APP_LOCALE_FETCHED) export function fetchUiLocales () { return (dispatch) => { @@ -54,6 +57,13 @@ export function fetchUiLocales () { } } +// fetches react-intl translation json files for app i18n +export function fetchAppLocale (locale) { + return (dispatch) => { + dispatch(fetchI18nLocale(locale)) + } +} + export const changeUiLocale = createAction(CHANGE_UI_LOCALE) const decodeDocId = (docId) => { diff --git a/server/zanata-frontend/src/app/editor/api/index.js b/server/zanata-frontend/src/app/editor/api/index.js index 9754eacd23..742fc27db8 100644 --- a/server/zanata-frontend/src/app/editor/api/index.js +++ b/server/zanata-frontend/src/app/editor/api/index.js @@ -17,12 +17,15 @@ import { import { USER_PERMISSION_REQUEST, USER_PERMISSION_SUCCESS, - USER_PERMISSION_FAILURE + USER_PERMISSION_FAILURE, + LOCALE_MESSAGES_REQUEST, + LOCALE_MESSAGES_SUCCESS, + LOCALE_MESSAGES_FAILURE } from '../actions/header-action-types' import { buildAPIRequest, getJsonHeaders } from '../../actions/common-actions' import { CALL_API } from 'redux-api-middleware' import { includes } from 'lodash' -import { apiUrl, serverUrl } from '../../config' +import { apiUrl, serverUrl, appUrl } from '../../config' import stableStringify from 'faster-stable-stringify' export const dashboardUrl = serverUrl + '/dashboard' @@ -83,6 +86,27 @@ export function fetchLocales () { }) } +export function fetchI18nLocale (locale) { + const endpoint = `${appUrl}/messages/${locale}.json` + const apiTypes = [ + LOCALE_MESSAGES_REQUEST, + { + type: LOCALE_MESSAGES_SUCCESS, + payload: (_action, _state, res) => { + const contentType = res.headers.get('Content-Type') + if (contentType && includes(contentType, 'json')) { + return res.json().then((json) => { + return json + }) + } + } + }, + LOCALE_MESSAGES_FAILURE] + return { + [CALL_API]: buildAPIRequest(endpoint, 'GET', getJsonHeaders(), apiTypes) + } +} + export function fetchMyInfo () { const userUrl = `${apiUrl}/user` return fetch(userUrl, { diff --git a/server/zanata-frontend/src/app/editor/components/TranslatingIndicator/TranslatingIndicator.test.js b/server/zanata-frontend/src/app/editor/components/TranslatingIndicator/TranslatingIndicator.test.js index 8f9884dbf7..3c7b3cfef3 100644 --- a/server/zanata-frontend/src/app/editor/components/TranslatingIndicator/TranslatingIndicator.test.js +++ b/server/zanata-frontend/src/app/editor/components/TranslatingIndicator/TranslatingIndicator.test.js @@ -1,27 +1,30 @@ - import React from 'react' import * as ReactDOMServer from 'react-dom/server' import TranslatingIndicator from '.' import { Icon } from '../../../components' import { Row } from 'react-bootstrap' +import { IntlProvider } from 'react-intl' -describe('TranslatingIndicatorTest', () => { - it('TranslatingIndicator markup', () => { +/* global describe expect it */ +describe('TranslatingIndicator', () => { + it('renders default markup', () => { const gettextCatalog = { getString: (key) => { return key } } const actual = ReactDOMServer.renderToStaticMarkup( - ) + + + ) const expected = ReactDOMServer.renderToStaticMarkup( /* eslint-disable max-len */ - diff --git a/server/zanata-frontend/src/app/editor/components/TranslatingIndicator/index.js b/server/zanata-frontend/src/app/editor/components/TranslatingIndicator/index.js index f8aa8dd219..897ef71955 100644 --- a/server/zanata-frontend/src/app/editor/components/TranslatingIndicator/index.js +++ b/server/zanata-frontend/src/app/editor/components/TranslatingIndicator/index.js @@ -23,6 +23,7 @@ import React from 'react' import * as PropTypes from 'prop-types' import { Icon } from '../../../components' import { Row } from 'react-bootstrap' +import { FormattedMessage } from 'react-intl' /** * Indicator that shows 'Translating' when the user is @@ -41,11 +42,13 @@ class TranslatingIndicator extends React.Component { render () { return ( /* eslint-disable max-len */ - diff --git a/server/zanata-frontend/src/app/editor/containers/Root/index.js b/server/zanata-frontend/src/app/editor/containers/Root/index.js index b05f8be711..618e1adae4 100644 --- a/server/zanata-frontend/src/app/editor/containers/Root/index.js +++ b/server/zanata-frontend/src/app/editor/containers/Root/index.js @@ -9,12 +9,15 @@ import EditorHeader from '../EditorHeader' import KeyShortcutCheatSheet from '../KeyShortcutCheatSheet' import KeyShortcutDispatcher from '../KeyShortcutDispatcher' import SuggestionsPanel from '../SuggestionsPanel' -import { getSuggestionsPanelVisible } from '../../reducers' -import { fetchUiLocales } from '../../actions/header-actions' +import { getSuggestionsPanelVisible, getAppLocale } from '../../reducers' +import { fetchUiLocales, fetchAppLocale } from '../../actions/header-actions' import { saveSuggestionPanelHeight } from '../../actions/suggestions-actions' import SplitPane from 'react-split-pane' import { Icons } from '../../../components' import Sidebar from '../Sidebar' +import { locale, formats } from '../../config/intl' +import { appLocale } from '../../../config' +import { IntlProvider } from 'react-intl' /** * Top level of Zanata view hierarchy. @@ -24,7 +27,13 @@ class Root extends Component { percentHeight: PropTypes.number.isRequired, showSuggestion: PropTypes.bool, requestUiLocales: PropTypes.func.isRequired, - saveSuggestionPanelHeight: PropTypes.func.isRequired + requestAppLocale: PropTypes.func.isRequired, + saveSuggestionPanelHeight: PropTypes.func.isRequired, + localeMessages: PropTypes.object + } + + componentWillMount () { + this.props.requestAppLocale() } componentDidMount () { @@ -68,26 +77,31 @@ class Root extends Component { const pixelHeight = this.props.showSuggestion ? this.props.percentHeight * window.innerHeight : 0 - // TODO adjust scrollbar width on div like Angular template editor.html return ( - - - - - - - - {this.props.showSuggestion && } - - - - - + + + + + + + + + {this.props.showSuggestion && } + + + + + + ) } } @@ -97,12 +111,16 @@ function mapStateToProps (state) { return { percentHeight: suggestions.heightPercent, showSidebar: sidebar.visible, - showSuggestion: getSuggestionsPanelVisible(state) + showSuggestion: getSuggestionsPanelVisible(state), + localeMessages: getAppLocale(state) } } function mapDispatchToProps (dispatch) { return { + requestAppLocale: () => { + dispatch(fetchAppLocale(appLocale)) + }, saveSuggestionPanelHeight: (pixelHeight) => { const percent = pixelHeight / window.innerHeight dispatch(saveSuggestionPanelHeight(percent)) diff --git a/server/zanata-frontend/src/app/editor/entrypoint/index.js b/server/zanata-frontend/src/app/editor/entrypoint/index.js index b78cee56b0..83651e9f13 100644 --- a/server/zanata-frontend/src/app/editor/entrypoint/index.js +++ b/server/zanata-frontend/src/app/editor/entrypoint/index.js @@ -3,17 +3,15 @@ import 'babel-polyfill' import React from 'react' import * as ReactDOM from 'react-dom' import { appUrl, serverUrl } from '../../config' -import { locale, formats } from '../config/intl' import createStoreWithMiddleware from '../middlewares' -import { addLocaleData, IntlProvider } from 'react-intl' -import enLocaleData from 'react-intl/locale-data/en.js' +import { addLocaleData } from 'react-intl' import { Provider } from 'react-redux' import { browserHistory, Router, Route } from 'react-router' import { syncHistoryWithStore } from 'react-router-redux' import rootReducer from '../reducers' import addWatchers from '../watchers' import { isEmpty } from 'lodash' - +import { appLocale } from '../../config' import Root from '../containers/Root' import NeedSlugMessage from '../containers/NeedSlugMessage' import { fetchSettings } from '../actions/settings-actions' @@ -36,19 +34,8 @@ import '../index.css' * - ensuring the required css for the React component tree is available */ function runApp () { - // TODO add all the relevant locale data - // Something like: - // import en from './react-intl/locale-data/en' - // import de from './react-intl/locale-data/de' - // ... then just addLocaleData(en) etc. - // See https://github.com/yahoo/react-intl/blob/master/UPGRADE.md - // if ('ReactIntlLocaleData' in window) { - // Object.keys(window.ReactIntlLocaleData).forEach(lang => { - // addLocaleData(window.ReactIntlLocaleData[lang]) - // }) - // } - - addLocaleData([...enLocaleData]) + // Dynamically load the locale data of the selected appLocale + addLocaleData(require(`react-intl/locale-data/${appLocale}`)) const history = browserHistory history.basename = appUrl @@ -75,15 +62,13 @@ function runApp () { // require.ensure and pass to IntlProvider as messages={...} // defaultLocale will use the default messages with no errors ReactDOM.render( - - - - {/* The ** is docId, captured as params.splat by react-router. */} - - - - - , rootElement) + + + {/* The ** is docId, captured as params.splat by react-router. */} + + + + , rootElement) } if (window.Intl) { diff --git a/server/zanata-frontend/src/app/editor/index.html b/server/zanata-frontend/src/app/editor/index.html index 8956f7949c..44d6037baf 100644 --- a/server/zanata-frontend/src/app/editor/index.html +++ b/server/zanata-frontend/src/app/editor/index.html @@ -43,7 +43,8 @@ data-links='{ "loginUrl": "http://localhost:8080/account/sign_in", "registerUrl": "http://localhost:8080/account/register" - }'> + }' + data-app-locale='ja'>