Skip to content

Commit

Permalink
Merge branch 'main' into feature/cleanup_finished_jobs_by_timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
nirikash authored Sep 2, 2024
2 parents c2416df + 77a76d4 commit d4d08f3
Show file tree
Hide file tree
Showing 45 changed files with 697 additions and 835 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,10 @@ ch.sbb.polarion.extension.pdf-exporter.internalizeExternalCss=true
## Extension Configuration

1. On the top of the project's navigation pane click ⚙ (Actions) ➙ 🔧 Administration. Project's administration page will be opened.
2. On the administration's navigation pane select `PDF Export`. There are 5 sub-menus with different configuration options for PDF Exporter.
3. For 5 of these options (Cover page, Header and Footer, Localization, Webhooks and Filename template) `Quick Help` section available with option short description. For the rest 2
2. On the administration's navigation pane select `PDF Export`. There are expandable sub-menus with different configuration options for PDF Exporter.
3. For some of these options (Cover page, Header and Footer, Localization, Webhooks and Filename template) `Quick Help` section available with option short description. For the rest
(Style package, Stylesheets) there's no `Quick Help` section as their content is self-evident.
4. To change configuration of PDF exporter extension just edit corresponding section and press `Save` button.
4. To change configuration of PDF Exporter extension just edit corresponding section and press `Save` button.

## Usage

Expand Down
49 changes: 48 additions & 1 deletion docs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,53 @@
]
}
},
"/api/settings/style-package/suitable-names": {
"get": {
"operationId": "getSuitableStylePackageNames",
"parameters": [
{
"in": "query",
"name": "projectId",
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "spaceId",
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "documentName",
"schema": {
"type": "string"
}
}
],
"responses": {
"default": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/SettingName"
},
"type": "array"
}
}
},
"description": "default response"
}
},
"summary": "Get list of style packages suitable for a document",
"tags": [
"Settings"
]
}
},
"/api/settings/{feature}/default-content": {
"get": {
"operationId": "getDefaultValues_1",
Expand Down Expand Up @@ -1165,7 +1212,7 @@
"description": "default response"
}
},
"summary": "Returns boolean value telling if webhooks are enabled or not",
"summary": "Gets webhooks status - if they are enabled or not",
"tags": [
"Utility resources"
]
Expand Down
8 changes: 2 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>ch.sbb.polarion.extensions</groupId>
<artifactId>ch.sbb.polarion.extension.generic</artifactId>
<version>6.7.0</version>
<version>7.0.0</version>
</parent>

<artifactId>ch.sbb.polarion.extension.pdf-exporter</artifactId>
Expand Down Expand Up @@ -59,6 +59,7 @@

<maven-jar-plugin.Extension-Context>pdf-exporter</maven-jar-plugin.Extension-Context>
<maven-jar-plugin.Automatic-Module-Name>ch.sbb.polarion.extension.pdf_exporter</maven-jar-plugin.Automatic-Module-Name>
<maven-jar-plugin.Discover-Base-Package>ch.sbb.polarion.extension.pdf.exporter</maven-jar-plugin.Discover-Base-Package>
<web.app.name>${maven-jar-plugin.Extension-Context}</web.app.name>

<!--suppress UnresolvedMavenProperty -->
Expand Down Expand Up @@ -109,11 +110,6 @@
<artifactId>byte-buddy</artifactId>
<version>${byte-buddy.version}</version>
</dependency>
<dependency>
<groupId>com.google.re2j</groupId>
<artifactId>re2j</artifactId>
<version>${re2j.version}</version>
</dependency>

<!-- Test dependencies -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package ch.sbb.polarion.extension.pdf.exporter;

import ch.sbb.polarion.extension.generic.GenericUiServlet;
import ch.sbb.polarion.extension.generic.properties.CurrentExtensionConfiguration;
import ch.sbb.polarion.extension.pdf.exporter.properties.PdfExporterExtensionConfiguration;

import java.io.Serial;

