availableValidations = Maps
.newHashMap();
@@ -429,10 +434,11 @@ public void setSlug(String slug) {
@Override
@Transactional
public String persist() {
- if (!validateSlug(getInputSlugValue(), "slug")) {
+ String slug = getInputSlugValue();
+ if (!validateSlug(slug, "slug")) {
return null;
}
- getInstance().setSlug(getInputSlugValue());
+ getInstance().setSlug(slug);
updateProjectType();
HProject project = getProject();
@@ -450,7 +456,11 @@ public String persist() {
getInstance().getCustomizedValidations().putAll(
project.getCustomizedValidations());
- return super.persist();
+ String result = super.persist();
+ webhookServiceImpl.processWebhookVersionChanged(getProjectSlug(), slug,
+ getProject().getWebHooks(),
+ VersionChangedEvent.ChangeType.CREATE);
+ return result;
}
@Override
@@ -559,7 +569,12 @@ public void updateStatus(char initial) {
@Transactional
public void deleteSelf() {
+ String slug = getInstance().getSlug();
updateStatus('O');
+
+ webhookServiceImpl.processWebhookVersionChanged(getProjectSlug(), slug,
+ getProject().getWebHooks(),
+ VersionChangedEvent.ChangeType.DELETE);
}
@Transactional
diff --git a/zanata-war/src/main/java/org/zanata/events/WebhookEvent.java b/zanata-war/src/main/java/org/zanata/events/WebhookEvent.java
new file mode 100644
index 0000000000..cfd0590f64
--- /dev/null
+++ b/zanata-war/src/main/java/org/zanata/events/WebhookEvent.java
@@ -0,0 +1,29 @@
+package org.zanata.events;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * Event for publish webhook after transaction.
+ * See {@link org.zanata.service.impl.WebhookServiceImpl#onPublishWebhook}
+ *
+ * @author Alex Eng aeng@redhat.com
+ */
+@AllArgsConstructor
+public final class WebhookEvent {
+
+ @Getter
+ @Nonnull
+ private final String url;
+
+ @Getter
+ @Nullable
+ private final String secret;
+
+ @Getter
+ @Nonnull
+ private final WebhookEventType type;
+}
diff --git a/zanata-war/src/main/java/org/zanata/rest/service/ProjectService.java b/zanata-war/src/main/java/org/zanata/rest/service/ProjectService.java
index 6427feb72e..015f4546f4 100644
--- a/zanata-war/src/main/java/org/zanata/rest/service/ProjectService.java
+++ b/zanata-war/src/main/java/org/zanata/rest/service/ProjectService.java
@@ -2,6 +2,7 @@
import static org.zanata.common.EntityStatus.OBSOLETE;
import static org.zanata.common.EntityStatus.READONLY;
+import static org.zanata.model.ProjectRole.Maintainer;
import static org.zanata.rest.service.GlossaryService.PROJECT_QUALIFIER_PREFIX;
import java.net.URI;
@@ -39,7 +40,9 @@
import org.zanata.rest.dto.ProjectIteration;
import org.zanata.rest.dto.QualifiedName;
import org.zanata.security.ZanataIdentity;
+import org.zanata.service.impl.WebhookServiceImpl;
import org.zanata.util.GlossaryUtil;
+import org.zanata.webhook.events.ProjectMaintainerChangedEvent;
import com.google.common.base.Objects;
@@ -71,6 +74,9 @@ public class ProjectService implements ProjectResource {
@Inject
ZanataIdentity identity;
+ @Inject
+ WebhookServiceImpl webhookServiceImpl;
+
@Inject
ETagUtils eTagUtils;
@@ -184,6 +190,12 @@ public Response put(Project project) {
projectDAO.makePersistent(hProject);
projectDAO.flush();
etag = eTagUtils.generateTagForProject(projectSlug);
+
+ webhookServiceImpl.processWebhookMaintainerChanged(
+ hProject.getSlug(),
+ identity.getCredentials().getUsername(),
+ Maintainer, hProject.getWebHooks(),
+ ProjectMaintainerChangedEvent.ChangeType.ADD);
return response.tag(etag).build();
}
diff --git a/zanata-war/src/main/java/org/zanata/service/ProjectService.java b/zanata-war/src/main/java/org/zanata/service/ProjectService.java
new file mode 100644
index 0000000000..2a91fb9b73
--- /dev/null
+++ b/zanata-war/src/main/java/org/zanata/service/ProjectService.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2016, Red Hat, Inc. and individual contributors as indicated by the
+ * @author tags. See the copyright.txt file in the distribution for a full
+ * listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF
+ * site: http://www.fsf.org.
+ */
+
+package org.zanata.service;
+
+import org.apache.deltaspike.jpa.api.transaction.Transactional;
+import org.zanata.model.HProject;
+import org.zanata.model.PersonProjectMemberships;
+import org.zanata.model.type.WebhookType;
+import org.zanata.service.impl.ProjectServiceImpl;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * @author Alex Engaeng@redhat.com
+ */
+public interface ProjectService {
+
+ /**
+ * Update all security settings for a person.
+ *
+ * The HPerson and HLocale entities in memberships must be attached to avoid
+ * persistence problems with Hibernate.
+ */
+ List updateProjectPermissions(HProject project,
+ PersonProjectMemberships memberships);
+
+ @Transactional
+ boolean updateWebhook(HProject project, Long webhookId, String url,
+ String secret, Set types);
+
+ @Transactional
+ boolean addWebhook(HProject project, String url, String secret,
+ Set types);
+
+ /**
+ * Check if project contains duplicate webhook with matching url
+ */
+ boolean isDuplicateWebhookUrl(HProject project, String url);
+
+ /**
+ * Check if project contains duplicate webhook with matching url other than
+ * given webhookId
+ */
+ boolean isDuplicateWebhookUrl(HProject project, String url, Long webhookId);
+
+ void updateLocalePermissions(HProject project, PersonProjectMemberships memberships);
+}
diff --git a/zanata-war/src/main/java/org/zanata/service/impl/DocumentServiceImpl.java b/zanata-war/src/main/java/org/zanata/service/impl/DocumentServiceImpl.java
index b0f6360078..209a952079 100644
--- a/zanata-war/src/main/java/org/zanata/service/impl/DocumentServiceImpl.java
+++ b/zanata-war/src/main/java/org/zanata/service/impl/DocumentServiceImpl.java
@@ -27,11 +27,9 @@
import java.util.concurrent.Future;
import java.util.stream.Collectors;
-import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import javax.enterprise.context.RequestScoped;
-import javax.enterprise.event.TransactionPhase;
import javax.inject.Inject;
import javax.inject.Named;
@@ -48,7 +46,6 @@
import org.zanata.events.DocumentLocaleKey;
import org.zanata.events.DocumentUploadedEvent;
import org.zanata.model.type.WebhookType;
-import org.zanata.webhook.events.DocumentMilestoneEvent;
import org.zanata.i18n.Messages;
import org.zanata.lock.Lock;
import org.zanata.model.HAccount;
@@ -74,6 +71,7 @@
import lombok.extern.slf4j.Slf4j;
import javax.enterprise.event.Event;
import org.zanata.util.UrlUtil;
+import org.zanata.webhook.events.SourceDocumentChangedEvent;
import javax.enterprise.event.Observes;
@@ -125,6 +123,9 @@ public class DocumentServiceImpl implements DocumentService {
@Inject @Authenticated
private HAccount authenticatedAccount;
+ @Inject
+ private WebhookServiceImpl webhookServiceImpl;
+
@Inject
private Messages msgs;
@@ -187,6 +188,7 @@ public HDocument saveDocument(String projectSlug, String iterationSlug,
.validateSourceLocale(sourceDoc.getLang());
boolean changed = false;
+ boolean isCreateOperation = false;
int nextDocRev;
if (document == null) { // must be a create operation
nextDocRev = 1;
@@ -198,12 +200,14 @@ public HDocument saveDocument(String projectSlug, String iterationSlug,
document.setProjectIteration(hProjectIteration);
hProjectIteration.getDocuments().put(docId, document);
document = documentDAO.makePersistent(document);
+ isCreateOperation = true;
} else if (document.isObsolete()) { // must also be a create operation
nextDocRev = document.getRevision() + 1;
changed = true;
document.setObsolete(false);
// not sure if this is needed
hProjectIteration.getDocuments().put(docId, document);
+ isCreateOperation = true;
} else { // must be an update operation
nextDocRev = document.getRevision() + 1;
}
@@ -218,6 +222,13 @@ public HDocument saveDocument(String projectSlug, String iterationSlug,
documentUploadedEvent.fire(new DocumentUploadedEvent(
actorId, document.getId(), true, hLocale.getLocaleId()));
clearStatsCacheForUpdatedDocument(document);
+
+ if (isCreateOperation) {
+ webhookServiceImpl.processWebhookSourceDocumentChanged(
+ projectSlug, iterationSlug, document.getDocId(),
+ hProjectIteration.getProject().getWebHooks(),
+ SourceDocumentChangedEvent.ChangeType.ADD);
+ }
}
if (copyTrans && nextDocRev == 1) {
@@ -236,6 +247,13 @@ public void makeObsolete(HDocument document) {
documentDAO.makePersistent(document);
documentDAO.flush();
clearStatsCacheForUpdatedDocument(document);
+
+ HProjectIteration version = document.getProjectIteration();
+ HProject proj = version.getProject();
+ webhookServiceImpl.processWebhookSourceDocumentChanged(
+ proj.getSlug(), version.getSlug(), document.getDocId(),
+ proj.getWebHooks(),
+ SourceDocumentChangedEvent.ChangeType.REMOVE);
}
// TODO [CDI] simulate async event (e.g. this event was fired asyncly in seam)
@@ -249,10 +267,10 @@ public void documentStatisticUpdated(@Observes DocStatsEvent event) {
}
List docMilestoneWebHooks =
- project.getWebHooks().stream().filter(
- webHook -> webHook.getWebhookType()
- .equals(WebhookType.DocumentMilestoneEvent))
- .collect(Collectors.toList());
+ project.getWebHooks().stream().filter(
+ webHook -> webHook.getTypes()
+ .contains(WebhookType.DocumentMilestoneEvent))
+ .collect(Collectors.toList());
if (docMilestoneWebHooks.isEmpty()) {
return;
@@ -313,20 +331,9 @@ private void processWebHookDocumentMilestoneEvent(DocumentLocaleKey key,
versionSlug, localeId,
LocaleId.EN_US, document.getDocId());
- DocumentMilestoneEvent milestoneEvent =
- new DocumentMilestoneEvent(projectSlug,
- versionSlug, document.getDocId(),
- localeId, message, editorUrl);
- publishDocumentMilestoneEvent(docMilestoneWebHooks,
- milestoneEvent);
- }
- }
-
- public void publishDocumentMilestoneEvent(List webHooks,
- DocumentMilestoneEvent event) {
- for (WebHook webHook : webHooks) {
- WebHooksPublisher.publish(webHook.getUrl(), event,
- Optional.fromNullable(webHook.getSecret()));
+ webhookServiceImpl.processDocumentMilestone(projectSlug,
+ versionSlug, document.getDocId(), localeId, message,
+ editorUrl, docMilestoneWebHooks);
}
}
@@ -379,12 +386,14 @@ private void clearStatsCacheForUpdatedDocument(HDocument document) {
public void init(ProjectIterationDAO projectIterationDAO,
DocumentDAO documentDAO,
TranslationStateCache translationStateCacheImpl, UrlUtil urlUtil,
- ApplicationConfiguration applicationConfiguration, Messages msgs) {
+ ApplicationConfiguration applicationConfiguration, Messages msgs,
+ WebhookServiceImpl webhookService) {
this.projectIterationDAO = projectIterationDAO;
this.documentDAO = documentDAO;
this.translationStateCacheImpl = translationStateCacheImpl;
this.urlUtil = urlUtil;
this.applicationConfiguration = applicationConfiguration;
this.msgs = msgs;
+ this.webhookServiceImpl = webhookService;
}
}
diff --git a/zanata-war/src/main/java/org/zanata/service/impl/ProjectServiceImpl.java b/zanata-war/src/main/java/org/zanata/service/impl/ProjectServiceImpl.java
new file mode 100644
index 0000000000..02224ca81f
--- /dev/null
+++ b/zanata-war/src/main/java/org/zanata/service/impl/ProjectServiceImpl.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2016, Red Hat, Inc. and individual contributors as indicated by the
+ * @author tags. See the copyright.txt file in the distribution for a full
+ * listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This software is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this software; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF
+ * site: http://www.fsf.org.
+ */
+
+package org.zanata.service.impl;
+
+import com.google.common.collect.Lists;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.deltaspike.jpa.api.transaction.Transactional;
+import org.zanata.dao.ProjectDAO;
+import org.zanata.dao.WebHookDAO;
+import org.zanata.model.HLocale;
+import org.zanata.model.HPerson;
+import org.zanata.model.HProject;
+import org.zanata.model.HProjectLocaleMember;
+import org.zanata.model.HProjectMember;
+import org.zanata.model.LocaleRole;
+import org.zanata.model.PersonProjectMemberships;
+import org.zanata.model.ProjectRole;
+import org.zanata.model.WebHook;
+import org.zanata.model.type.WebhookType;
+import org.zanata.service.ProjectService;
+
+import javax.enterprise.context.RequestScoped;
+import javax.inject.Inject;
+import javax.inject.Named;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import static javax.faces.application.FacesMessage.SEVERITY_ERROR;
+import static org.zanata.model.LocaleRole.Coordinator;
+import static org.zanata.model.LocaleRole.Reviewer;
+import static org.zanata.model.LocaleRole.Translator;
+import static org.zanata.model.ProjectRole.Maintainer;
+import static org.zanata.model.ProjectRole.TranslationMaintainer;
+
+/**
+ * @author Alex Engaeng@redhat.com
+ */
+@Named("projectServiceImpl")
+@RequestScoped
+@Slf4j
+public class ProjectServiceImpl implements ProjectService {
+
+ @Inject
+ private ProjectDAO projectDAO;
+
+ @Inject
+ private WebHookDAO webHookDAO;
+
+ @Override
+ public List updateProjectPermissions(HProject project,
+ PersonProjectMemberships memberships) {
+ HPerson person = memberships.getPerson();
+
+ boolean wasMaintainer =
+ project.getMaintainers().contains(memberships.getPerson());
+ boolean isLastMaintainer =
+ wasMaintainer && project.getMaintainers().size() <= 1;
+ // business rule: every project must have at least one maintainer
+ boolean isMaintainer = isLastMaintainer || memberships.isMaintainer();
+
+ List updatedRoles = Lists.newArrayList();
+
+ Optional
+ updatedMaintainerRole = ensureMembership(project, isMaintainer,
+ asMember(project, person, Maintainer));
+
+ if (updatedMaintainerRole.isPresent()) {
+ updatedRoles.add(updatedMaintainerRole.get());
+ }
+
+ // business rule: if someone is a Maintainer, they must also be a TranslationMaintainer
+ boolean isTranslationMaintainer = memberships.isMaintainer() ||
+ memberships.isTranslationMaintainer();
+
+ Optional updatedTranslationMaintainer =
+ ensureMembership(project, isTranslationMaintainer,
+ asMember(project, person, TranslationMaintainer));
+
+ if (updatedTranslationMaintainer.isPresent()) {
+ updatedRoles.add(updatedTranslationMaintainer.get());
+ }
+ return updatedRoles;
+ }
+
+ @Transactional
+ @Override
+ public boolean updateWebhook(HProject project, Long webhookId, String url,
+ String secret, Set types) {
+ if (types.isEmpty()) {
+ return false;
+ }
+ if (project == null) {
+ return false;
+ }
+ WebHook webHook = webHookDAO.findById(webhookId);
+ if (webHook == null) {
+ return false;
+ }
+ secret = StringUtils.isBlank(secret) ? null : secret;
+ webHook.update(url, types, secret);
+ webHookDAO.makePersistent(webHook);
+ return true;
+ }
+
+ @Transactional
+ @Override
+ public boolean addWebhook(HProject project, String url, String secret,
+ Set types) {
+ if (types.isEmpty()) {
+ return false;
+ }
+ if (project == null) {
+ return false;
+ }
+ secret = StringUtils.isBlank(secret) ? null : secret;
+ WebHook webHook =
+ new WebHook(project, url, types, secret);
+ project.getWebHooks().add(webHook);
+ projectDAO.makePersistent(project);
+ return true;
+ }
+
+ @Override
+ public boolean isDuplicateWebhookUrl(HProject project, String url) {
+ for (WebHook webHook : project.getWebHooks()) {
+ if (StringUtils.equalsIgnoreCase(webHook.getUrl(), url)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isDuplicateWebhookUrl(HProject project, String url,
+ Long webhookId) {
+ for (WebHook webHook : project.getWebHooks()) {
+ if (!webhookId.equals(webHook.getId())
+ && StringUtils.equalsIgnoreCase(webHook.getUrl(), url)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void updateLocalePermissions(HProject project,
+ PersonProjectMemberships memberships) {
+ HPerson person = memberships.getPerson();
+
+ for (PersonProjectMemberships.LocaleRoles localeRoles
+ : memberships.getLocaleRoles()) {
+ HLocale locale = localeRoles.getLocale();
+ ensureMembership(project, localeRoles.isTranslator(),
+ asMember(project, locale, person, Translator));
+ ensureMembership(project, localeRoles.isReviewer(),
+ asMember(project, locale, person, Reviewer));
+ ensureMembership(project, localeRoles.isCoordinator(),
+ asMember(project, locale, person, Coordinator));
+ }
+ }
+
+ @Getter
+ @AllArgsConstructor
+ public class UpdatedRole {
+ private String username;
+ private ProjectRole role;
+ private boolean added;
+ }
+
+ /**
+ * Get a person as a member object in this project for a role.
+ */
+ private HProjectMember asMember(HProject project, HPerson person,
+ ProjectRole role) {
+ return new HProjectMember(project, person, role);
+ }
+
+ /**
+ * Get a person as a member object in this project for a locale-specific role.
+ */
+ private HProjectLocaleMember asMember(HProject project, HLocale locale,
+ HPerson person, LocaleRole role) {
+ return new HProjectLocaleMember(project, locale, person, role);
+ }
+
+ /**
+ * Ensure the given membership is present or absent.
+ */
+ private Optional ensureMembership(HProject project, boolean shouldBePresent,
+ HProjectMember membership) {
+ UpdatedRole updatedRole = null;
+ final Set members = project.getMembers();
+ final boolean isPresent = members.contains(membership);
+ if (isPresent != shouldBePresent) {
+ if (shouldBePresent) {
+ members.add(membership);
+ updatedRole = new UpdatedRole(
+ membership.getPerson().getAccount().getUsername(),
+ membership.getRole(), true);
+ } else {
+ members.remove(membership);
+ updatedRole = new UpdatedRole(
+ membership.getPerson().getAccount().getUsername(),
+ membership.getRole(), false);
+ }
+ }
+ return Optional.ofNullable(updatedRole);
+ }
+
+ /**
+ * Ensure the given locale membership is present or absent.
+ */
+ private void ensureMembership(HProject project, boolean shouldBePresent,
+ HProjectLocaleMember membership) {
+ final Set members = project.getLocaleMembers();
+ final boolean isPresent = members.contains(membership);
+ if (isPresent != shouldBePresent) {
+ if (shouldBePresent) {
+ members.add(membership);
+ } else {
+ members.remove(membership);
+ }
+ }
+ }
+}
diff --git a/zanata-war/src/main/java/org/zanata/service/impl/RegisterServiceImpl.java b/zanata-war/src/main/java/org/zanata/service/impl/RegisterServiceImpl.java
index c8bbd80c7d..7595afbe6c 100644
--- a/zanata-war/src/main/java/org/zanata/service/impl/RegisterServiceImpl.java
+++ b/zanata-war/src/main/java/org/zanata/service/impl/RegisterServiceImpl.java
@@ -49,6 +49,9 @@
import org.zanata.seam.security.ZanataJpaIdentityStore;
import org.zanata.service.RegisterService;
import org.zanata.util.HashUtil;
+import org.zanata.webhook.events.ProjectMaintainerChangedEvent;
+
+import static org.zanata.model.ProjectRole.Maintainer;
@Named("registerServiceImpl")
@RequestScoped
@@ -66,6 +69,9 @@ public class RegisterServiceImpl implements RegisterService {
@Inject
PersonDAO personDAO;
+ @Inject
+ WebhookServiceImpl webhookServiceImpl;
+
@Inject
AccountRoleDAO accountRoleDAO;
@@ -232,7 +238,16 @@ public void execute() {
obsoletePerson.getMaintainerProjects());
for (HProject proj : maintainedProjects) {
proj.addMaintainer(activePerson);
+ webhookServiceImpl.processWebhookMaintainerChanged(proj.getSlug(),
+ activePerson.getAccount().getUsername(), Maintainer,
+ proj.getWebHooks(),
+ ProjectMaintainerChangedEvent.ChangeType.ADD);
+
proj.removeMaintainer(obsoletePerson);
+ webhookServiceImpl.processWebhookMaintainerChanged(proj.getSlug(),
+ obsoletePerson.getAccount().getUsername(), Maintainer,
+ proj.getWebHooks(),
+ ProjectMaintainerChangedEvent.ChangeType.REMOVE);
}
// Merge all maintained Version Groups
diff --git a/zanata-war/src/main/java/org/zanata/service/impl/TranslationUpdatedManager.java b/zanata-war/src/main/java/org/zanata/service/impl/TranslationUpdatedManager.java
index fcac96e3dc..2416f9d723 100644
--- a/zanata-war/src/main/java/org/zanata/service/impl/TranslationUpdatedManager.java
+++ b/zanata-war/src/main/java/org/zanata/service/impl/TranslationUpdatedManager.java
@@ -12,7 +12,6 @@
import org.zanata.dao.TextFlowTargetDAO;
import org.zanata.events.DocStatsEvent;
import org.zanata.model.type.WebhookType;
-import org.zanata.webhook.events.DocumentStatsEvent;
import org.zanata.model.HDocument;
import org.zanata.model.HPerson;
import org.zanata.model.HProject;
@@ -20,7 +19,6 @@
import org.zanata.model.WebHook;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Optional;
import lombok.extern.slf4j.Slf4j;
import javax.enterprise.event.Observes;
@@ -47,6 +45,9 @@ public class TranslationUpdatedManager {
@Inject
private DocumentDAO documentDAO;
+ @Inject
+ private WebhookServiceImpl webhookServiceImpl;
+
@Async
public void docStatsUpdated(
@Observes(during = TransactionPhase.AFTER_SUCCESS)
@@ -69,10 +70,10 @@ protected void processWebHookEvent(DocStatsEvent event) {
}
List docStatsWebHooks =
- project.getWebHooks().stream().filter(
- webHook -> webHook.getWebhookType()
- .equals(WebhookType.DocumentStatsEvent))
- .collect(Collectors.toList());
+ project.getWebHooks().stream().filter(
+ webHook -> webHook.getTypes()
+ .contains(WebhookType.DocumentStatsEvent))
+ .collect(Collectors.toList());
if (docStatsWebHooks.isEmpty()) {
return;
@@ -83,27 +84,18 @@ protected void processWebHookEvent(DocStatsEvent event) {
String projectSlug = project.getSlug();
LocaleId localeId = event.getKey().getLocaleId();
- DocumentStatsEvent webhookEvent =
- new DocumentStatsEvent(person.getAccount().getUsername(),
- projectSlug, versionSlug, docId, localeId,
- event.getWordDeltasByState());
-
- publishWebhookEvent(docStatsWebHooks, webhookEvent);
- }
-
- @VisibleForTesting
- public void publishWebhookEvent(List webHooks,
- DocumentStatsEvent event) {
- for (WebHook webHook : webHooks) {
- WebHooksPublisher.publish(webHook.getUrl(), event,
- Optional.fromNullable(webHook.getSecret()));
- }
+ webhookServiceImpl.processDocumentStats(
+ person.getAccount().getUsername(),
+ projectSlug, versionSlug, docId, localeId,
+ event.getWordDeltasByState(), docStatsWebHooks);
}
@VisibleForTesting
- public void init(DocumentDAO documentDAO,
- TextFlowTargetDAO textFlowTargetDAO) {
+ protected void init(DocumentDAO documentDAO,
+ TextFlowTargetDAO textFlowTargetDAO,
+ WebhookServiceImpl webhookService) {
this.documentDAO = documentDAO;
this.textFlowTargetDAO = textFlowTargetDAO;
+ this.webhookServiceImpl = webhookService;
}
}
diff --git a/zanata-war/src/main/java/org/zanata/service/impl/WebHooksPublisher.java b/zanata-war/src/main/java/org/zanata/service/impl/WebHooksPublisher.java
index f10f055b93..b969f83d1b 100644
--- a/zanata-war/src/main/java/org/zanata/service/impl/WebHooksPublisher.java
+++ b/zanata-war/src/main/java/org/zanata/service/impl/WebHooksPublisher.java
@@ -21,6 +21,7 @@
package org.zanata.service.impl;
+import java.util.Optional;
import javax.annotation.Nonnull;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
@@ -32,7 +33,6 @@
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
import org.zanata.events.WebhookEventType;
-import com.google.common.base.Optional;
import lombok.extern.slf4j.Slf4j;
import org.zanata.util.HmacUtil;
diff --git a/zanata-war/src/main/java/org/zanata/service/impl/WebhookServiceImpl.java b/zanata-war/src/main/java/org/zanata/service/impl/WebhookServiceImpl.java
new file mode 100644
index 0000000000..e01195d4d8
--- /dev/null
+++ b/zanata-war/src/main/java/org/zanata/service/impl/WebhookServiceImpl.java
@@ -0,0 +1,241 @@
+package org.zanata.service.impl;
+
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.enterprise.context.RequestScoped;
+import javax.enterprise.event.Event;
+import javax.enterprise.event.Observes;
+import javax.enterprise.event.TransactionPhase;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.apache.commons.lang3.StringUtils;
+import org.ocpsoft.common.util.Strings;
+import org.zanata.async.Async;
+import org.zanata.common.ContentState;
+import org.zanata.common.LocaleId;
+import org.zanata.events.WebhookEvent;
+import org.zanata.events.WebhookEventType;
+import org.zanata.i18n.Messages;
+import org.zanata.model.ProjectRole;
+import org.zanata.model.WebHook;
+import org.zanata.model.type.WebhookType;
+import org.zanata.util.UrlUtil;
+import org.zanata.webhook.events.DocumentMilestoneEvent;
+import org.zanata.webhook.events.DocumentStatsEvent;
+import org.zanata.webhook.events.ProjectMaintainerChangedEvent;
+import org.zanata.webhook.events.SourceDocumentChangedEvent;
+import org.zanata.webhook.events.TestEvent;
+import org.zanata.webhook.events.VersionChangedEvent;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author Alex Eng aeng@redhat.com
+ */
+@Slf4j
+@Named("webhookServiceImpl")
+@RequestScoped
+public class WebhookServiceImpl implements Serializable {
+
+ @Inject
+ private Messages msgs;
+
+ @Inject
+ private Event webhookEventEvent;
+
+ private static final int URL_MAX_LENGTH = 255;
+
+ /**
+ * Need @Async annotation for TransactionPhase.AFTER_SUCCESS event
+ */
+ @Async
+ public void onPublishWebhook(@Observes(
+ during = TransactionPhase.AFTER_SUCCESS) WebhookEvent event) {
+ WebHooksPublisher
+ .publish(event.getUrl(), event.getType(),
+ Optional.ofNullable(event.getSecret()));
+ }
+
+ /**
+ * Process TestEvent
+ */
+ public void processTestEvent(String username, String projectSlug,
+ String url, String secret) {
+ TestEvent event = new TestEvent(username, projectSlug);
+ webhookEventEvent.fire(new WebhookEvent(url, secret, event));
+ }
+
+ /**
+ * Process VersionChangedEvent
+ */
+ public void processWebhookVersionChanged(String projectSlug, String versionSlug,
+ List webHooks, VersionChangedEvent.ChangeType changeType) {
+ List versionWebhooks =
+ filterWebhookByType(webHooks, WebhookType.VersionChangedEvent);
+
+ if (versionWebhooks.isEmpty()) {
+ return;
+ }
+ VersionChangedEvent event =
+ new VersionChangedEvent(projectSlug, versionSlug, changeType);
+ publishWebhooks(versionWebhooks, event);
+ }
+
+ /**
+ * Process ProjectMaintainerChangedEvent
+ */
+ public void processWebhookMaintainerChanged(String projectSlug,
+ String username, ProjectRole role, List webHooks,
+ ProjectMaintainerChangedEvent.ChangeType changeType) {
+
+ List maintainerWebhooks =
+ filterWebhookByType(webHooks,
+ WebhookType.ProjectMaintainerChangedEvent);
+
+ if (maintainerWebhooks.isEmpty()) {
+ return;
+ }
+ ProjectMaintainerChangedEvent event =
+ new ProjectMaintainerChangedEvent(projectSlug, username,
+ role, changeType);
+ publishWebhooks(maintainerWebhooks, event);
+ }
+
+ /**
+ * Process SourceDocumentChangedEvent
+ */
+ public void processWebhookSourceDocumentChanged(String project,
+ String version, String docId, List webHooks,
+ SourceDocumentChangedEvent.ChangeType changeType) {
+ List eventWebhooks = filterWebhookByType(webHooks,
+ WebhookType.SourceDocumentChangedEvent);
+
+ if (eventWebhooks.isEmpty()) {
+ return;
+ }
+ SourceDocumentChangedEvent event = new SourceDocumentChangedEvent(
+ project, version, docId, changeType);
+
+ publishWebhooks(eventWebhooks, event);
+ }
+
+ /**
+ * Process DocumentMilestoneEvent
+ */
+ public void processDocumentMilestone(String projectSlug,
+ String versionSlug, String docId, LocaleId localeId, String message,
+ String editorUrl, List webHooks) {
+ DocumentMilestoneEvent milestoneEvent =
+ new DocumentMilestoneEvent(projectSlug, versionSlug, docId,
+ localeId, message, editorUrl);
+ publishWebhooks(webHooks, milestoneEvent);
+ }
+
+ /**
+ * Process DocumentStatsEvent
+ */
+ public void processDocumentStats(String username, String projectSlug,
+ String versionSlug, String docId, LocaleId localeId,
+ Map wordDeltasByState, List webHooks) {
+ DocumentStatsEvent statsEvent =
+ new DocumentStatsEvent(username, projectSlug, versionSlug,
+ docId, localeId, wordDeltasByState);
+ publishWebhooks(webHooks, statsEvent);
+ }
+
+ public List getAvailableWebhookTypes() {
+ WebhookTypeItem docMilestone =
+ new WebhookTypeItem(WebhookType.DocumentMilestoneEvent,
+ msgs.get(
+ "jsf.webhookType.DocumentMilestoneEvent.desc"));
+
+ WebhookTypeItem stats =
+ new WebhookTypeItem(WebhookType.DocumentStatsEvent,
+ msgs.get("jsf.webhookType.DocumentStatsEvent.desc"));
+
+ WebhookTypeItem version =
+ new WebhookTypeItem(WebhookType.VersionChangedEvent,
+ msgs.get("jsf.webhookType.VersionChangedEvent.desc"));
+
+ WebhookTypeItem maintainer =
+ new WebhookTypeItem(WebhookType.ProjectMaintainerChangedEvent,
+ msgs.get("jsf.webhookType.ProjectMaintainerChangedEvent.desc"));
+
+ WebhookTypeItem srcDoc =
+ new WebhookTypeItem(WebhookType.SourceDocumentChangedEvent,
+ msgs.get("jsf.webhookType.SourceDocumentChangedEvent.desc"));
+
+ return Lists
+ .newArrayList(docMilestone, stats, version, maintainer, srcDoc);
+ }
+
+ public List getDisplayNames(Set types) {
+ return types.stream().map(WebhookType::getDisplayName)
+ .collect(Collectors.toList());
+ }
+
+ public String getTypesAsString(WebHook webHook) {
+ if (webHook == null) {
+ return "";
+ }
+ List results = webHook.getTypes().stream().map(Enum::name)
+ .collect(Collectors.toList());
+ return Strings.join(results, ",");
+ }
+
+ public boolean isValidUrl(String url) {
+ return UrlUtil.isValidUrl(url)
+ && StringUtils.length(url) <= URL_MAX_LENGTH;
+ }
+
+ public static Set getTypesFromString(String strTypes) {
+ return new HashSet(
+ Lists.transform(Lists.newArrayList(strTypes.split(",")),
+ convertToWebHookType));
+ }
+
+ private static Function convertToWebHookType =
+ new Function() {
+ @Override
+ public WebhookType apply(String input) {
+ return WebhookType.valueOf(input);
+ }
+ };
+
+ /**
+ * Object for all available webhook list
+ */
+ @Getter
+ public final static class WebhookTypeItem {
+ private WebhookType type;
+ private String description;
+
+ public WebhookTypeItem(WebhookType webhookType, String desc) {
+ this.type = webhookType;
+ this.description = desc;
+ }
+ }
+
+ private void publishWebhooks(List webHooks,
+ WebhookEventType event) {
+ for (WebHook webhook : webHooks) {
+ webhookEventEvent.fire(new WebhookEvent(webhook.getUrl(),
+ webhook.getSecret(), event));
+ }
+ }
+
+ private List filterWebhookByType(List webHooks,
+ WebhookType webhookType) {
+ return webHooks.stream().filter(webHook -> webHook.getTypes()
+ .contains(webhookType)).collect(Collectors.toList());
+ }
+}
diff --git a/zanata-war/src/main/java/org/zanata/webhook/events/ProjectMaintainerChangedEvent.java b/zanata-war/src/main/java/org/zanata/webhook/events/ProjectMaintainerChangedEvent.java
new file mode 100644
index 0000000000..aace6f88c9
--- /dev/null
+++ b/zanata-war/src/main/java/org/zanata/webhook/events/ProjectMaintainerChangedEvent.java
@@ -0,0 +1,58 @@
+package org.zanata.webhook.events;
+
+import java.util.Date;
+
+import org.codehaus.jackson.annotate.JsonPropertyOrder;
+import org.zanata.events.WebhookEventType;
+import org.zanata.model.ProjectRole;
+import org.zanata.model.type.WebhookType;
+
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Event for when a project maintainer is added or removed
+ * @author Alex Eng aeng@redhat.com
+ */
+@Getter
+@Setter
+@JsonPropertyOrder({"project", "username", "changeType", "role"})
+@AllArgsConstructor
+@EqualsAndHashCode
+public class ProjectMaintainerChangedEvent extends WebhookEventType {
+ private static final String EVENT_TYPE =
+ WebhookType.ProjectMaintainerChangedEvent.name();
+
+ public static enum ChangeType {
+ ADD,
+ REMOVE
+ }
+
+ /**
+ * Target project slug.
+ * {@link org.zanata.model.HProject#slug}
+ */
+ private final String project;
+
+ /**
+ * Username of the maintainer
+ */
+ private final String username;
+
+ /**
+ * Changed role /
+ */
+ private final ProjectRole role;
+
+ /**
+ * Change type
+ */
+ private final ChangeType changeType;
+
+ @Override
+ public String getType() {
+ return EVENT_TYPE;
+ }
+}
diff --git a/zanata-war/src/main/java/org/zanata/webhook/events/SourceDocumentChangedEvent.java b/zanata-war/src/main/java/org/zanata/webhook/events/SourceDocumentChangedEvent.java
new file mode 100644
index 0000000000..95f246b7cc
--- /dev/null
+++ b/zanata-war/src/main/java/org/zanata/webhook/events/SourceDocumentChangedEvent.java
@@ -0,0 +1,59 @@
+package org.zanata.webhook.events;
+
+import java.util.Date;
+
+import org.codehaus.jackson.annotate.JsonPropertyOrder;
+import org.zanata.events.WebhookEventType;
+import org.zanata.model.ProjectRole;
+import org.zanata.model.type.WebhookType;
+
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Event for when a source document is added or removed
+ * @author Alex Eng aeng@redhat.com
+ */
+@Getter
+@Setter
+@JsonPropertyOrder({"project", "version", "docId", "changeType"})
+@AllArgsConstructor
+@EqualsAndHashCode
+public class SourceDocumentChangedEvent extends WebhookEventType {
+ private static final String EVENT_TYPE =
+ WebhookType.SourceDocumentChangedEvent.name();
+
+ public static enum ChangeType {
+ ADD,
+ REMOVE
+ }
+
+ /**
+ * Target project slug.
+ * {@link org.zanata.model.HProject#slug}
+ */
+ private final String project;
+
+ /**
+ * Target version slug.
+ * {@link org.zanata.model.HProjectIteration#slug}
+ */
+ private final String version;
+
+ /**
+ * Document id
+ */
+ private final String docId;
+
+ /**
+ * Change type
+ */
+ private final ChangeType changeType;
+
+ @Override
+ public String getType() {
+ return EVENT_TYPE;
+ }
+}
diff --git a/zanata-war/src/main/java/org/zanata/webhook/events/VersionChangedEvent.java b/zanata-war/src/main/java/org/zanata/webhook/events/VersionChangedEvent.java
new file mode 100644
index 0000000000..de52f0cd6a
--- /dev/null
+++ b/zanata-war/src/main/java/org/zanata/webhook/events/VersionChangedEvent.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2016, 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.webhook.events;
+
+import java.util.Date;
+import java.util.Map;
+
+import org.codehaus.jackson.annotate.JsonPropertyOrder;
+import org.zanata.common.ContentState;
+import org.zanata.common.LocaleId;
+import org.zanata.events.WebhookEventType;
+import org.zanata.model.type.WebhookType;
+
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ *
+ * Event for when a version is created/removed in a project
+ *
+ * @author Alex Eng aeng@redhat.com
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@JsonPropertyOrder({"project", "version", "changeType"})
+@EqualsAndHashCode
+public class VersionChangedEvent extends WebhookEventType {
+
+ private static final String EVENT_TYPE =
+ WebhookType.VersionChangedEvent.name();
+
+ public static enum ChangeType {
+ CREATE,
+ DELETE
+ }
+
+ /**
+ * Target project slug.
+ * {@link org.zanata.model.HProject#slug}
+ */
+ private final String project;
+
+ /**
+ * Target project version slug.
+ * {@link org.zanata.model.HProjectIteration#slug}
+ */
+ private final String version;
+
+ /**
+ * Change type
+ */
+ private final ChangeType changeType;
+
+ @Override
+ public String getType() {
+ return EVENT_TYPE;
+ }
+}
diff --git a/zanata-war/src/main/resources/db/changelogs/db.changelog-4.0.xml b/zanata-war/src/main/resources/db/changelogs/db.changelog-4.0.xml
index 3018166188..fa589ca050 100644
--- a/zanata-war/src/main/resources/db/changelogs/db.changelog-4.0.xml
+++ b/zanata-war/src/main/resources/db/changelogs/db.changelog-4.0.xml
@@ -25,22 +25,6 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
-
- Add webhookType to WebHook
-
-
-
-
-
-
-
-
- Migrate all WebHook to type DocumentMilestoneEvent
-
- UPDATE WebHook set webhookType='DocumentMilestoneEvent'
-
-
-
Add table AllowedApp.
@@ -152,4 +136,59 @@
constraintName="UKHProject_Glossary"/>
+
+ Add table WebHook_WebHookType
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Insert DocumentMilestoneEvent to WebHook_WebHookType for existing webhook
+
+ INSERT INTO WebHook_WebHookType (webhookId, type) SELECT id, 'DocumentMilestoneEvent' FROM WebHook
+
+
+
+
+
+
+
+
+
+ Migrate all WebHookType from WebHook to WebHook_WebHookType, and drop webhookType column from WebHook
+
+ INSERT INTO WebHook_WebHookType (webhookId, type) SELECT id, webhookType FROM WebHook
+
+
+
+
+
+ Change url column to text, Add unique constrains of url and project
+
+
+
+
diff --git a/zanata-war/src/main/resources/messages.properties b/zanata-war/src/main/resources/messages.properties
index c0e86c9430..2ae8c57f1e 100644
--- a/zanata-war/src/main/resources/messages.properties
+++ b/zanata-war/src/main/resources/messages.properties
@@ -319,17 +319,23 @@ jsf.project.LanguageUpdateFromGlobal=Updated languages from global settings.
jsf.project.AboutPageUpdated=About page updated.
jsf.project.AboutPageUpdateFailed=There was a problem while updating the about page.
jsf.project.AddWebhook=Add webhook
+jsf.project.NewWebhook=New webhook
jsf.project.RemoveWebhook=Webhook {0} removed.
jsf.project.AddNewWebhook=Webhook {0} added.
+jsf.project.webhookType.empty=Please select a type for this webhook.
+jsf.project.UpdateWebhook=Webhook {0} updated.
jsf.project.PayloadURL=Payload URL
jsf.project.WebhookType.label=Type
jsf.project.InvalidUrl=Invalid URL: {0}
-jsf.project.DuplicateUrl=Same URL and type is already in the list.
+jsf.project.DuplicateUrl=Same URL is already in the list. {0}
jsf.webhook.response.state={0}% {1}
jsf.webhook.test.label=Test webhook
jsf.webhook.test.tooltip=Fire a test event
-jsf.webhookType.DocumentMilestoneEvent.desc=Trigger when a document is 100% translated or approved
-jsf.webhookType.DocumentStatsEvent.desc=Trigger when translations are updated (singly or in a batch)
+jsf.webhookType.DocumentMilestoneEvent.desc=A document is 100% translated or approved
+jsf.webhookType.DocumentStatsEvent.desc=Translations are updated (singly or in a batch)
+jsf.webhookType.VersionChangedEvent.desc=Project version is created or removed
+jsf.webhookType.ProjectMaintainerChangedEvent.desc=Project maintainer is added or removed
+jsf.webhookType.SourceDocumentChangedEvent.desc=Source document is added or removed
#------ [home] > Projects > [project-id] ------
jsf.ReadOnlyVersions=Read-only versions
diff --git a/zanata-war/src/main/webapp/WEB-INF/layout/project/settings-tab-webhook.xhtml b/zanata-war/src/main/webapp/WEB-INF/layout/project/settings-tab-webhook.xhtml
new file mode 100644
index 0000000000..f53682ef11
--- /dev/null
+++ b/zanata-war/src/main/webapp/WEB-INF/layout/project/settings-tab-webhook.xhtml
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ #{msgs['jsf.project.WebHooks']}
+
+
+
+
+
+
+
+
+
+
#{msgs['jsf.project.NewWebhook']}
+
+
+
+
+
+
+
+ -
+ #{webhook.url}
+ #{webhookServiceImpl.getDisplayNames(webhook.types)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/zanata-war/src/main/webapp/WEB-INF/layout/project/settings-tab.xhtml b/zanata-war/src/main/webapp/WEB-INF/layout/project/settings-tab.xhtml
index 9dcdd40206..a662b73347 100644
--- a/zanata-war/src/main/webapp/WEB-INF/layout/project/settings-tab.xhtml
+++ b/zanata-war/src/main/webapp/WEB-INF/layout/project/settings-tab.xhtml
@@ -18,33 +18,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -204,89 +177,7 @@
-
- #{msgs['jsf.project.WebHooks']}
-
-
-
-
-
-
-
-
-
-
-
-
- -
- #{webhook.url}
- #{webhook.webhookType.name()}
-
-
-
-
-
-
-
-
-
- #{msgs['jsf.project.AddWebhook']}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/zanata-war/src/main/webapp/resources/script/components-script.js b/zanata-war/src/main/webapp/resources/script/components-script.js
index 1b57cff962..eaae5496d8 100644
--- a/zanata-war/src/main/webapp/resources/script/components-script.js
+++ b/zanata-war/src/main/webapp/resources/script/components-script.js
@@ -473,3 +473,37 @@ function onCheckboxValueChanged(checkbox, jsFunction) {
var key = jQuery(checkbox).children("input").first().val();
jsFunction(key, isChecked);
}
+
+
+/* ---------------------------------------------------------- */
+/*------------------ webhook-form component ------------------*/
+/* ---------------------------------------------------------- */
+
+function toggleButtons(form, disable) {
+ form.find('[name="addWebhookBtn"]').attr('disabled', disable);
+ form.find('[name="testWebhookBtn"]').attr('disabled', disable);
+ form.find('[name="updateWebhookBtn"]').attr('disabled', disable);
+}
+
+function onTestWebhook(formId, callback) {
+ var form = jQuery('#' + formId);
+ var url = form.find('[name="payloadUrlInput"]').val();
+ var secret = form.find('[name="secretInput"]').val();
+ callback(url, secret, formId);
+}
+
+function onAddWebhook(formId, callback) {
+ var form = jQuery('#' + formId);
+ var url = form.find('[name="payloadUrlInput"]').val();
+ var secret = form.find('[name="secretInput"]').val();
+ var types = form.find('[name="webhookTypes"]').val();
+ callback(url, secret, types, formId);
+}
+
+function onUpdateWebhook(id, formId, callback) {
+ var form = jQuery('#' + formId);
+ var url = form.find('[name="payloadUrlInput"]').val();
+ var secret = form.find('[name="secretInput"]').val();
+ var types = form.find('[name="webhookTypes"]').val();
+ callback(id, url, secret, types, formId);
+}
diff --git a/zanata-war/src/main/webapp/resources/zanata/webbook-form.xhtml b/zanata-war/src/main/webapp/resources/zanata/webbook-form.xhtml
new file mode 100644
index 0000000000..22735e3937
--- /dev/null
+++ b/zanata-war/src/main/webapp/resources/zanata/webbook-form.xhtml
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/zanata-war/src/test/java/org/zanata/search/FilterConstraintToQueryJpaTest.java b/zanata-war/src/test/java/org/zanata/search/FilterConstraintToQueryJpaTest.java
index 0fc7c2cc8e..77b908374d 100644
--- a/zanata-war/src/test/java/org/zanata/search/FilterConstraintToQueryJpaTest.java
+++ b/zanata-war/src/test/java/org/zanata/search/FilterConstraintToQueryJpaTest.java
@@ -31,6 +31,7 @@
import com.github.huangp.entityunit.maker.FixedValueMaker;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
/**
* @author Patrick Huang types =
+ Sets.newHashSet(WebhookType.DocumentMilestoneEvent);
webHooks.add(new WebHook(project, "http://test.example.com",
- WebhookType.DocumentMilestoneEvent, key));
+ types, key));
webHooks.add(new WebHook(project, "http://test1.example.com",
- WebhookType.DocumentMilestoneEvent, key));
+ types, key));
webHooks.add(new WebHook(project, "http://test1.example.com",
- WebhookType.DocumentStatsEvent, key));
+ Sets.newHashSet(WebhookType.DocumentStatsEvent), key));
when(projectIterationDAO.findById(versionId)).thenReturn(version);
when(version.getProject()).thenReturn(project);
@@ -141,28 +150,26 @@ public void setup() {
@Test
public void documentMilestoneEventTranslatedTest() {
- doNothing().when(spyService).publishDocumentMilestoneEvent(
- any(List.class), any(DocumentMilestoneEvent.class));
WordStatistic stats = new WordStatistic(0, 0, 0, 10, 0);
when(translationStateCacheImpl.getDocumentStatistics(docId, localeId))
.thenReturn(stats);
- runDocumentStatisticUpdatedTest(spyService, ContentState.New,
+ runDocumentStatisticUpdatedTest(documentService, ContentState.New,
ContentState.Translated);
- DocumentMilestoneEvent milestoneEvent =
- new DocumentMilestoneEvent(projectSlug, versionSlug,
- docIdString, localeId,
- msgs.format("jsf.webhook.response.state", milestone,
- ContentState.Translated), testUrl);
+ String message = msgs.format("jsf.webhook.response.state", milestone,
+ ContentState.Translated);
ArgumentCaptor captor = ArgumentCaptor.forClass(List.class);
- verify(spyService).publishDocumentMilestoneEvent(captor.capture(),
- eq(milestoneEvent));
+
+ verify(webhookService).processDocumentMilestone(
+ eq(projectSlug), eq(versionSlug), eq(docIdString),
+ eq(localeId), eq(message), eq(testUrl), captor.capture());
+
assertThat(captor.getValue().size(), is(2));
- assertThat(((WebHook) captor.getValue().get(0)).getWebhookType(),
- is(WebhookType.DocumentMilestoneEvent));
- assertThat(((WebHook) captor.getValue().get(1)).getWebhookType(),
- is(WebhookType.DocumentMilestoneEvent));
+ assertThat(((WebHook) captor.getValue().get(0)).getTypes(),
+ contains(WebhookType.DocumentMilestoneEvent));
+ assertThat(((WebHook) captor.getValue().get(1)).getTypes(),
+ contains(WebhookType.DocumentMilestoneEvent));
}
@Test
@@ -170,43 +177,38 @@ public void documentMilestoneEventTranslatedNot100Test() {
WordStatistic stats = new WordStatistic(0, 1, 0, 9, 0);
when(translationStateCacheImpl.getDocumentStatistics(docId, localeId))
.thenReturn(stats);
- runDocumentStatisticUpdatedTest(spyService, ContentState.New,
+ runDocumentStatisticUpdatedTest(documentService, ContentState.New,
ContentState.Translated);
- DocumentMilestoneEvent milestoneEvent =
- new DocumentMilestoneEvent(projectSlug, versionSlug,
- docIdString, localeId, testUrl,
- msgs.format("jsf.webhook.response.state", milestone,
- ContentState.Translated));
+ String message = msgs.format("jsf.webhook.response.state", milestone,
+ ContentState.Translated);
- verify(spyService, never()).publishDocumentMilestoneEvent(
- webHooks, milestoneEvent);
+ verify(webhookService, never()).processDocumentMilestone(
+ eq(projectSlug), eq(versionSlug), eq(docIdString),
+ eq(localeId), eq(message), eq(testUrl), Mockito.anyList());
}
@Test
public void documentMilestoneEventApprovedTest() {
- doNothing().when(spyService).publishDocumentMilestoneEvent(
- any(List.class), any(DocumentMilestoneEvent.class));
WordStatistic stats = new WordStatistic(10, 0, 0, 0, 0);
when(translationStateCacheImpl.getDocumentStatistics(docId, localeId))
.thenReturn(stats);
- runDocumentStatisticUpdatedTest(spyService, ContentState.Translated,
+ runDocumentStatisticUpdatedTest(documentService, ContentState.Translated,
ContentState.Approved);
-
- DocumentMilestoneEvent milestoneEvent =
- new DocumentMilestoneEvent(projectSlug,
- versionSlug, docIdString,
- localeId, msgs.format("jsf.webhook.response.state",
- milestone, ContentState.Approved), testUrl);
+ String message = msgs.format("jsf.webhook.response.state",
+ milestone, ContentState.Approved);
ArgumentCaptor captor = ArgumentCaptor.forClass(List.class);
- verify(spyService).publishDocumentMilestoneEvent(captor.capture(),
- eq(milestoneEvent));
+
+ verify(webhookService).processDocumentMilestone(
+ eq(projectSlug), eq(versionSlug), eq(docIdString),
+ eq(localeId), eq(message), eq(testUrl), captor.capture());
+
assertThat(captor.getValue().size(), is(2));
- assertThat(((WebHook) captor.getValue().get(0)).getWebhookType(),
- is(WebhookType.DocumentMilestoneEvent));
- assertThat(((WebHook) captor.getValue().get(1)).getWebhookType(),
- is(WebhookType.DocumentMilestoneEvent));
+ assertThat(((WebHook) captor.getValue().get(0)).getTypes(),
+ contains(WebhookType.DocumentMilestoneEvent));
+ assertThat(((WebHook) captor.getValue().get(1)).getTypes(),
+ contains(WebhookType.DocumentMilestoneEvent));
}
@Test
@@ -214,17 +216,13 @@ public void documentMilestoneEventApprovedNot100Test() {
WordStatistic stats = new WordStatistic(9, 0, 0, 1, 0);
when(translationStateCacheImpl.getDocumentStatistics(docId, localeId))
.thenReturn(stats);
- runDocumentStatisticUpdatedTest(spyService, ContentState.Translated,
+ runDocumentStatisticUpdatedTest(documentService, ContentState.Translated,
ContentState.Approved);
-
- DocumentMilestoneEvent milestoneEvent =
- new DocumentMilestoneEvent(projectSlug, versionSlug,
- docIdString, localeId, msgs.format(
- "jsf.webhook.response.state", milestone,
- ContentState.Approved), testUrl);
-
- verify(spyService, never()).publishDocumentMilestoneEvent(
- webHooks, milestoneEvent);
+ String message = msgs.format(
+ "jsf.webhook.response.state", milestone, ContentState.Approved);
+ verify(webhookService, never()).processDocumentMilestone(
+ eq(projectSlug), eq(versionSlug), eq(docIdString),
+ eq(localeId), eq(message), eq(testUrl), Mockito.anyList());
}
@Test
@@ -233,16 +231,13 @@ public void documentMilestoneEventSameStateTest1() {
when(translationStateCacheImpl.getDocumentStatistics(docId, localeId))
.thenReturn(stats);
- runDocumentStatisticUpdatedTest(spyService, ContentState.Approved,
+ runDocumentStatisticUpdatedTest(documentService, ContentState.Approved,
ContentState.Approved);
-
- DocumentMilestoneEvent milestoneEvent =
- new DocumentMilestoneEvent(projectSlug, versionSlug,
- docIdString, localeId, msgs.format(
- "jsf.webhook.response.state", milestone,
- ContentState.Approved), testUrl);
- verify(spyService, never()).publishDocumentMilestoneEvent(
- webHooks, milestoneEvent);
+ String message = msgs.format(
+ "jsf.webhook.response.state", milestone, ContentState.Approved);
+ verify(webhookService, never()).processDocumentMilestone(
+ eq(projectSlug), eq(versionSlug), eq(docIdString),
+ eq(localeId), eq(message), eq(testUrl), Mockito.anyList());
}
@Test
@@ -252,16 +247,14 @@ public void documentMilestoneEventSameStateTest2() {
when(translationStateCacheImpl.getDocumentStatistics(docId, localeId))
.thenReturn(stats);
- runDocumentStatisticUpdatedTest(spyService, ContentState.Translated,
+ runDocumentStatisticUpdatedTest(documentService, ContentState.Translated,
ContentState.Translated);
- DocumentMilestoneEvent milestoneEvent =
- new DocumentMilestoneEvent(projectSlug, versionSlug,
- docIdString, localeId, msgs.format(
- "jsf.webhook.response.state", milestone,
- ContentState.Translated), testUrl);
- verify(spyService, never()).publishDocumentMilestoneEvent(
- webHooks, milestoneEvent);
+ String message = msgs.format(
+ "jsf.webhook.response.state", milestone, ContentState.Translated);
+ verify(webhookService, never()).processDocumentMilestone(
+ eq(projectSlug), eq(versionSlug), eq(docIdString),
+ eq(localeId), eq(message), eq(testUrl), Mockito.anyList());
}
private void runDocumentStatisticUpdatedTest(
diff --git a/zanata-war/src/test/java/org/zanata/service/impl/TranslationUpdatedManagerTest.java b/zanata-war/src/test/java/org/zanata/service/impl/TranslationUpdatedManagerTest.java
index 43c7fa5e32..10f4897498 100644
--- a/zanata-war/src/test/java/org/zanata/service/impl/TranslationUpdatedManagerTest.java
+++ b/zanata-war/src/test/java/org/zanata/service/impl/TranslationUpdatedManagerTest.java
@@ -38,7 +38,6 @@
import org.zanata.events.DocStatsEvent;
import org.zanata.events.DocumentLocaleKey;
import org.zanata.model.type.WebhookType;
-import org.zanata.webhook.events.DocumentStatsEvent;
import org.zanata.model.HAccount;
import org.zanata.model.HDocument;
import org.zanata.model.HPerson;
@@ -50,8 +49,10 @@
import org.zanata.util.StatisticsUtil;
import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.is;
import static org.mockito.Matchers.eq;
@@ -69,6 +70,9 @@ public class TranslationUpdatedManagerTest {
@Mock
private TextFlowTargetDAO textFlowTargetDAO;
+ @Mock
+ private WebhookServiceImpl webhookService;
+
TranslationUpdatedManager manager;
List webHooks = Lists.newArrayList();
@@ -77,10 +81,8 @@ public class TranslationUpdatedManagerTest {
private String strDocId = "doc/test.txt";
private Long docId = 1L;
- private Long tfId = 1L;
private Long tftId = 1L;
private Long versionId = 1L;
- private Long personId = 1L;
private LocaleId localeId = LocaleId.DE;
private String versionSlug = "versionSlug";
private String projectSlug = "projectSlug";
@@ -93,7 +95,7 @@ public class TranslationUpdatedManagerTest {
public void setup() {
MockitoAnnotations.initMocks(this);
manager = new TranslationUpdatedManager();
- manager.init(documentDAO, textFlowTargetDAO);
+ manager.init(documentDAO, textFlowTargetDAO, webhookService);
HProjectIteration version = Mockito.mock(HProjectIteration.class);
HProject project = Mockito.mock(HProject.class);
@@ -103,10 +105,10 @@ public void setup() {
HTextFlowTarget target = Mockito.mock(HTextFlowTarget.class);
webHooks = Lists
- .newArrayList(new WebHook(project, "http://test.example.com",
- WebhookType.DocumentMilestoneEvent, key),
- new WebHook(project, "http://test.example.com",
- WebhookType.DocumentStatsEvent, key));
+ .newArrayList(new WebHook(project, "http://test.example.com",
+ Sets.newHashSet(WebhookType.DocumentMilestoneEvent), key),
+ new WebHook(project, "http://test.example.com",
+ Sets.newHashSet(WebhookType.DocumentStatsEvent), key));
when(person.getAccount()).thenReturn(account);
when(account.getUsername()).thenReturn(username);
@@ -141,19 +143,14 @@ public void onDocStatUpdateTest() {
DocStatsEvent event =
new DocStatsEvent(key, versionId, contentStates, tftId);
- DocumentStatsEvent webhookEvent =
- new DocumentStatsEvent(username, projectSlug,
- versionSlug, strDocId, event.getKey().getLocaleId(),
- contentStates);
-
spyManager.docStatsUpdated(event);
-
verify(spyManager).processWebHookEvent(event);
ArgumentCaptor captor = ArgumentCaptor.forClass(List.class);
- verify(spyManager).publishWebhookEvent(captor.capture(),
- eq(webhookEvent));
+ verify(webhookService).processDocumentStats(eq(username),
+ eq(projectSlug), eq(versionSlug), eq(strDocId), eq(localeId),
+ eq(contentStates), captor.capture());
assertThat(captor.getValue().size(), is(1));
- assertThat(((WebHook) captor.getValue().get(0)).getWebhookType(),
- is(WebhookType.DocumentStatsEvent));
+ assertThat(((WebHook) captor.getValue().get(0)).getTypes(),
+ contains(WebhookType.DocumentStatsEvent));
}
}