diff --git a/functional-test/src/main/java/org/zanata/page/projects/ProjectBasePage.java b/functional-test/src/main/java/org/zanata/page/projects/ProjectBasePage.java
index 377ac1a3ca..6fee0fe0af 100644
--- a/functional-test/src/main/java/org/zanata/page/projects/ProjectBasePage.java
+++ b/functional-test/src/main/java/org/zanata/page/projects/ProjectBasePage.java
@@ -36,6 +36,7 @@
import com.google.common.base.Predicate;
import lombok.extern.slf4j.Slf4j;
+import org.zanata.page.projects.projectsettings.ProjectWebHooksTab;
@Slf4j
public class ProjectBasePage extends BasePage {
@@ -67,6 +68,8 @@ public class ProjectBasePage extends BasePage {
@FindBy(id = "settings-about_tab")
private WebElement settingsAboutTab;
+ private By settingsWebHooksTab = By.id("settings-webhooks_tab");
+
public ProjectBasePage(final WebDriver driver) {
super(driver);
}
@@ -197,6 +200,13 @@ public boolean apply(WebDriver input) {
return new ProjectLanguagesTab(getDriver());
}
+ public ProjectWebHooksTab gotoSettingsWebHooksTab() {
+ log.info("Click WebHooks settings sub-tab");
+ clickWhenTabEnabled(waitForWebElement(settingsWebHooksTab));
+ waitForWebElement(By.id("settings-webhooks"));
+ return new ProjectWebHooksTab(getDriver());
+ }
+
public ProjectAboutTab gotoSettingsAboutTab() {
log.info("Click About settings sub-tab");
clickWhenTabEnabled(settingsAboutTab);
diff --git a/functional-test/src/main/java/org/zanata/page/projects/projectsettings/ProjectWebHooksTab.java b/functional-test/src/main/java/org/zanata/page/projects/projectsettings/ProjectWebHooksTab.java
new file mode 100644
index 0000000000..5385e02233
--- /dev/null
+++ b/functional-test/src/main/java/org/zanata/page/projects/projectsettings/ProjectWebHooksTab.java
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+package org.zanata.page.projects.projectsettings;
+
+import com.google.common.base.Predicate;
+import lombok.extern.slf4j.Slf4j;
+import org.openqa.selenium.By;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.zanata.page.projects.ProjectBasePage;
+import org.zanata.util.WebElementUtil;
+
+import java.util.List;
+
+/**
+ * @author Damian Jansen djansen@redhat.com
+ */
+@Slf4j
+public class ProjectWebHooksTab extends ProjectBasePage {
+
+ private By webHooksList = By.id("settings-webhooks-list");
+ private By urlInputField = By.id("payloadUrlInput");
+
+ public ProjectWebHooksTab(WebDriver driver) {
+ super(driver);
+ }
+
+ public ProjectWebHooksTab enterUrl(String url) {
+ waitForWebElement(urlInputField).sendKeys(url + Keys.ENTER);
+ return new ProjectWebHooksTab(getDriver());
+ }
+
+ public List getWebHooks() {
+ return WebElementUtil.elementsToText(waitForWebElement(webHooksList)
+ .findElement(By.className("list--slat"))
+ .findElements(By.className("list-item")));
+ }
+
+ public ProjectWebHooksTab waitForWebHooksContains(final String url) {
+ waitForAMoment().until(new Predicate() {
+ @Override
+ public boolean apply(WebDriver input) {
+ return getWebHooks().contains(url);
+ }
+ });
+ return new ProjectWebHooksTab(getDriver());
+ }
+
+ public ProjectWebHooksTab waitForWebHooksNotContains(final String url) {
+ waitForAMoment().until(new Predicate() {
+ @Override
+ public boolean apply(WebDriver input) {
+ return !getWebHooks().contains(url);
+ }
+ });
+ return new ProjectWebHooksTab(getDriver());
+ }
+
+ public ProjectWebHooksTab clickRemoveOn(String url) {
+ List listItems = waitForWebElement(webHooksList)
+ .findElement(By.className("list--slat"))
+ .findElements(By.className("list-item"));
+ boolean clicked = false;
+ for (WebElement listItem : listItems) {
+ if (listItem.getText().contains(url)) {
+ listItem.findElement(By.tagName("a")).click();
+ clicked = true;
+ break;
+ }
+ }
+ if (!clicked) {
+ log.info("Did not find item {}", url);
+ }
+ return new ProjectWebHooksTab(getDriver());
+ }
+}
diff --git a/functional-test/src/test/java/org/zanata/feature/project/EditWebHooksTest.java b/functional-test/src/test/java/org/zanata/feature/project/EditWebHooksTest.java
new file mode 100644
index 0000000000..b436c180de
--- /dev/null
+++ b/functional-test/src/test/java/org/zanata/feature/project/EditWebHooksTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+package org.zanata.feature.project;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.zanata.feature.Feature;
+import org.zanata.feature.testharness.TestPlan.DetailedTest;
+import org.zanata.feature.testharness.ZanataTestCase;
+import org.zanata.page.projects.projectsettings.ProjectWebHooksTab;
+import org.zanata.util.SampleProjectRule;
+import org.zanata.workflow.LoginWorkFlow;
+
+import static org.assertj.core.api.Assertions.assertThat;
+/**
+ * @author Damian Jansen djansen@redhat.com
+ */
+@Category(DetailedTest.class)
+public class EditWebHooksTest extends ZanataTestCase {
+
+ @Rule
+ public SampleProjectRule sampleProjectRule = new SampleProjectRule();
+
+ @Feature(summary = "The maintainer can add WebHooks for a project",
+ tcmsTestPlanIds = 5316, tcmsTestCaseIds = 0)
+ @Test(timeout = ZanataTestCase.MAX_SHORT_TEST_DURATION)
+ public void addWebHook() throws Exception {
+ String testUrl = "http://www.example.com";
+ ProjectWebHooksTab projectWebHooksTab = new LoginWorkFlow()
+ .signIn("admin", "admin")
+ .goToProjects()
+ .goToProject("about fedora")
+ .gotoSettingsTab()
+ .gotoSettingsWebHooksTab()
+ .enterUrl(testUrl)
+ .waitForWebHooksContains(testUrl);
+
+ assertThat(projectWebHooksTab.getWebHooks())
+ .contains(testUrl)
+ .as("The web hook was added");
+ }
+
+ @Feature(summary = "The maintainer can add WebHooks for a project",
+ tcmsTestPlanIds = 5316, tcmsTestCaseIds = 0)
+ @Test(timeout = ZanataTestCase.MAX_SHORT_TEST_DURATION)
+ public void removeWebHook() throws Exception {
+ String testUrl = "http://www.example.com";
+ ProjectWebHooksTab projectWebHooksTab = new LoginWorkFlow()
+ .signIn("admin", "admin")
+ .goToProjects()
+ .goToProject("about fedora")
+ .gotoSettingsTab()
+ .gotoSettingsWebHooksTab()
+ .enterUrl(testUrl)
+ .waitForWebHooksContains(testUrl)
+ .clickRemoveOn(testUrl)
+ .waitForWebHooksNotContains(testUrl);
+
+ assertThat(projectWebHooksTab.getWebHooks())
+ .doesNotContain(testUrl)
+ .as("The web hook was removed");
+ }
+}
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 072bb10f88..5eb3b8e0f1 100644
--- a/zanata-model/src/main/java/org/zanata/model/HProject.java
+++ b/zanata-model/src/main/java/org/zanata/model/HProject.java
@@ -31,6 +31,7 @@
import javax.persistence.Access;
import javax.persistence.AccessType;
+import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
@@ -52,8 +53,10 @@
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
+import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
+import org.hibernate.annotations.Where;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.validator.constraints.NotEmpty;
@@ -118,6 +121,9 @@ public class HProject extends SlugEntityBase implements Serializable,
name = "localeId"))
private Set customizedLocales = Sets.newHashSet();
+ @OneToMany(mappedBy = "project", cascade = CascadeType.ALL)
+ private List webHooks = Lists.newArrayList();
+
@Enumerated(EnumType.STRING)
private ProjectType defaultProjectType;
diff --git a/zanata-model/src/main/java/org/zanata/model/WebHook.java b/zanata-model/src/main/java/org/zanata/model/WebHook.java
new file mode 100644
index 0000000000..33da2e277a
--- /dev/null
+++ b/zanata-model/src/main/java/org/zanata/model/WebHook.java
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+package org.zanata.model;
+
+import java.io.Serializable;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import org.zanata.model.validator.Url;
+
+import com.google.common.annotations.VisibleForTesting;
+
+/**
+ * @author Alex Eng aeng@redhat.com
+ */
+@Entity
+@Getter
+@Setter(AccessLevel.PRIVATE)
+@NoArgsConstructor
+public class WebHook implements Serializable {
+
+ private Long id;
+
+ private HProject project;
+
+ @Url
+ private String url;
+
+ public WebHook(HProject project, String url) {
+ this.project = project;
+ this.url = url;
+ }
+
+ @Id
+ @GeneratedValue
+ public Long getId() {
+ return id;
+ }
+
+ @ManyToOne
+ @JoinColumn(name = "projectId", nullable = false)
+ public HProject getProject() {
+ return project;
+ }
+}
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 b45a434e8b..d001185a8b 100644
--- a/zanata-war/src/main/java/org/zanata/action/ProjectHome.java
+++ b/zanata-war/src/main/java/org/zanata/action/ProjectHome.java
@@ -52,6 +52,7 @@
import org.zanata.common.LocaleId;
import org.zanata.common.ProjectType;
import org.zanata.dao.AccountRoleDAO;
+import org.zanata.dao.WebHookDAO;
import org.zanata.i18n.Messages;
import org.zanata.model.HAccount;
import org.zanata.model.HAccountRole;
@@ -59,6 +60,7 @@
import org.zanata.model.HPerson;
import org.zanata.model.HProject;
import org.zanata.model.HProjectIteration;
+import org.zanata.model.WebHook;
import org.zanata.seam.scope.ConversationScopeMessages;
import org.zanata.security.ZanataIdentity;
import org.zanata.service.LocaleService;
@@ -69,6 +71,7 @@
import org.zanata.ui.autocomplete.LocaleAutocomplete;
import org.zanata.ui.autocomplete.MaintainerAutocomplete;
import org.zanata.util.ComparatorUtil;
+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;
@@ -110,6 +113,9 @@ public class ProjectHome extends SlugHome {
@In
private AccountRoleDAO accountRoleDAO;
+ @In
+ private WebHookDAO webHookDAO;
+
@In
private ValidationService validationServiceImpl;
@@ -533,6 +539,41 @@ public List getValidationList() {
return sortedList;
}
+ @Restrict("#{s:hasPermission(projectHome.instance, 'update')}")
+ public void addWebHook(String url) {
+ if (isValidUrl(url)) {
+ WebHook webHook = new WebHook(this.getInstance(), url);
+ getInstance().getWebHooks().add(webHook);
+ update();
+ FacesMessages.instance().add(StatusMessage.Severity.INFO,
+ msgs.format("jsf.project.AddNewWebhook", webHook.getUrl()));
+ }
+ }
+
+ @Restrict("#{s:hasPermission(projectHome.instance, 'update')}")
+ public void removeWebHook(WebHook webHook) {
+ getInstance().getWebHooks().remove(webHook);
+ webHookDAO.makeTransient(webHook);
+ FacesMessages.instance().add(StatusMessage.Severity.INFO,
+ msgs.format("jsf.project.RemoveWebhook", webHook.getUrl()));
+ }
+
+ private boolean isValidUrl(String url) {
+ if (!UrlUtil.isValidUrl(url)) {
+ FacesMessages.instance().add(StatusMessage.Severity.ERROR,
+ msgs.format("jsf.project.InvalidUrl", url));
+ return false;
+ }
+ for(WebHook webHook: getInstance().getWebHooks()) {
+ if(StringUtils.equalsIgnoreCase(webHook.getUrl(), url)) {
+ FacesMessages.instance().add(StatusMessage.Severity.ERROR,
+ msgs.format("jsf.project.DuplicateUrl", url));
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* If this action is enabled(Warning or Error), then it's exclusive
* validation will be turn off
diff --git a/zanata-war/src/main/java/org/zanata/dao/TextFlowDAO.java b/zanata-war/src/main/java/org/zanata/dao/TextFlowDAO.java
index 334acfeb2c..16e3a1bba7 100644
--- a/zanata-war/src/main/java/org/zanata/dao/TextFlowDAO.java
+++ b/zanata-war/src/main/java/org/zanata/dao/TextFlowDAO.java
@@ -113,6 +113,16 @@ public List