Expand All @@ -13,6 +11,5 @@ public class PdfExporterAdminUiServlet extends GenericUiServlet {

public PdfExporterAdminUiServlet() {
super("pdf-exporter-admin");
CurrentExtensionConfiguration.getInstance().setExtensionConfiguration(PdfExporterExtensionConfiguration.getInstance());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ public class PdfExporterFormExtension implements IFormExtension {
private static final String OPTION_SELECTED = "<option value='%s' selected";
private static final String SELECTED = "selected";

private final PdfExporterPolarionService polarionService = new PdfExporterPolarionService();

@Override
@Nullable
public String render(@NotNull IFormExtensionContext context) {
Expand All @@ -66,7 +68,7 @@ public String renderForm(@NotNull SharedContext context, @NotNull IPObject objec
String scope = ScopeUtils.getScopeFromProject(module.getProject().getId());
form = form.replace("{SCOPE_VALUE}", scope);

Collection<SettingName> stylePackageNames = getSettingNames(StylePackageSettings.FEATURE_NAME, scope);
Collection<SettingName> stylePackageNames = getSuitableStylePackages(module);
SettingName stylePackageNameToSelect = getStylePackageNameToSelect(stylePackageNames); // Either default (if exists) or first from list

form = form.replace("{STYLE_PACKAGE_OPTIONS}", generateSelectOptions(stylePackageNames, stylePackageNameToSelect != null ? stylePackageNameToSelect.getName() : null));
Expand Down Expand Up @@ -109,7 +111,7 @@ public String renderForm(@NotNull SharedContext context, @NotNull IPObject objec

private SettingName getStylePackageNameToSelect(Collection<SettingName> stylePackageNames) {
return stylePackageNames.stream()
.filter(stylePackageName -> NamedSettings.DEFAULT_NAME.equals(stylePackageName.getName()))
.filter(stylePackageName -> !NamedSettings.DEFAULT_NAME.equals(stylePackageName.getName()))
.findFirst()
.orElse(stylePackageNames.stream()
.findFirst()
Expand Down Expand Up @@ -155,12 +157,25 @@ private String adjustWebhooks(String scope, String form, StylePackageModel style
Collection<SettingName> webhooksNames = getSettingNames(WebhooksSettings.FEATURE_NAME, scope);
boolean noHooks = StringUtils.isEmpty(stylePackage.getWebhooks());
String webhooksOptions = generateSelectOptions(webhooksNames, noHooks ? NamedSettings.DEFAULT_NAME : stylePackage.getWebhooks());
form = form.replace("{WEBHOOKS_DISPLAY}", PdfExporterExtensionConfiguration.getInstance().areWebhooksEnabled() ? "" : "hidden");
form = form.replace("{WEBHOOKS_DISPLAY}", PdfExporterExtensionConfiguration.getInstance().getWebhooksEnabled() ? "" : "hidden");
form = form.replace("{WEBHOOKS_OPTIONS}", webhooksOptions);
form = form.replace("{WEBHOOKS_SELECTOR_DISPLAY}", noHooks ? "none" : "inline-block");
return form.replace("{WEBHOOKS_SELECTED}", noHooks ? "" : "checked");
}

private Collection<SettingName> getSuitableStylePackages(@NotNull IModule module) {
String locationPath = module.getModuleLocation().getLocationPath();
String spaceId = "";
final String documentName;
if (locationPath.contains("/")) {
spaceId = locationPath.substring(0, locationPath.lastIndexOf('/'));
documentName = locationPath.substring(locationPath.lastIndexOf('/') + 1);
} else {
documentName = locationPath;
}
return polarionService.getSuitableStylePackages(module.getProject().getId(), spaceId, documentName);
}

private Collection<SettingName> getSettingNames(@NotNull String featureName, @NotNull String scope) {
try {
return NamedSettingsRegistry.INSTANCE.getByFeatureName(featureName).readNames(scope);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import ch.sbb.polarion.extension.pdf.exporter.util.PdfExporterFileResourceProvider;
import ch.sbb.polarion.extension.pdf.exporter.util.PdfTemplateProcessor;
import ch.sbb.polarion.extension.pdf.exporter.util.html.HtmlLinksHelper;
import ch.sbb.polarion.extension.pdf.exporter.util.regex.RegexMatcher;
import ch.sbb.polarion.extension.generic.regex.RegexMatcher;
import ch.sbb.polarion.extension.pdf.exporter.weasyprint.WeasyPrintOptions;
import ch.sbb.polarion.extension.pdf.exporter.weasyprint.service.WeasyPrintServiceConnector;
import lombok.Getter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
import ch.sbb.polarion.extension.pdf.exporter.rest.model.conversion.DocumentType;
import ch.sbb.polarion.extension.pdf.exporter.rest.model.conversion.ExportParams;
import ch.sbb.polarion.extension.pdf.exporter.rest.model.settings.headerfooter.HeaderFooterModel;
import ch.sbb.polarion.extension.pdf.exporter.rest.model.settings.hooks.WebhooksModel;
import ch.sbb.polarion.extension.pdf.exporter.rest.model.settings.webhooks.AuthType;
import ch.sbb.polarion.extension.pdf.exporter.rest.model.settings.webhooks.WebhookConfig;
import ch.sbb.polarion.extension.pdf.exporter.rest.model.settings.webhooks.WebhooksModel;
import ch.sbb.polarion.extension.pdf.exporter.service.PdfExporterPolarionService;
import ch.sbb.polarion.extension.pdf.exporter.settings.CssSettings;
import ch.sbb.polarion.extension.pdf.exporter.settings.HeaderFooterSettings;
import ch.sbb.polarion.extension.pdf.exporter.settings.WebhooksSettings;
import ch.sbb.polarion.extension.pdf.exporter.settings.LocalizationSettings;
import ch.sbb.polarion.extension.pdf.exporter.settings.WebhooksSettings;
import ch.sbb.polarion.extension.pdf.exporter.util.EnumValuesProvider;
import ch.sbb.polarion.extension.pdf.exporter.util.HtmlLogger;
import ch.sbb.polarion.extension.pdf.exporter.util.HtmlProcessor;
Expand All @@ -27,11 +29,12 @@
import ch.sbb.polarion.extension.pdf.exporter.util.placeholder.PlaceholderProcessor;
import ch.sbb.polarion.extension.pdf.exporter.util.velocity.VelocityEvaluator;
import ch.sbb.polarion.extension.pdf.exporter.weasyprint.WeasyPrintOptions;
import com.fasterxml.jackson.databind.ObjectMapper;
import ch.sbb.polarion.extension.pdf.exporter.weasyprint.service.WeasyPrintServiceConnector;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.polarion.alm.tracker.model.ITrackerProject;
import com.polarion.core.util.StringUtils;
import com.polarion.core.util.logging.Logger;
import com.polarion.platform.internal.security.UserAccountVault;
import lombok.AllArgsConstructor;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
Expand All @@ -43,12 +46,15 @@
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
Expand Down Expand Up @@ -148,39 +154,43 @@ public byte[] convertToPdf(@NotNull ExportParams exportParams, @Nullable ExportM

private @NotNull String applyWebhooks(@NotNull ExportParams exportParams, @NotNull String htmlContent) {
// Skip webhooks processing among other if this functionality is not enabled by system administrator
if (!PdfExporterExtensionConfiguration.getInstance().areWebhooksEnabled() || exportParams.getWebhooks() == null) {
if (!PdfExporterExtensionConfiguration.getInstance().getWebhooksEnabled() || exportParams.getWebhooks() == null) {
return htmlContent;
}

WebhooksModel webhooksModel = new WebhooksSettings().load(exportParams.getProjectId(), SettingId.fromName(exportParams.getWebhooks()));
String result = htmlContent;
for (String webhook : webhooksModel.getWebhooks()) {
result = applyWebhook(webhook, exportParams, result);
for (WebhookConfig webhookConfig : webhooksModel.getWebhookConfigs()) {
result = applyWebhook(webhookConfig, exportParams, result);
}
return result;
}

private @NotNull String applyWebhook(@NotNull String webhook, @NotNull ExportParams exportParams, @NotNull String htmlContent) {
private @NotNull String applyWebhook(@NotNull WebhookConfig webhookConfig, @NotNull ExportParams exportParams, @NotNull String htmlContent) {
Client client = null;
try {
client = ClientBuilder.newClient();
WebTarget webTarget = client.target(webhook).register(MultiPartFeature.class);
WebTarget webTarget = client.target(webhookConfig.getUrl()).register(MultiPartFeature.class);

FormDataMultiPart multipart = new FormDataMultiPart();
multipart.bodyPart(new FormDataBodyPart("exportParams", new ObjectMapper().writeValueAsString(exportParams), MediaType.APPLICATION_JSON_TYPE));
multipart.bodyPart(new FormDataBodyPart("html", htmlContent.getBytes(StandardCharsets.UTF_8), MediaType.APPLICATION_OCTET_STREAM_TYPE));

try (Response response = webTarget.request(MediaType.TEXT_PLAIN).post(Entity.entity(multipart, multipart.getMediaType()))) {
Invocation.Builder requestBuilder = webTarget.request(MediaType.TEXT_PLAIN);

addAuthHeader(webhookConfig, requestBuilder);

try (Response response = requestBuilder.post(Entity.entity(multipart, multipart.getMediaType()))) {
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
try (InputStream inputStream = response.readEntity(InputStream.class)) {
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
}
} else {
logger.error(String.format("Could not get proper response from webhook [%s]: response status %s", webhook, response.getStatus()));
logger.error(String.format("Could not get proper response from webhook [%s]: response status %s", webhookConfig.getUrl(), response.getStatus()));
}
}
} catch (Exception e) {
logger.error(String.format("Could not get response from webhook [%s]", webhook), e);
logger.error(String.format("Could not get response from webhook [%s]", webhookConfig.getUrl()), e);
} finally {
if (client != null) {
client.close();
Expand All @@ -191,6 +201,31 @@ public byte[] convertToPdf(@NotNull ExportParams exportParams, @Nullable ExportM
return htmlContent;
}

private static void addAuthHeader(@NotNull WebhookConfig webhookConfig, @NotNull Invocation.Builder requestBuilder) {
if (webhookConfig.getAuthType() == null || webhookConfig.getAuthTokenName() == null) {
return;
}

String authInfoFromUserAccountVault = getAuthInfoFromUserAccountVault(webhookConfig.getAuthType(), webhookConfig.getAuthTokenName());
if (authInfoFromUserAccountVault == null) {
return;
}

requestBuilder.header(HttpHeaders.AUTHORIZATION, webhookConfig.getAuthType().getAuthHeaderPrefix() + " " + authInfoFromUserAccountVault);
}

private static @Nullable String getAuthInfoFromUserAccountVault(@NotNull AuthType authType, @NotNull String authTokenName) {
@NotNull UserAccountVault.Credentials credentials = UserAccountVault.getInstance().getCredentialsForKey(authTokenName);

return switch (authType) {
case BASIC_AUTH -> {
String authInfo = credentials.getUser() + ":" + credentials.getPassword();
yield Base64.getEncoder().encodeToString(authInfo.getBytes());
}
case BEARER_TOKEN -> credentials.getPassword();
};
}

@VisibleForTesting
byte[] generatePdf(
LiveDocHelper.DocumentData documentData,
Expand Down Expand Up @@ -219,8 +254,8 @@ String postProcessDocumentContent(@NotNull ExportParams exportParams, @Nullable
@NotNull
@VisibleForTesting
String composeHtml(@NotNull String documentName,
@NotNull HtmlData htmlData,
@NotNull ExportParams exportParams) {
@NotNull HtmlData htmlData,
@NotNull ExportParams exportParams) {
String content = htmlData.headerFooterContent
+ "<div class='content'>" + htmlData.documentContent + "</div>";
return pdfTemplateProcessor.processUsing(exportParams, documentName, htmlData.cssContent, content);
Expand All @@ -236,9 +271,9 @@ String getCssContent(
String listStyles = new PdfExporterListStyleProvider(exportParams.getNumberedListStyles()).getStyle();
String css = pdfStyles
+ (exportParams.getHeadersColor() != null ?
" h1, h2, h3, h4, h5, h6, .content .title {"
+ " color: " + exportParams.getHeadersColor() + ";"
+ " }"
" h1, h2, h3, h4, h5, h6, .content .title {" +
" color: " + exportParams.getHeadersColor() + ";" +
" }"
: "")
+ listStyles;

Expand Down Expand Up @@ -279,5 +314,6 @@ private String appendWikiCss(String css) {
return css + System.lineSeparator() + ScopeUtils.getFileContent("default/wiki.css");
}

record HtmlData(String cssContent, String documentContent, String headerFooterContent) {}
record HtmlData(String cssContent, String documentContent, String headerFooterContent) {
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package ch.sbb.polarion.extension.pdf.exporter.properties;

import ch.sbb.polarion.extension.generic.properties.CurrentExtensionConfiguration;
import ch.sbb.polarion.extension.generic.properties.ExtensionConfiguration;
import ch.sbb.polarion.extension.generic.util.Discoverable;
import com.polarion.core.config.impl.SystemValueReader;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.List;

@Discoverable
public class PdfExporterExtensionConfiguration extends ExtensionConfiguration {

public static final String WEASYPRINT_SERVICE = "weasyprint.service";
Expand All @@ -28,7 +31,7 @@ public Boolean getInternalizeExternalCss() {
}

@NotNull
public Boolean areWebhooksEnabled() {
public Boolean getWebhooksEnabled() {
return SystemValueReader.getInstance().readBoolean(getPropertyPrefix() + WEBHOOKS_ENABLED, false);
}

Expand All @@ -42,16 +45,7 @@ public Boolean areWebhooksEnabled() {
return supportedProperties;
}

public PdfExporterExtensionConfiguration() {
super();
}

public static PdfExporterExtensionConfiguration getInstance() {
return PdfExporterExtensionConfigurationHolder.INSTANCE;
return (PdfExporterExtensionConfiguration) CurrentExtensionConfiguration.getInstance().getExtensionConfiguration();
}

private static class PdfExporterExtensionConfigurationHolder {
private static final PdfExporterExtensionConfiguration INSTANCE = new PdfExporterExtensionConfiguration();
}

}
Loading

0 comments on commit d4d08f3

Please sign in to comment.