diff --git a/src/main/java/org/hibernate/infra/replicate/jira/JiraConfig.java b/src/main/java/org/hibernate/infra/replicate/jira/JiraConfig.java index ce0f0f9..51f042b 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/JiraConfig.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/JiraConfig.java @@ -253,6 +253,20 @@ interface UserValueMapping extends ValueMapping { * Jira. */ Optional notMappedAssignee(); + + /** + * Allows specifying an URL template that will be passed exactly 1 argument + * (mapped user value). E.g. a template can look like + * {@code https://my-jira-server/secure/ViewProfile.jspa?name={arg1}}, where + * {@code arg1} is the mapped user value from {@link #mapping()}. If not + * specified the link to the upstream profile will be created with a template of + * {@code https://my-upstream-jira-server/jira/people/{arg1}}, where + * {@code arg1} is the original user id i.e. the {@link #mapping() mapping key}. + *

+ * Note, this profile URL only applies to the profiles that have a defined + * {@link #mapping() mapping}. + */ + Optional profileUrl(); } /** diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/JiraService.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/JiraService.java index fc15375..e996fb2 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/JiraService.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/JiraService.java @@ -194,10 +194,10 @@ public void registerManagementRoutes(@Observes ManagementInterface mi) { }); rc.end(); }); - mi.router().post("/sync/issues/query").consumes(MediaType.APPLICATION_JSON).blockingHandler(rc -> { - JsonObject request = rc.body().asJsonObject(); - String project = request.getString("project"); - String query = request.getString("query"); + mi.router().get("/sync/issues/query/full/:project").blockingHandler(rc -> { + // syncs issue with comments, links etc. + String project = rc.pathParam("project"); + String query = rc.queryParam("query").getFirst(); HandlerProjectContext context = contextPerProject.get(project); diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraCommentUpsertEventHandler.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraCommentUpsertEventHandler.java index 2abfc3b..7574f3b 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraCommentUpsertEventHandler.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraCommentUpsertEventHandler.java @@ -8,7 +8,6 @@ import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraComment; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraComments; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraIssue; -import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraTextContent; import org.hibernate.infra.replicate.jira.service.reporting.ReportingConfig; public class JiraCommentUpsertEventHandler extends JiraCommentEventHandler { @@ -57,12 +56,12 @@ private JiraComment prepareComment(JiraIssue issue, JiraComment source) { private String prepareCommentQuote(JiraIssue issue, JiraComment comment) { URI jiraCommentUri = createJiraCommentUri(issue, comment); - URI jiraUserUri = createJiraUserUri(comment.self, comment.author); + UserData userData = userData(comment.self, comment.author, "the user %s"); String content = """ - {quote}This [comment|%s] was posted by the [user %s|%s].{quote} + {quote}This [comment|%s] was posted by [%s|%s].{quote} - """.formatted(jiraCommentUri, JiraTextContent.userIdPart(comment.author), jiraUserUri); + """.formatted(jiraCommentUri, userData.name(), userData.uri()); return truncateContent(content); } diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraEventHandler.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraEventHandler.java index fca99ab..d44d07c 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraEventHandler.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraEventHandler.java @@ -12,6 +12,7 @@ import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraComment; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraIssue; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraSimpleObject; +import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraTextContent; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraUser; import org.hibernate.infra.replicate.jira.service.reporting.FailureCollector; import org.hibernate.infra.replicate.jira.service.reporting.ReportingConfig; @@ -133,6 +134,38 @@ protected Optional user(JiraUser sourceUser) { userId -> context.projectGroup().users().mapping().get(sourceUser.accountId))); } + protected UserData userData(URI someUpstreamUri, JiraUser user) { + return userData(someUpstreamUri, user, "user %s"); + } + + protected UserData userData(URI someUpstreamUri, JiraUser user, String unmappedUserPattern) { + if (user == null) { + return null; + } + Optional mappedUser = user(user); + URI jiraUserUri; + String userName; + if (mappedUser.isPresent()) { + // means it is one of the users that we've mapped so we would want to point it + // to the user on the "downstream" side and also add a name: + Optional template = context.projectGroup().users().profileUrl(); + if (template.isPresent()) { + jiraUserUri = UriBuilder.fromUri(template.get()).build(mappedUser.get()); + } else { + jiraUserUri = createJiraUserUri(someUpstreamUri, user); + } + // NOTE: we are using the upstream username here, so that we do not make an + // extra call to get the downstream name. We probably can do that once at the + // start and cache it, but for now this should be a "good-enough-approximation". + userName = user.displayName; + + } else { + jiraUserUri = createJiraUserUri(someUpstreamUri, user); + userName = unmappedUserPattern.formatted(JiraTextContent.userIdPart(user)); + } + return new UserData(userName, jiraUserUri); + } + private static Map createMapping(List source, List destination) { Map mapping = new HashMap<>(); @@ -189,4 +222,7 @@ protected String toDestinationKey(String key) { } public abstract String toString(); + + protected record UserData(String name, URI uri) { + } } diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraIssueAbstractEventHandler.java b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraIssueAbstractEventHandler.java index 59101ed..396fb60 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraIssueAbstractEventHandler.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraIssueAbstractEventHandler.java @@ -13,7 +13,6 @@ import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraIssueLink; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraRemoteLink; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraSimpleObject; -import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraTextContent; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraTransition; import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraUser; import org.hibernate.infra.replicate.jira.service.reporting.ReportingConfig; @@ -161,8 +160,10 @@ protected Optional prepareParentLink(String destinationKey, JiraI private String prepareDescriptionQuote(JiraIssue issue) { URI issueUri = createJiraIssueUri(issue); - URI reporterUri = createJiraUserUri(issue.self, issue.fields.reporter); - URI assigneeUri = createJiraUserUri(issue.self, issue.fields.assignee); + + UserData assignee = userData(issue.self, issue.fields.assignee); + UserData reporter = userData(issue.self, issue.fields.reporter); + return """ {quote}This issue is created as a copy of [%s|%s]. @@ -172,12 +173,8 @@ private String prepareDescriptionQuote(JiraIssue issue) { """.formatted(issue.key, issueUri, - assigneeUri == null - ? " Unassigned" - : "[user %s|%s]".formatted(JiraTextContent.userIdPart(issue.fields.assignee), assigneeUri), - reporterUri == null - ? " Unknown" - : "[user %s|%s]".formatted(JiraTextContent.userIdPart(issue.fields.reporter), reporterUri)); + assignee == null ? " Unassigned" : "[%s|%s]".formatted(assignee.name(), assignee.uri()), + reporter == null ? " Unknown" : "[%s|%s]".formatted(reporter.name(), reporter.uri())); } } diff --git a/src/main/java/org/hibernate/infra/replicate/jira/service/validation/RequestSignatureFilter.java b/src/main/java/org/hibernate/infra/replicate/jira/service/validation/RequestSignatureFilter.java index 896279e..349e4e3 100644 --- a/src/main/java/org/hibernate/infra/replicate/jira/service/validation/RequestSignatureFilter.java +++ b/src/main/java/org/hibernate/infra/replicate/jira/service/validation/RequestSignatureFilter.java @@ -21,6 +21,7 @@ import org.jboss.resteasy.reactive.server.ServerRequestFilter; import org.jboss.resteasy.reactive.server.WithFormRead; +import io.quarkus.logging.Log; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.ws.rs.container.ContainerRequestContext; @@ -63,6 +64,7 @@ public Response checkSignature(ContainerRequestContext requestContext) throws IO String signature = requestContext.getHeaderString("x-hub-signature"); if (signature == null || !requestContext.hasEntity()) { + Log.warnf("Rejecting a web hook event because of the missing signature. Posted to %s", path); return Response.status(401).entity("Invalid request. Missing x-hub-signature header.").build(); } try (InputStream entityStream = requestContext.getEntityStream()) { @@ -70,6 +72,7 @@ public Response checkSignature(ContainerRequestContext requestContext) throws IO final String calculatedSignature = sign(mac, payload); if (!calculatedSignature.equals(signature)) { + Log.warnf("Rejecting a web hook event because of the signature mismatch. Posted to %s", path); return Response.status(401).entity("Signatures do not match.").build(); } requestContext.setEntityStream(new ByteArrayInputStream(payload));